diff --git a/vendor/github.com/mattermost/platform/einterfaces/cluster.go b/vendor/github.com/mattermost/platform/einterfaces/cluster.go
index 0d7bf7e8..b7ba5514 100644
--- a/vendor/github.com/mattermost/platform/einterfaces/cluster.go
+++ b/vendor/github.com/mattermost/platform/einterfaces/cluster.go
@@ -12,10 +12,15 @@ type ClusterInterface interface {
StopInterNodeCommunication()
GetClusterInfos() []*model.ClusterInfo
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
- RemoveAllSessionsForUserId(userId string)
+ ClearSessionCacheForUser(userId string)
InvalidateCacheForUser(userId string)
InvalidateCacheForChannel(channelId string)
+ InvalidateCacheForChannelByName(teamId, name string)
+ InvalidateCacheForChannelMembers(channelId string)
+ InvalidateCacheForChannelMembersNotifyProps(channelId string)
InvalidateCacheForChannelPosts(channelId string)
+ InvalidateCacheForWebhook(webhookId string)
+ InvalidateCacheForReactions(postId string)
Publish(event *model.WebSocketEvent)
UpdateStatus(status *model.Status)
GetLogs() ([]string, *model.AppError)
diff --git a/vendor/github.com/mattermost/platform/einterfaces/metrics.go b/vendor/github.com/mattermost/platform/einterfaces/metrics.go
index f7d660da..cc2eea97 100644
--- a/vendor/github.com/mattermost/platform/einterfaces/metrics.go
+++ b/vendor/github.com/mattermost/platform/einterfaces/metrics.go
@@ -8,6 +8,7 @@ type MetricsInterface interface {
StopServer()
IncrementPostCreate()
+ IncrementWebhookPost()
IncrementPostSentEmail()
IncrementPostSentPush()
IncrementPostBroadcast()
@@ -17,6 +18,9 @@ type MetricsInterface interface {
IncrementHttpError()
ObserveHttpRequestDuration(elapsed float64)
+ IncrementClusterRequest()
+ ObserveClusterRequestDuration(elapsed float64)
+
IncrementLogin()
IncrementLoginFail()
@@ -25,6 +29,10 @@ type MetricsInterface interface {
IncrementMemCacheHitCounter(cacheName string)
IncrementMemCacheMissCounter(cacheName string)
+ IncrementMemCacheMissCounterSession()
+ IncrementMemCacheHitCounterSession()
+
+ IncrementWebsocketEvent(eventType string)
AddMemCacheHitCounter(cacheName string, amount float64)
AddMemCacheMissCounter(cacheName string, amount float64)
diff --git a/vendor/github.com/mattermost/platform/einterfaces/saml.go b/vendor/github.com/mattermost/platform/einterfaces/saml.go
index af2e815a..478cf8a6 100644
--- a/vendor/github.com/mattermost/platform/einterfaces/saml.go
+++ b/vendor/github.com/mattermost/platform/einterfaces/saml.go
@@ -10,7 +10,7 @@ import (
type SamlInterface interface {
ConfigureSP() *model.AppError
BuildRequest(relayState string) (*model.SamlAuthRequest, *model.AppError)
- DoLogin(encodedXML string, relayState map[string]string) (*model.User, *model.AppError)
+ DoLogin(encodedXML string, relayState map[string]string, siteURL string) (*model.User, *model.AppError)
GetMetadata() (string, *model.AppError)
}
diff --git a/vendor/github.com/mattermost/platform/model/authorization.go b/vendor/github.com/mattermost/platform/model/authorization.go
index 75aebf55..a7a6f374 100644
--- a/vendor/github.com/mattermost/platform/model/authorization.go
+++ b/vendor/github.com/mattermost/platform/model/authorization.go
@@ -27,7 +27,10 @@ var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission
var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission
var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission
var PERMISSION_MANAGE_ROLES *Permission
+var PERMISSION_MANAGE_TEAM_ROLES *Permission
+var PERMISSION_MANAGE_CHANNEL_ROLES *Permission
var PERMISSION_CREATE_DIRECT_CHANNEL *Permission
+var PERMISSION_CREATE_GROUP_CHANNEL *Permission
var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission
var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission
var PERMISSION_LIST_TEAM_CHANNELS *Permission
@@ -46,9 +49,13 @@ var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission
var PERMISSION_CREATE_POST *Permission
var PERMISSION_EDIT_POST *Permission
var PERMISSION_EDIT_OTHERS_POSTS *Permission
+var PERMISSION_DELETE_POST *Permission
+var PERMISSION_DELETE_OTHERS_POSTS *Permission
var PERMISSION_REMOVE_USER_FROM_TEAM *Permission
+var PERMISSION_CREATE_TEAM *Permission
var PERMISSION_MANAGE_TEAM *Permission
var PERMISSION_IMPORT_TEAM *Permission
+var PERMISSION_VIEW_TEAM *Permission
// General permission that encompases all system admin functions
// in the future this could be broken up to allow access to some
@@ -123,6 +130,16 @@ func InitalizePermissions() {
"authentication.permissions.manage_roles.name",
"authentication.permissions.manage_roles.description",
}
+ PERMISSION_MANAGE_TEAM_ROLES = &Permission{
+ "manage_team_roles",
+ "authentication.permissions.manage_team_roles.name",
+ "authentication.permissions.manage_team_roles.description",
+ }
+ PERMISSION_MANAGE_CHANNEL_ROLES = &Permission{
+ "manage_channel_roles",
+ "authentication.permissions.manage_channel_roles.name",
+ "authentication.permissions.manage_channel_roles.description",
+ }
PERMISSION_MANAGE_SYSTEM = &Permission{
"manage_system",
"authentication.permissions.manage_system.name",
@@ -133,6 +150,11 @@ func InitalizePermissions() {
"authentication.permissions.create_direct_channel.name",
"authentication.permissions.create_direct_channel.description",
}
+ PERMISSION_CREATE_GROUP_CHANNEL = &Permission{
+ "create_group_channel",
+ "authentication.permissions.create_group_channel.name",
+ "authentication.permissions.create_group_channel.description",
+ }
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{
"manage__publicchannel_properties",
"authentication.permissions.manage_public_channel_properties.name",
@@ -223,11 +245,26 @@ func InitalizePermissions() {
"authentication.permissions.edit_others_posts.name",
"authentication.permissions.edit_others_posts.description",
}
+ PERMISSION_DELETE_POST = &Permission{
+ "delete_post",
+ "authentication.permissions.delete_post.name",
+ "authentication.permissions.delete_post.description",
+ }
+ PERMISSION_DELETE_OTHERS_POSTS = &Permission{
+ "delete_others_posts",
+ "authentication.permissions.delete_others_posts.name",
+ "authentication.permissions.delete_others_posts.description",
+ }
PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{
"remove_user_from_team",
"authentication.permissions.remove_user_from_team.name",
"authentication.permissions.remove_user_from_team.description",
}
+ PERMISSION_CREATE_TEAM = &Permission{
+ "create_team",
+ "authentication.permissions.create_team.name",
+ "authentication.permissions.create_team.description",
+ }
PERMISSION_MANAGE_TEAM = &Permission{
"manage_team",
"authentication.permissions.manage_team.name",
@@ -238,6 +275,11 @@ func InitalizePermissions() {
"authentication.permissions.import_team.name",
"authentication.permissions.import_team.description",
}
+ PERMISSION_VIEW_TEAM = &Permission{
+ "view_team",
+ "authentication.permissions.view_team.name",
+ "authentication.permissions.view_team.description",
+ }
}
func InitalizeRoles() {
@@ -264,7 +306,9 @@ func InitalizeRoles() {
"channel_admin",
"authentication.roles.channel_admin.name",
"authentication.roles.channel_admin.description",
- []string{},
+ []string{
+ PERMISSION_MANAGE_CHANNEL_ROLES.Id,
+ },
}
BuiltInRoles[ROLE_CHANNEL_ADMIN.Id] = ROLE_CHANNEL_ADMIN
ROLE_CHANNEL_GUEST = &Role{
@@ -282,6 +326,7 @@ func InitalizeRoles() {
[]string{
PERMISSION_LIST_TEAM_CHANNELS.Id,
PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
+ PERMISSION_VIEW_TEAM.Id,
},
}
BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER
@@ -295,7 +340,8 @@ func InitalizeRoles() {
PERMISSION_REMOVE_USER_FROM_TEAM.Id,
PERMISSION_MANAGE_TEAM.Id,
PERMISSION_IMPORT_TEAM.Id,
- PERMISSION_MANAGE_ROLES.Id,
+ PERMISSION_MANAGE_TEAM_ROLES.Id,
+ PERMISSION_MANAGE_CHANNEL_ROLES.Id,
PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id,
PERMISSION_MANAGE_SLASH_COMMANDS.Id,
PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id,
@@ -310,6 +356,7 @@ func InitalizeRoles() {
"authentication.roles.global_user.description",
[]string{
PERMISSION_CREATE_DIRECT_CHANNEL.Id,
+ PERMISSION_CREATE_GROUP_CHANNEL.Id,
PERMISSION_PERMANENT_DELETE_USER.Id,
PERMISSION_MANAGE_OAUTH.Id,
},
@@ -329,6 +376,7 @@ func InitalizeRoles() {
[]string{
PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id,
PERMISSION_MANAGE_SYSTEM.Id,
+ PERMISSION_MANAGE_ROLES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id,
PERMISSION_DELETE_PUBLIC_CHANNEL.Id,
PERMISSION_CREATE_PUBLIC_CHANNEL.Id,
@@ -340,6 +388,9 @@ func InitalizeRoles() {
PERMISSION_EDIT_OTHER_USERS.Id,
PERMISSION_MANAGE_OAUTH.Id,
PERMISSION_INVITE_USER.Id,
+ PERMISSION_DELETE_POST.Id,
+ PERMISSION_DELETE_OTHERS_POSTS.Id,
+ PERMISSION_CREATE_TEAM.Id,
},
ROLE_TEAM_USER.Permissions...,
),
diff --git a/vendor/github.com/mattermost/platform/model/channel.go b/vendor/github.com/mattermost/platform/model/channel.go
index 2ad257cc..d24fdb2b 100644
--- a/vendor/github.com/mattermost/platform/model/channel.go
+++ b/vendor/github.com/mattermost/platform/model/channel.go
@@ -4,8 +4,12 @@
package model
import (
+ "crypto/sha1"
+ "encoding/hex"
"encoding/json"
"io"
+ "sort"
+ "strings"
"unicode/utf8"
)
@@ -13,11 +17,16 @@ const (
CHANNEL_OPEN = "O"
CHANNEL_PRIVATE = "P"
CHANNEL_DIRECT = "D"
+ CHANNEL_GROUP = "G"
+ CHANNEL_GROUP_MAX_USERS = 8
+ CHANNEL_GROUP_MIN_USERS = 3
DEFAULT_CHANNEL = "town-square"
CHANNEL_DISPLAY_NAME_MAX_RUNES = 64
+ CHANNEL_NAME_MIN_LENGTH = 2
CHANNEL_NAME_MAX_LENGTH = 64
CHANNEL_HEADER_MAX_RUNES = 1024
CHANNEL_PURPOSE_MAX_RUNES = 250
+ CHANNEL_CACHE_SIZE = 25000
)
type Channel struct {
@@ -83,15 +92,11 @@ func (o *Channel) IsValid() *AppError {
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id)
}
- if len(o.Name) > CHANNEL_NAME_MAX_LENGTH {
- return NewLocAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "id="+o.Id)
- }
-
if !IsValidChannelIdentifier(o.Name) {
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+o.Id)
}
- if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT) {
+ if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP) {
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id)
}
@@ -128,6 +133,10 @@ func (o *Channel) ExtraUpdated() {
o.ExtraUpdateAt = GetMillis()
}
+func (o *Channel) IsGroupOrDirect() bool {
+ return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP
+}
+
func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 {
return userId2 + "__" + userId1
@@ -135,3 +144,31 @@ func GetDMNameFromIds(userId1, userId2 string) string {
return userId1 + "__" + userId2
}
}
+
+func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string {
+ usernames := make([]string, len(users))
+ for index, user := range users {
+ usernames[index] = user.Username
+ }
+
+ sort.Strings(usernames)
+
+ name := strings.Join(usernames, ", ")
+
+ if truncate && len(name) > CHANNEL_NAME_MAX_LENGTH {
+ name = name[:CHANNEL_NAME_MAX_LENGTH]
+ }
+
+ return name
+}
+
+func GetGroupNameFromUserIds(userIds []string) string {
+ sort.Strings(userIds)
+
+ h := sha1.New()
+ for _, id := range userIds {
+ io.WriteString(h, id)
+ }
+
+ return hex.EncodeToString(h.Sum(nil))
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel_member.go b/vendor/github.com/mattermost/platform/model/channel_member.go
index a607a505..5de58bc4 100644
--- a/vendor/github.com/mattermost/platform/model/channel_member.go
+++ b/vendor/github.com/mattermost/platform/model/channel_member.go
@@ -88,18 +88,32 @@ func (o *ChannelMember) IsValid() *AppError {
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "")
}
- notifyLevel := o.NotifyProps["desktop"]
+ notifyLevel := o.NotifyProps[DESKTOP_NOTIFY_PROP]
if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) {
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error",
nil, "notify_level="+notifyLevel)
}
- markUnreadLevel := o.NotifyProps["mark_unread"]
+ markUnreadLevel := o.NotifyProps[MARK_UNREAD_NOTIFY_PROP]
if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) {
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error",
nil, "mark_unread_level="+markUnreadLevel)
}
+ if pushLevel, ok := o.NotifyProps[PUSH_NOTIFY_PROP]; ok {
+ if len(pushLevel) > 20 || !IsChannelNotifyLevelValid(pushLevel) {
+ return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.push_level.app_error",
+ nil, "push_notification_level="+pushLevel)
+ }
+ }
+
+ if sendEmail, ok := o.NotifyProps[EMAIL_NOTIFY_PROP]; ok {
+ if len(sendEmail) > 20 || !IsSendEmailValid(sendEmail) {
+ return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.email_value.app_error",
+ nil, "push_notification_level="+sendEmail)
+ }
+ }
+
return nil
}
@@ -126,9 +140,15 @@ func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool {
return markUnreadLevel == CHANNEL_MARK_UNREAD_ALL || markUnreadLevel == CHANNEL_MARK_UNREAD_MENTION
}
+func IsSendEmailValid(sendEmail string) bool {
+ return sendEmail == CHANNEL_NOTIFY_DEFAULT || sendEmail == "true" || sendEmail == "false"
+}
+
func GetDefaultChannelNotifyProps() StringMap {
return StringMap{
- "desktop": CHANNEL_NOTIFY_DEFAULT,
- "mark_unread": CHANNEL_MARK_UNREAD_ALL,
+ DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
+ MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL,
+ PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
+ EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
}
}
diff --git a/vendor/github.com/mattermost/platform/model/client.go b/vendor/github.com/mattermost/platform/model/client.go
index 540bc747..24ee2c2b 100644
--- a/vendor/github.com/mattermost/platform/model/client.go
+++ b/vendor/github.com/mattermost/platform/model/client.go
@@ -35,12 +35,14 @@ const (
STATUS = "status"
STATUS_OK = "OK"
STATUS_FAIL = "FAIL"
+ STATUS_REMOVE = "REMOVE"
CLIENT_DIR = "webapp/dist"
API_URL_SUFFIX_V1 = "/api/v1"
API_URL_SUFFIX_V3 = "/api/v3"
- API_URL_SUFFIX = API_URL_SUFFIX_V3
+ API_URL_SUFFIX_V4 = "/api/v4"
+ API_URL_SUFFIX = API_URL_SUFFIX_V4
)
type Result struct {
@@ -71,7 +73,7 @@ type Client struct {
// 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_V3, &http.Client{}, "", "", "", "", "", ""}
}
func closeBody(r *http.Response) {
@@ -782,7 +784,7 @@ func (c *Client) GetSessions(id string) (*Result, *AppError) {
}
func (c *Client) EmailToOAuth(m map[string]string) (*Result, *AppError) {
- if r, err := c.DoApiPost("/users/claim/email_to_sso", MapToJson(m)); err != nil {
+ if r, err := c.DoApiPost("/users/claim/email_to_oauth", MapToJson(m)); err != nil {
return nil, err
} else {
defer closeBody(r)
@@ -1119,6 +1121,16 @@ func (c *Client) CreateDirectChannel(userId string) (*Result, *AppError) {
}
}
+func (c *Client) CreateGroupChannel(userIds []string) (*Result, *AppError) {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_group", ArrayToJson(userIds)); 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
+ }
+}
+
func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update", channel.ToJson()); err != nil {
return nil, err
@@ -1471,6 +1483,21 @@ func (c *Client) GetPostById(postId string, etag string) (*PostList, *ResponseMe
}
}
+// GetPermalink returns a post list, based on the provided channel and post ID.
+func (c *Client) GetPermalink(channelId string, postId string, etag string) (*PostList, *ResponseMetadata) {
+ if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/pltmp/%v", postId), "", etag); err != nil {
+ return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostListFromJson(r.Body),
+ &ResponseMetadata{
+ StatusCode: r.StatusCode,
+ RequestId: r.Header.Get(HEADER_REQUEST_ID),
+ Etag: r.Header.Get(HEADER_ETAG_SERVER),
+ }
+ }
+}
+
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
@@ -1991,6 +2018,16 @@ func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppErro
}
}
+func (c *Client) UpdateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/update", 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
+ }
+}
+
func (c *Client) PostToWebhook(id, payload string) (*Result, *AppError) {
if r, err := c.DoPost("/hooks/"+id, payload, "application/x-www-form-urlencoded"); err != nil {
return nil, err
@@ -2082,6 +2119,16 @@ func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppErro
}
}
+func (c *Client) UpdateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) {
+ if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/update", 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
+ }
+}
+
func (c *Client) DeleteOutgoingWebhook(id string) (*Result, *AppError) {
data := make(map[string]string)
data["id"] = id
@@ -2319,3 +2366,26 @@ func (c *Client) ListReactions(channelId string, postId string) ([]*Reaction, *A
return ReactionsFromJson(r.Body), nil
}
}
+
+// Updates the user's roles in the channel by replacing them with the roles provided.
+func (c *Client) UpdateChannelRoles(channelId string, userId string, roles string) (map[string]string, *ResponseMetadata) {
+ data := make(map[string]string)
+ data["new_roles"] = roles
+ data["user_id"] = userId
+
+ if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_member_roles", MapToJson(data)); err != nil {
+ metadata := ResponseMetadata{Error: err}
+ if r != nil {
+ metadata.StatusCode = r.StatusCode
+ }
+ return nil, &metadata
+ } else {
+ defer closeBody(r)
+ return MapFromJson(r.Body),
+ &ResponseMetadata{
+ StatusCode: r.StatusCode,
+ RequestId: r.Header.Get(HEADER_REQUEST_ID),
+ Etag: r.Header.Get(HEADER_ETAG_SERVER),
+ }
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/client4.go b/vendor/github.com/mattermost/platform/model/client4.go
new file mode 100644
index 00000000..d3bb6534
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/client4.go
@@ -0,0 +1,1006 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+type Response struct {
+ StatusCode int
+ Error *AppError
+ RequestId string
+ Etag string
+ ServerVersion string
+}
+
+type Client4 struct {
+ Url string // The location of the server, for example "http://localhost:8065"
+ ApiUrl string // The api location of the server, for example "http://localhost:8065/api/v4"
+ HttpClient *http.Client // The http client
+ AuthToken string
+ AuthType string
+}
+
+func NewAPIv4Client(url string) *Client4 {
+ return &Client4{url, url + API_URL_SUFFIX, &http.Client{}, "", ""}
+}
+
+func BuildResponse(r *http.Response) *Response {
+ return &Response{
+ StatusCode: r.StatusCode,
+ RequestId: r.Header.Get(HEADER_REQUEST_ID),
+ Etag: r.Header.Get(HEADER_ETAG_SERVER),
+ ServerVersion: r.Header.Get(HEADER_VERSION_ID),
+ }
+}
+
+func (c *Client4) SetOAuthToken(token string) {
+ c.AuthToken = token
+ c.AuthType = HEADER_TOKEN
+}
+
+func (c *Client4) ClearOAuthToken() {
+ c.AuthToken = ""
+ c.AuthType = HEADER_BEARER
+}
+
+func (c *Client4) GetUsersRoute() string {
+ return fmt.Sprintf("/users")
+}
+
+func (c *Client4) GetUserRoute(userId string) string {
+ return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId)
+}
+
+func (c *Client4) GetUserByUsernameRoute(userName string) string {
+ return fmt.Sprintf(c.GetUsersRoute()+"/username/%v", userName)
+}
+
+func (c *Client4) GetUserByEmailRoute(email string) string {
+ return fmt.Sprintf(c.GetUsersRoute()+"/email/%v", email)
+}
+
+func (c *Client4) GetTeamsRoute() string {
+ return fmt.Sprintf("/teams")
+}
+
+func (c *Client4) GetTeamRoute(teamId string) string {
+ return fmt.Sprintf(c.GetTeamsRoute()+"/%v", teamId)
+}
+
+func (c *Client4) GetTeamByNameRoute(teamName string) string {
+ return fmt.Sprintf(c.GetTeamsRoute()+"/name/%v", teamName)
+}
+
+func (c *Client4) GetTeamMemberRoute(teamId, userId string) string {
+ return fmt.Sprintf(c.GetTeamRoute(teamId)+"/members/%v", userId)
+}
+
+func (c *Client4) GetTeamMembersRoute(teamId string) string {
+ return fmt.Sprintf(c.GetTeamRoute(teamId) + "/members")
+}
+
+func (c *Client4) GetTeamStatsRoute(teamId string) string {
+ return fmt.Sprintf(c.GetTeamRoute(teamId) + "/stats")
+}
+
+func (c *Client4) GetChannelsRoute() string {
+ return fmt.Sprintf("/channels")
+}
+
+func (c *Client4) GetChannelRoute(channelId string) string {
+ return fmt.Sprintf(c.GetChannelsRoute()+"/%v", channelId)
+}
+
+func (c *Client4) GetChannelByNameRoute(channelName, teamId string) string {
+ return fmt.Sprintf(c.GetTeamRoute(teamId)+"/channels/name/%v", channelName)
+}
+
+func (c *Client4) GetChannelByNameForTeamNameRoute(channelName, teamName string) string {
+ return fmt.Sprintf(c.GetTeamByNameRoute(teamName)+"/channels/name/%v", channelName)
+}
+
+func (c *Client4) GetChannelMembersRoute(channelId string) string {
+ return fmt.Sprintf(c.GetChannelRoute(channelId) + "/members")
+}
+
+func (c *Client4) GetChannelMemberRoute(channelId, userId string) string {
+ return fmt.Sprintf(c.GetChannelMembersRoute(channelId)+"/%v", userId)
+}
+
+func (c *Client4) GetPostsRoute() string {
+ return fmt.Sprintf("/posts")
+}
+
+func (c *Client4) GetPostRoute(postId string) string {
+ return fmt.Sprintf(c.GetPostsRoute()+"/%v", postId)
+}
+
+func (c *Client4) GetFilesRoute() string {
+ return fmt.Sprintf("/files")
+}
+
+func (c *Client4) GetFileRoute(fileId string) string {
+ return fmt.Sprintf(c.GetFilesRoute()+"/%v", fileId)
+}
+
+func (c *Client4) GetSystemRoute() string {
+ return fmt.Sprintf("/system")
+}
+
+func (c *Client4) GetIncomingWebhooksRoute() string {
+ return fmt.Sprintf("/hooks/incoming")
+}
+
+func (c *Client4) GetPreferencesRoute(userId string) string {
+ return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences")
+}
+
+func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) {
+ return c.DoApiRequest(http.MethodGet, url, "", etag)
+}
+
+func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) {
+ return c.DoApiRequest(http.MethodPost, url, data, "")
+}
+
+func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) {
+ return c.DoApiRequest(http.MethodPut, url, data, "")
+}
+
+func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) {
+ return c.DoApiRequest(http.MethodDelete, url, "", "")
+}
+
+func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) {
+ rq, _ := http.NewRequest(method, c.ApiUrl+url, strings.NewReader(data))
+ rq.Close = true
+
+ if len(etag) > 0 {
+ rq.Header.Set(HEADER_ETAG_CLIENT, etag)
+ }
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
+ }
+
+ 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 == 304 {
+ return rp, nil
+ } else if rp.StatusCode >= 300 {
+ defer closeBody(rp)
+ return rp, AppErrorFromJson(rp.Body)
+ } else {
+ return rp, nil
+ }
+}
+
+func (c *Client4) DoUploadFile(url string, data []byte, contentType string) (*FileUploadResponse, *Response) {
+ rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data))
+ rq.Header.Set("Content-Type", contentType)
+ rq.Close = true
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
+ }
+
+ if rp, err := c.HttpClient.Do(rq); err != nil {
+ return nil, &Response{Error: NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)}
+ } else if rp.StatusCode >= 300 {
+ return nil, &Response{StatusCode: rp.StatusCode, Error: AppErrorFromJson(rp.Body)}
+ } else {
+ defer closeBody(rp)
+ return FileUploadResponseFromJson(rp.Body), BuildResponse(rp)
+ }
+}
+
+// CheckStatusOK is a convenience function for checking the standard OK response
+// from the web service.
+func CheckStatusOK(r *http.Response) bool {
+ m := MapFromJson(r.Body)
+ defer closeBody(r)
+
+ if m != nil && m[STATUS] == STATUS_OK {
+ return true
+ }
+
+ return false
+}
+
+// Authentication Section
+
+// LoginById authenticates a user by user id and password.
+func (c *Client4) LoginById(id string, password string) (*User, *Response) {
+ m := make(map[string]string)
+ m["id"] = id
+ m["password"] = password
+ return c.login(m)
+}
+
+// Login authenticates a user by login id, which can be username, email or some sort
+// of SSO identifier based on server configuration, and a password.
+func (c *Client4) Login(loginId string, password string) (*User, *Response) {
+ m := make(map[string]string)
+ m["login_id"] = loginId
+ m["password"] = password
+ return c.login(m)
+}
+
+// LoginByLdap authenticates a user by LDAP id and password.
+func (c *Client4) LoginByLdap(loginId string, password string) (*User, *Response) {
+ m := make(map[string]string)
+ m["login_id"] = loginId
+ m["password"] = password
+ m["ldap_only"] = "true"
+ return c.login(m)
+}
+
+// LoginWithDevice authenticates a user by login id (username, email or some sort
+// of SSO identifier based on configuration), password and attaches a device id to
+// the session.
+func (c *Client4) LoginWithDevice(loginId string, password string, deviceId string) (*User, *Response) {
+ m := make(map[string]string)
+ m["login_id"] = loginId
+ m["password"] = password
+ m["device_id"] = deviceId
+ return c.login(m)
+}
+
+func (c *Client4) login(m map[string]string) (*User, *Response) {
+ if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ c.AuthToken = r.Header.Get(HEADER_TOKEN)
+ c.AuthType = HEADER_BEARER
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// Logout terminates the current user's session.
+func (c *Client4) Logout() (bool, *Response) {
+ if r, err := c.DoApiPost("/users/logout", ""); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ c.AuthToken = ""
+ c.AuthType = HEADER_BEARER
+
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// User Section
+
+// CreateUser creates a user in the system based on the provided user struct.
+func (c *Client4) CreateUser(user *User) (*User, *Response) {
+ if r, err := c.DoApiPost(c.GetUsersRoute(), user.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUser returns a user based on the provided user id string.
+func (c *Client4) GetUser(userId, etag string) (*User, *Response) {
+ if r, err := c.DoApiGet(c.GetUserRoute(userId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUserByUsername returns a user based on the provided user name string.
+func (c *Client4) GetUserByUsername(userName, etag string) (*User, *Response) {
+ if r, err := c.DoApiGet(c.GetUserByUsernameRoute(userName), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUserByEmail returns a user based on the provided user email string.
+func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response) {
+ if r, err := c.DoApiGet(c.GetUserByEmailRoute(email), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetProfileImage gets user's profile image. Must be logged in or be a system administrator.
+func (c *Client4) GetProfileImage(userId, etag string) ([]byte, *Response) {
+ if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/image", etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else if data, err := ioutil.ReadAll(r.Body); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: NewAppError("GetProfileImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)}
+ } else {
+ return data, BuildResponse(r)
+ }
+}
+
+// GetUsers returns a page of users on the system. Page counting starts at 0.
+func (c *Client4) GetUsers(page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUsersInTeam returns a page of users on a team. Page counting starts at 0.
+func (c *Client4) GetUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUsersInChannel returns a page of users on a team. Page counting starts at 0.
+func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage)
+ if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUsersNotInChannel returns a page of users on a team. Page counting starts at 0.
+func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPage int, etag string) ([]*User, *Response) {
+ query := fmt.Sprintf("?in_team=%v¬_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage)
+ if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetUsersByIds returns a list of users based on the provided user ids.
+func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response) {
+ if r, err := c.DoApiPost(c.GetUsersRoute()+"/ids", ArrayToJson(userIds)); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// UpdateUser updates a user in the system based on the provided user struct.
+func (c *Client4) UpdateUser(user *User) (*User, *Response) {
+ if r, err := c.DoApiPut(c.GetUserRoute(user.Id), user.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// PatchUser partially updates a user in the system. Any missing fields are not updated.
+func (c *Client4) PatchUser(userId string, patch *UserPatch) (*User, *Response) {
+ if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/patch", patch.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return UserFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// UpdateUserPassword updates a user's password. Must be logged in as the user or be a system administrator.
+func (c *Client4) UpdateUserPassword(userId, currentPassword, newPassword string) (bool, *Response) {
+ requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword}
+ if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/password", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// UpdateUserRoles updates a user's roles in the system. A user can have "system_user" and "system_admin" roles.
+func (c *Client4) UpdateUserRoles(userId, roles string) (bool, *Response) {
+ requestBody := map[string]string{"roles": roles}
+ if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/roles", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// DeleteUser deactivates a user in the system based on the provided user id string.
+func (c *Client4) DeleteUser(userId string) (bool, *Response) {
+ if r, err := c.DoApiDelete(c.GetUserRoute(userId)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// SendPasswordResetEmail will send a link for password resetting to a user with the
+// provided email.
+func (c *Client4) SendPasswordResetEmail(email string) (bool, *Response) {
+ requestBody := map[string]string{"email": email}
+ if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset/send", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// ResetPassword uses a recovery code to update reset a user's password.
+func (c *Client4) ResetPassword(code, newPassword string) (bool, *Response) {
+ requestBody := map[string]string{"code": code, "new_password": newPassword}
+ if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// GetSessions returns a list of sessions based on the provided user id string.
+func (c *Client4) GetSessions(userId, etag string) ([]*Session, *Response) {
+ if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/sessions", etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return SessionsFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// RevokeSession revokes a user session based on the provided user id and session id strings.
+func (c *Client4) RevokeSession(userId, sessionId string) (bool, *Response) {
+ requestBody := map[string]string{"session_id": sessionId}
+ if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/sessions/revoke", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// getTeamsUnreadForUser will return an array with TeamUnread objects that contain the amount of
+// unread messages and mentions the current user has for the teams it belongs to.
+// An optional team ID can be set to exclude that team from the results. Must be authenticated.
+func (c *Client4) GetTeamsUnreadForUser(userId, teamIdToExclude string) ([]*TeamUnread, *Response) {
+ optional := ""
+ if teamIdToExclude != "" {
+ optional += fmt.Sprintf("?exclude_team=%s", url.QueryEscape(teamIdToExclude))
+ }
+
+ if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/unread"+optional, ""); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamsUnreadFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetAudits returns a list of audit based on the provided user id string.
+func (c *Client4) GetAudits(userId string, page int, perPage int, etag string) (Audits, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/audits"+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return AuditsFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// Verify user email user id and hash strings.
+func (c *Client4) VerifyUserEmail(userId, hashId string) (bool, *Response) {
+ requestBody := map[string]string{"uid": userId, "hid": hashId}
+ if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/email/verify", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// SetProfileImage sets profile image of the user
+func (c *Client4) SetProfileImage(userId string, data []byte) (bool, *Response) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ if part, err := writer.CreateFormFile("image", "profile.png"); err != nil {
+ return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
+ } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
+ }
+
+ if err := writer.Close(); err != nil {
+ return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)}
+ }
+
+ rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetUserRoute(userId)+"/image", bytes.NewReader(body.Bytes()))
+ rq.Header.Set("Content-Type", writer.FormDataContentType())
+ rq.Close = true
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
+ }
+
+ if rp, err := c.HttpClient.Do(rq); err != nil {
+ // set to http.StatusForbidden(403)
+ return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetUserRoute(userId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)}
+ } else if rp.StatusCode >= 300 {
+ return false, &Response{StatusCode: rp.StatusCode, Error: AppErrorFromJson(rp.Body)}
+ } else {
+ defer closeBody(rp)
+ return CheckStatusOK(rp), BuildResponse(rp)
+ }
+}
+
+// Team Section
+
+// CreateTeam creates a team in the system based on the provided team struct.
+func (c *Client4) CreateTeam(team *Team) (*Team, *Response) {
+ if r, err := c.DoApiPost(c.GetTeamsRoute(), team.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetTeam returns a team based on the provided team id string.
+func (c *Client4) GetTeam(teamId, etag string) (*Team, *Response) {
+ if r, err := c.DoApiGet(c.GetTeamRoute(teamId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetAllTeams returns all teams based on permissions.
+func (c *Client4) GetAllTeams(etag string, page int, perPage int) ([]*Team, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetTeamsRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetTeamByName returns a team based on the provided team name string.
+func (c *Client4) GetTeamByName(name, etag string) (*Team, *Response) {
+ if r, err := c.DoApiGet(c.GetTeamByNameRoute(name), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetTeamsForUser returns a list of teams a user is on. Must be logged in as the user
+// or be a system administrator.
+func (c *Client4) GetTeamsForUser(userId, etag string) ([]*Team, *Response) {
+ if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams", etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetTeamMember returns a team member based on the provided team and user id strings.
+func (c *Client4) GetTeamMember(teamId, userId, etag string) (*TeamMember, *Response) {
+ if r, err := c.DoApiGet(c.GetTeamMemberRoute(teamId, userId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamMemberFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// UpdateTeamMemberRoles will update the roles on a team for a user
+func (c *Client4) UpdateTeamMemberRoles(teamId, userId, newRoles string) (bool, *Response) {
+ requestBody := map[string]string{"roles": newRoles}
+ if r, err := c.DoApiPut(c.GetTeamMemberRoute(teamId, userId)+"/roles", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// GetTeamMembers returns team members based on the provided team id string.
+func (c *Client4) GetTeamMembers(teamId string, page int, perPage int, etag string) ([]*TeamMember, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetTeamMembersRoute(teamId)+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamMembersFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetTeamStats returns a team stats based on the team id string.
+// Must be authenticated.
+func (c *Client4) GetTeamStats(teamId, etag string) (*TeamStats, *Response) {
+ if r, err := c.DoApiGet(c.GetTeamStatsRoute(teamId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return TeamStatsFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// Channel Section
+
+// CreateChannel creates a channel based on the provided channel struct.
+func (c *Client4) CreateChannel(channel *Channel) (*Channel, *Response) {
+ if r, err := c.DoApiPost(c.GetChannelsRoute(), channel.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// CreateDirectChannel creates a direct message channel based on the two user
+// ids provided.
+func (c *Client4) CreateDirectChannel(userId1, userId2 string) (*Channel, *Response) {
+ requestBody := []string{userId1, userId2}
+ if r, err := c.DoApiPost(c.GetChannelsRoute()+"/direct", ArrayToJson(requestBody)); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetChannel returns a channel based on the provided channel id string.
+func (c *Client4) GetChannel(channelId, etag string) (*Channel, *Response) {
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetChannelByName returns a channel based on the provided channel name and team id strings.
+func (c *Client4) GetChannelByName(channelName, teamId string, etag string) (*Channel, *Response) {
+ if r, err := c.DoApiGet(c.GetChannelByNameRoute(channelName, teamId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetChannelByNameForTeamName returns a channel based on the provided channel name and team name strings.
+func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag string) (*Channel, *Response) {
+ if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetChannelMembers gets a page of channel members.
+func (c *Client4) GetChannelMembers(channelId string, page, perPage int, etag string) (*ChannelMembers, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetChannelMembersRoute(channelId)+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelMembersFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetChannelMember gets a channel member.
+func (c *Client4) GetChannelMember(channelId, userId, etag string) (*ChannelMember, *Response) {
+ if r, err := c.DoApiGet(c.GetChannelMemberRoute(channelId, userId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelMemberFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetChannelMembersForUser gets all the channel members for a user on a team.
+func (c *Client4) GetChannelMembersForUser(userId, teamId, etag string) (*ChannelMembers, *Response) {
+ if r, err := c.DoApiGet(fmt.Sprintf(c.GetUserRoute(userId)+"/teams/%v/channels/members", teamId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return ChannelMembersFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// ViewChannel performs a view action for a user. Synonymous with switching channels or marking channels as read by a user.
+func (c *Client4) ViewChannel(userId string, view *ChannelView) (bool, *Response) {
+ url := fmt.Sprintf(c.GetChannelsRoute()+"/members/%v/view", userId)
+ if r, err := c.DoApiPost(url, view.ToJson()); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// UpdateChannelRoles will update the roles on a channel for a user.
+func (c *Client4) UpdateChannelRoles(channelId, userId, roles string) (bool, *Response) {
+ requestBody := map[string]string{"roles": roles}
+ if r, err := c.DoApiPut(c.GetChannelMemberRoute(channelId, userId)+"/roles", MapToJson(requestBody)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// RemoveUserFromChannel will delete the channel member object for a user, effectively removing the user from a channel.
+func (c *Client4) RemoveUserFromChannel(channelId, userId string) (bool, *Response) {
+ if r, err := c.DoApiDelete(c.GetChannelMemberRoute(channelId, userId)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// Post Section
+
+// CreatePost creates a post based on the provided post struct.
+func (c *Client4) CreatePost(post *Post) (*Post, *Response) {
+ if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// UpdatePost updates a post based on the provided post struct.
+func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response) {
+ if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetPost gets a single post.
+func (c *Client4) GetPost(postId string, etag string) (*Post, *Response) {
+ if r, err := c.DoApiGet(c.GetPostRoute(postId), etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// DeletePost deletes a post from the provided post id string.
+func (c *Client4) DeletePost(postId string) (bool, *Response) {
+ if r, err := c.DoApiDelete(c.GetPostRoute(postId)); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// GetPostThread gets a post with all the other posts in the same thread.
+func (c *Client4) GetPostThread(postId string, etag string) (*PostList, *Response) {
+ if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/thread", etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetPostsForChannel gets a page of posts with an array for ordering for a channel.
+func (c *Client4) GetPostsForChannel(channelId string, page, perPage int, etag string) (*PostList, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// SearchPosts returns any posts with matching terms string.
+func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*PostList, *Response) {
+ requestBody := map[string]string{"terms": terms, "is_or_search": strconv.FormatBool(isOrSearch)}
+ if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", MapToJson(requestBody)); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// File Section
+
+// UploadFile will upload a file to a channel, to be later attached to a post.
+func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ if part, err := writer.CreateFormFile("files", filename); err != nil {
+ return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)}
+ } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)}
+ }
+
+ if part, err := writer.CreateFormField("channel_id"); err != nil {
+ return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), http.StatusBadRequest)}
+ } else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil {
+ return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), http.StatusBadRequest)}
+ }
+
+ if err := writer.Close(); err != nil {
+ return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), http.StatusBadRequest)}
+ }
+
+ return c.DoUploadFile(c.GetFilesRoute(), body.Bytes(), writer.FormDataContentType())
+}
+
+// GetFile gets the bytes for a file by id.
+func (c *Client4) GetFile(fileId string) ([]byte, *Response) {
+ if r, err := c.DoApiGet(c.GetFileRoute(fileId), ""); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else if data, err := ioutil.ReadAll(r.Body); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: NewAppError("GetFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)}
+ } else {
+ return data, BuildResponse(r)
+ }
+}
+
+// GetFileThumbnail gets the bytes for a file by id.
+func (c *Client4) GetFileThumbnail(fileId string) ([]byte, *Response) {
+ if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/thumbnail", ""); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else if data, err := ioutil.ReadAll(r.Body); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: NewAppError("GetFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)}
+ } else {
+ return data, BuildResponse(r)
+ }
+}
+
+// GetFileInfosForPost gets all the file info objects attached to a post.
+func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo, *Response) {
+ if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/files/info", etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return FileInfosFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// General Section
+
+// GetPing will ping the server and to see if it is up and running.
+func (c *Client4) GetPing() (bool, *Response) {
+ if r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", ""); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
+// Webhooks Section
+
+// CreateIncomingWebhook creates an incoming webhook for a channel.
+func (c *Client4) CreateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response) {
+ if r, err := c.DoApiPost(c.GetIncomingWebhooksRoute(), hook.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return IncomingWebhookFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetIncomingWebhooks returns a page of incoming webhooks on the system. Page counting starts at 0.
+func (c *Client4) GetIncomingWebhooks(page int, perPage int, etag string) ([]*IncomingWebhook, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if r, err := c.DoApiGet(c.GetIncomingWebhooksRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return IncomingWebhookListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// GetIncomingWebhooksForTeam returns a page of incoming webhooks for a team. Page counting starts at 0.
+func (c *Client4) GetIncomingWebhooksForTeam(teamId string, page int, perPage int, etag string) ([]*IncomingWebhook, *Response) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId)
+ if r, err := c.DoApiGet(c.GetIncomingWebhooksRoute()+query, etag); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return IncomingWebhookListFromJson(r.Body), BuildResponse(r)
+ }
+}
+
+// Preferences Section
+
+// GetPreferences returns the user's preferences
+func (c *Client4) GetPreferences(userId string) (Preferences, *Response) {
+ if r, err := c.DoApiGet(c.GetPreferencesRoute(userId), ""); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ preferences, _ := PreferencesFromJson(r.Body)
+ defer closeBody(r)
+ return preferences, BuildResponse(r)
+ }
+}
+
+// UpdatePreferences saves the user's preferences
+func (c *Client4) UpdatePreferences(userId string, preferences *Preferences) (bool, *Response) {
+ if r, err := c.DoApiPut(c.GetPreferencesRoute(userId), preferences.ToJson()); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return true, BuildResponse(r)
+ }
+}
+
+// DeletePreferences deletes the user's preferences
+func (c *Client4) DeletePreferences(userId string, preferences *Preferences) (bool, *Response) {
+ if r, err := c.DoApiPost(c.GetPreferencesRoute(userId)+"/delete", preferences.ToJson()); err != nil {
+ return false, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return true, BuildResponse(r)
+ }
+}
+
+// GetPreferencesByCategory returns the user's preferences from the provided category string
+func (c *Client4) GetPreferencesByCategory(userId string, category string) (Preferences, *Response) {
+ url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s", category)
+ if r, err := c.DoApiGet(url, ""); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ preferences, _ := PreferencesFromJson(r.Body)
+ defer closeBody(r)
+ return preferences, BuildResponse(r)
+ }
+}
+
+// GetPreferenceByCategoryAndName returns the user's preferences from the provided category and preference name string
+func (c *Client4) GetPreferenceByCategoryAndName(userId string, category string, preferenceName string) (*Preference, *Response) {
+ url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s/name/%v", category, preferenceName)
+ if r, err := c.DoApiGet(url, ""); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PreferenceFromJson(r.Body), BuildResponse(r)
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/command_response.go b/vendor/github.com/mattermost/platform/model/command_response.go
index bbb70418..f6977235 100644
--- a/vendor/github.com/mattermost/platform/model/command_response.go
+++ b/vendor/github.com/mattermost/platform/model/command_response.go
@@ -5,6 +5,7 @@ package model
import (
"encoding/json"
+ "fmt"
"io"
)
@@ -14,12 +15,12 @@ const (
)
type CommandResponse struct {
- ResponseType string `json:"response_type"`
- Text string `json:"text"`
- Username string `json:"username"`
- IconURL string `json:"icon_url"`
- GotoLocation string `json:"goto_location"`
- Attachments interface{} `json:"attachments"`
+ ResponseType string `json:"response_type"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ GotoLocation string `json:"goto_location"`
+ Attachments []*SlackAttachment `json:"attachments"`
}
func (o *CommandResponse) ToJson() string {
@@ -34,10 +35,19 @@ func (o *CommandResponse) ToJson() string {
func CommandResponseFromJson(data io.Reader) *CommandResponse {
decoder := json.NewDecoder(data)
var o CommandResponse
- err := decoder.Decode(&o)
- if err == nil {
- return &o
- } else {
+
+ if err := decoder.Decode(&o); err != nil {
return nil
}
+
+ // Ensure attachment fields are stored as strings
+ for _, attachment := range o.Attachments {
+ for _, field := range attachment.Fields {
+ if field.Value != nil {
+ field.Value = fmt.Sprintf("%v", field.Value)
+ }
+ }
+ }
+
+ return &o
}
diff --git a/vendor/github.com/mattermost/platform/model/config.go b/vendor/github.com/mattermost/platform/model/config.go
index 0134e1a3..86693b57 100644
--- a/vendor/github.com/mattermost/platform/model/config.go
+++ b/vendor/github.com/mattermost/platform/model/config.go
@@ -49,49 +49,109 @@ const (
RESTRICT_EMOJI_CREATION_ADMIN = "admin"
RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin"
+ PERMISSIONS_DELETE_POST_ALL = "all"
+ PERMISSIONS_DELETE_POST_TEAM_ADMIN = "team_admin"
+ PERMISSIONS_DELETE_POST_SYSTEM_ADMIN = "system_admin"
+
+ ALLOW_EDIT_POST_ALWAYS = "always"
+ ALLOW_EDIT_POST_NEVER = "never"
+ ALLOW_EDIT_POST_TIME_LIMIT = "time_limit"
+
EMAIL_BATCHING_BUFFER_SIZE = 256
EMAIL_BATCHING_INTERVAL = 30
SITENAME_MAX_LENGTH = 30
+
+ SERVICE_SETTINGS_DEFAULT_SITE_URL = ""
+ SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE = ""
+ SERVICE_SETTINGS_DEFAULT_TLS_KEY_FILE = ""
+ SERVICE_SETTINGS_DEFAULT_READ_TIMEOUT = 300
+ SERVICE_SETTINGS_DEFAULT_WRITE_TIMEOUT = 300
+ SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM = ""
+
+ TEAM_SETTINGS_DEFAULT_CUSTOM_BRAND_TEXT = ""
+ TEAM_SETTINGS_DEFAULT_CUSTOM_DESCRIPTION_TEXT = ""
+ TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT = 300
+
+ EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION = ""
+
+ SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/"
+ SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/"
+ SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/"
+ SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/"
+ SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/"
+ SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com"
+
+ LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = ""
+ LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME = ""
+
+ SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
+ SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
+ SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = ""
+ SAML_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = ""
+ SAML_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = ""
+ SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE = ""
+ SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = ""
+
+ NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://about.mattermost.com/downloads/"
+ NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-android-app/"
+ NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-ios-app/"
+
+ WEBRTC_SETTINGS_DEFAULT_STUN_URI = ""
+ WEBRTC_SETTINGS_DEFAULT_TURN_URI = ""
+
+ ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS = 2500
)
type ServiceSettings struct {
- SiteURL *string
- ListenAddress string
- ConnectionSecurity *string
- TLSCertFile *string
- TLSKeyFile *string
- UseLetsEncrypt *bool
- LetsEncryptCertificateCacheFile *string
- Forward80To443 *bool
- ReadTimeout *int
- WriteTimeout *int
- MaximumLoginAttempts int
- SegmentDeveloperKey string
- GoogleDeveloperKey string
- EnableOAuthServiceProvider bool
- EnableIncomingWebhooks bool
- EnableOutgoingWebhooks bool
- EnableCommands *bool
- EnableOnlyAdminIntegrations *bool
- EnablePostUsernameOverride bool
- EnablePostIconOverride bool
- EnableTesting bool
- EnableDeveloper *bool
- EnableSecurityFixAlert *bool
- EnableInsecureOutgoingConnections *bool
- EnableMultifactorAuthentication *bool
- EnforceMultifactorAuthentication *bool
- AllowCorsFrom *string
- SessionLengthWebInDays *int
- SessionLengthMobileInDays *int
- SessionLengthSSOInDays *int
- SessionCacheInMinutes *int
- WebsocketSecurePort *int
- WebsocketPort *int
- WebserverMode *string
- EnableCustomEmoji *bool
- RestrictCustomEmojiCreation *string
+ SiteURL *string
+ ListenAddress string
+ ConnectionSecurity *string
+ TLSCertFile *string
+ TLSKeyFile *string
+ UseLetsEncrypt *bool
+ LetsEncryptCertificateCacheFile *string
+ Forward80To443 *bool
+ ReadTimeout *int
+ WriteTimeout *int
+ MaximumLoginAttempts int
+ GoogleDeveloperKey string
+ EnableOAuthServiceProvider bool
+ EnableIncomingWebhooks bool
+ EnableOutgoingWebhooks bool
+ EnableCommands *bool
+ EnableOnlyAdminIntegrations *bool
+ EnablePostUsernameOverride bool
+ EnablePostIconOverride bool
+ EnableLinkPreviews *bool
+ EnableTesting bool
+ EnableDeveloper *bool
+ EnableSecurityFixAlert *bool
+ EnableInsecureOutgoingConnections *bool
+ EnableMultifactorAuthentication *bool
+ EnforceMultifactorAuthentication *bool
+ AllowCorsFrom *string
+ SessionLengthWebInDays *int
+ SessionLengthMobileInDays *int
+ SessionLengthSSOInDays *int
+ SessionCacheInMinutes *int
+ WebsocketSecurePort *int
+ WebsocketPort *int
+ WebserverMode *string
+ EnableCustomEmoji *bool
+ RestrictCustomEmojiCreation *string
+ RestrictPostDelete *string
+ AllowEditPost *string
+ PostEditTimeLimit *int
+ TimeBetweenUserTypingUpdatesMilliseconds *int64
+ EnableUserTypingMessages *bool
+ ClusterLogTimeoutMilliseconds *int
}
type ClusterSettings struct {
@@ -433,7 +493,12 @@ func (o *Config) SetDefaults() {
if o.ServiceSettings.SiteURL == nil {
o.ServiceSettings.SiteURL = new(string)
- *o.ServiceSettings.SiteURL = ""
+ *o.ServiceSettings.SiteURL = SERVICE_SETTINGS_DEFAULT_SITE_URL
+ }
+
+ if o.ServiceSettings.EnableLinkPreviews == nil {
+ o.ServiceSettings.EnableLinkPreviews = new(bool)
+ *o.ServiceSettings.EnableLinkPreviews = false
}
if o.ServiceSettings.EnableDeveloper == nil {
@@ -493,12 +558,12 @@ func (o *Config) SetDefaults() {
if o.TeamSettings.CustomBrandText == nil {
o.TeamSettings.CustomBrandText = new(string)
- *o.TeamSettings.CustomBrandText = ""
+ *o.TeamSettings.CustomBrandText = TEAM_SETTINGS_DEFAULT_CUSTOM_BRAND_TEXT
}
if o.TeamSettings.CustomDescriptionText == nil {
o.TeamSettings.CustomDescriptionText = new(string)
- *o.TeamSettings.CustomDescriptionText = ""
+ *o.TeamSettings.CustomDescriptionText = TEAM_SETTINGS_DEFAULT_CUSTOM_DESCRIPTION_TEXT
}
if o.TeamSettings.EnableOpenServer == nil {
@@ -552,7 +617,7 @@ func (o *Config) SetDefaults() {
if o.TeamSettings.UserStatusAwayTimeout == nil {
o.TeamSettings.UserStatusAwayTimeout = new(int64)
- *o.TeamSettings.UserStatusAwayTimeout = 300
+ *o.TeamSettings.UserStatusAwayTimeout = TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT
}
if o.TeamSettings.MaxChannelsPerTeam == nil {
@@ -597,7 +662,7 @@ func (o *Config) SetDefaults() {
if o.EmailSettings.FeedbackOrganization == nil {
o.EmailSettings.FeedbackOrganization = new(string)
- *o.EmailSettings.FeedbackOrganization = ""
+ *o.EmailSettings.FeedbackOrganization = EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION
}
if o.EmailSettings.EnableEmailBatching == nil {
@@ -621,7 +686,7 @@ func (o *Config) SetDefaults() {
if o.SupportSettings.TermsOfServiceLink == nil {
o.SupportSettings.TermsOfServiceLink = new(string)
- *o.SupportSettings.TermsOfServiceLink = "https://about.mattermost.com/default-terms/"
+ *o.SupportSettings.TermsOfServiceLink = SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK
}
if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) {
@@ -630,7 +695,7 @@ func (o *Config) SetDefaults() {
if o.SupportSettings.PrivacyPolicyLink == nil {
o.SupportSettings.PrivacyPolicyLink = new(string)
- *o.SupportSettings.PrivacyPolicyLink = ""
+ *o.SupportSettings.PrivacyPolicyLink = SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK
}
if !IsSafeLink(o.SupportSettings.AboutLink) {
@@ -639,7 +704,7 @@ func (o *Config) SetDefaults() {
if o.SupportSettings.AboutLink == nil {
o.SupportSettings.AboutLink = new(string)
- *o.SupportSettings.AboutLink = ""
+ *o.SupportSettings.AboutLink = SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK
}
if !IsSafeLink(o.SupportSettings.HelpLink) {
@@ -648,7 +713,7 @@ func (o *Config) SetDefaults() {
if o.SupportSettings.HelpLink == nil {
o.SupportSettings.HelpLink = new(string)
- *o.SupportSettings.HelpLink = ""
+ *o.SupportSettings.HelpLink = SUPPORT_SETTINGS_DEFAULT_HELP_LINK
}
if !IsSafeLink(o.SupportSettings.ReportAProblemLink) {
@@ -657,12 +722,12 @@ func (o *Config) SetDefaults() {
if o.SupportSettings.ReportAProblemLink == nil {
o.SupportSettings.ReportAProblemLink = new(string)
- *o.SupportSettings.ReportAProblemLink = ""
+ *o.SupportSettings.ReportAProblemLink = SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK
}
if o.SupportSettings.SupportEmail == nil {
o.SupportSettings.SupportEmail = new(string)
- *o.SupportSettings.SupportEmail = "feedback@mattermost.com"
+ *o.SupportSettings.SupportEmail = SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL
}
if o.LdapSettings.Enable == nil {
@@ -707,37 +772,37 @@ func (o *Config) SetDefaults() {
if o.LdapSettings.FirstNameAttribute == nil {
o.LdapSettings.FirstNameAttribute = new(string)
- *o.LdapSettings.FirstNameAttribute = ""
+ *o.LdapSettings.FirstNameAttribute = LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE
}
if o.LdapSettings.LastNameAttribute == nil {
o.LdapSettings.LastNameAttribute = new(string)
- *o.LdapSettings.LastNameAttribute = ""
+ *o.LdapSettings.LastNameAttribute = LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE
}
if o.LdapSettings.EmailAttribute == nil {
o.LdapSettings.EmailAttribute = new(string)
- *o.LdapSettings.EmailAttribute = ""
+ *o.LdapSettings.EmailAttribute = LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE
}
if o.LdapSettings.UsernameAttribute == nil {
o.LdapSettings.UsernameAttribute = new(string)
- *o.LdapSettings.UsernameAttribute = ""
+ *o.LdapSettings.UsernameAttribute = LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE
}
if o.LdapSettings.NicknameAttribute == nil {
o.LdapSettings.NicknameAttribute = new(string)
- *o.LdapSettings.NicknameAttribute = ""
+ *o.LdapSettings.NicknameAttribute = LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE
}
if o.LdapSettings.IdAttribute == nil {
o.LdapSettings.IdAttribute = new(string)
- *o.LdapSettings.IdAttribute = ""
+ *o.LdapSettings.IdAttribute = LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE
}
if o.LdapSettings.PositionAttribute == nil {
o.LdapSettings.PositionAttribute = new(string)
- *o.LdapSettings.PositionAttribute = ""
+ *o.LdapSettings.PositionAttribute = LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE
}
if o.LdapSettings.SyncIntervalMinutes == nil {
@@ -762,7 +827,7 @@ func (o *Config) SetDefaults() {
if o.LdapSettings.LoginFieldName == nil {
o.LdapSettings.LoginFieldName = new(string)
- *o.LdapSettings.LoginFieldName = ""
+ *o.LdapSettings.LoginFieldName = LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME
}
if o.ServiceSettings.SessionLengthWebInDays == nil {
@@ -807,7 +872,7 @@ func (o *Config) SetDefaults() {
if o.ServiceSettings.AllowCorsFrom == nil {
o.ServiceSettings.AllowCorsFrom = new(string)
- *o.ServiceSettings.AllowCorsFrom = ""
+ *o.ServiceSettings.AllowCorsFrom = SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM
}
if o.ServiceSettings.WebserverMode == nil {
@@ -827,6 +892,21 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL
}
+ if o.ServiceSettings.RestrictPostDelete == nil {
+ o.ServiceSettings.RestrictPostDelete = new(string)
+ *o.ServiceSettings.RestrictPostDelete = PERMISSIONS_DELETE_POST_ALL
+ }
+
+ if o.ServiceSettings.AllowEditPost == nil {
+ o.ServiceSettings.AllowEditPost = new(string)
+ *o.ServiceSettings.AllowEditPost = ALLOW_EDIT_POST_ALWAYS
+ }
+
+ if o.ServiceSettings.PostEditTimeLimit == nil {
+ o.ServiceSettings.PostEditTimeLimit = new(int)
+ *o.ServiceSettings.PostEditTimeLimit = 300
+ }
+
if o.ClusterSettings.InterNodeListenAddress == nil {
o.ClusterSettings.InterNodeListenAddress = new(string)
*o.ClusterSettings.InterNodeListenAddress = ":8075"
@@ -853,7 +933,7 @@ func (o *Config) SetDefaults() {
if o.AnalyticsSettings.MaxUsersForStatistics == nil {
o.AnalyticsSettings.MaxUsersForStatistics = new(int)
- *o.AnalyticsSettings.MaxUsersForStatistics = 2500
+ *o.AnalyticsSettings.MaxUsersForStatistics = ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS
}
if o.ComplianceSettings.Enable == nil {
@@ -943,52 +1023,52 @@ func (o *Config) SetDefaults() {
if o.SamlSettings.FirstNameAttribute == nil {
o.SamlSettings.FirstNameAttribute = new(string)
- *o.SamlSettings.FirstNameAttribute = ""
+ *o.SamlSettings.FirstNameAttribute = SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE
}
if o.SamlSettings.LastNameAttribute == nil {
o.SamlSettings.LastNameAttribute = new(string)
- *o.SamlSettings.LastNameAttribute = ""
+ *o.SamlSettings.LastNameAttribute = SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE
}
if o.SamlSettings.EmailAttribute == nil {
o.SamlSettings.EmailAttribute = new(string)
- *o.SamlSettings.EmailAttribute = ""
+ *o.SamlSettings.EmailAttribute = SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE
}
if o.SamlSettings.UsernameAttribute == nil {
o.SamlSettings.UsernameAttribute = new(string)
- *o.SamlSettings.UsernameAttribute = ""
+ *o.SamlSettings.UsernameAttribute = SAML_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE
}
if o.SamlSettings.NicknameAttribute == nil {
o.SamlSettings.NicknameAttribute = new(string)
- *o.SamlSettings.NicknameAttribute = ""
+ *o.SamlSettings.NicknameAttribute = SAML_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE
}
if o.SamlSettings.PositionAttribute == nil {
o.SamlSettings.PositionAttribute = new(string)
- *o.SamlSettings.PositionAttribute = ""
+ *o.SamlSettings.PositionAttribute = SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE
}
if o.SamlSettings.LocaleAttribute == nil {
o.SamlSettings.LocaleAttribute = new(string)
- *o.SamlSettings.LocaleAttribute = ""
+ *o.SamlSettings.LocaleAttribute = SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE
}
if o.NativeAppSettings.AppDownloadLink == nil {
o.NativeAppSettings.AppDownloadLink = new(string)
- *o.NativeAppSettings.AppDownloadLink = "https://about.mattermost.com/downloads/"
+ *o.NativeAppSettings.AppDownloadLink = NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK
}
if o.NativeAppSettings.AndroidAppDownloadLink == nil {
o.NativeAppSettings.AndroidAppDownloadLink = new(string)
- *o.NativeAppSettings.AndroidAppDownloadLink = "https://about.mattermost.com/mattermost-android-app/"
+ *o.NativeAppSettings.AndroidAppDownloadLink = NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK
}
if o.NativeAppSettings.IosAppDownloadLink == nil {
o.NativeAppSettings.IosAppDownloadLink = new(string)
- *o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/"
+ *o.NativeAppSettings.IosAppDownloadLink = NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK
}
if o.RateLimitSettings.Enable == nil {
@@ -1008,12 +1088,12 @@ func (o *Config) SetDefaults() {
if o.ServiceSettings.TLSKeyFile == nil {
o.ServiceSettings.TLSKeyFile = new(string)
- *o.ServiceSettings.TLSKeyFile = ""
+ *o.ServiceSettings.TLSKeyFile = SERVICE_SETTINGS_DEFAULT_TLS_KEY_FILE
}
if o.ServiceSettings.TLSCertFile == nil {
o.ServiceSettings.TLSCertFile = new(string)
- *o.ServiceSettings.TLSCertFile = ""
+ *o.ServiceSettings.TLSCertFile = SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE
}
if o.ServiceSettings.UseLetsEncrypt == nil {
@@ -1028,12 +1108,12 @@ func (o *Config) SetDefaults() {
if o.ServiceSettings.ReadTimeout == nil {
o.ServiceSettings.ReadTimeout = new(int)
- *o.ServiceSettings.ReadTimeout = 300
+ *o.ServiceSettings.ReadTimeout = SERVICE_SETTINGS_DEFAULT_READ_TIMEOUT
}
if o.ServiceSettings.WriteTimeout == nil {
o.ServiceSettings.WriteTimeout = new(int)
- *o.ServiceSettings.WriteTimeout = 300
+ *o.ServiceSettings.WriteTimeout = SERVICE_SETTINGS_DEFAULT_WRITE_TIMEOUT
}
if o.ServiceSettings.Forward80To443 == nil {
@@ -1046,6 +1126,21 @@ func (o *Config) SetDefaults() {
*o.MetricsSettings.BlockProfileRate = 0
}
+ if o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds == nil {
+ o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds = new(int64)
+ *o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds = 5000
+ }
+
+ if o.ServiceSettings.EnableUserTypingMessages == nil {
+ o.ServiceSettings.EnableUserTypingMessages = new(bool)
+ *o.ServiceSettings.EnableUserTypingMessages = true
+ }
+
+ if o.ServiceSettings.ClusterLogTimeoutMilliseconds == nil {
+ o.ServiceSettings.ClusterLogTimeoutMilliseconds = new(int)
+ *o.ServiceSettings.ClusterLogTimeoutMilliseconds = 2000
+ }
+
o.defaultWebrtcSettings()
}
@@ -1277,6 +1372,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "")
}
+ if *o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds < 1000 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "")
+ }
+
return nil
}
@@ -1339,12 +1438,12 @@ func (o *Config) defaultWebrtcSettings() {
if o.WebrtcSettings.StunURI == nil {
o.WebrtcSettings.StunURI = new(string)
- *o.WebrtcSettings.StunURI = ""
+ *o.WebrtcSettings.StunURI = WEBRTC_SETTINGS_DEFAULT_STUN_URI
}
if o.WebrtcSettings.TurnURI == nil {
o.WebrtcSettings.TurnURI = new(string)
- *o.WebrtcSettings.TurnURI = ""
+ *o.WebrtcSettings.TurnURI = WEBRTC_SETTINGS_DEFAULT_TURN_URI
}
if o.WebrtcSettings.TurnUsername == nil {
diff --git a/vendor/github.com/mattermost/platform/model/file.go b/vendor/github.com/mattermost/platform/model/file.go
index c218c424..20f6236d 100644
--- a/vendor/github.com/mattermost/platform/model/file.go
+++ b/vendor/github.com/mattermost/platform/model/file.go
@@ -8,6 +8,10 @@ import (
"io"
)
+const (
+ MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image
+)
+
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 e189ee68..270d62d8 100644
--- a/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
+++ b/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
@@ -65,6 +65,15 @@ func gitLabUserFromJson(data io.Reader) *GitLabUser {
}
}
+func (glu *GitLabUser) ToJson() string {
+ b, err := json.Marshal(glu)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
func (glu *GitLabUser) IsValid() bool {
if glu.Id == 0 {
return false
diff --git a/vendor/github.com/mattermost/platform/model/incoming_webhook.go b/vendor/github.com/mattermost/platform/model/incoming_webhook.go
index 72fa3e54..2cc26cbc 100644
--- a/vendor/github.com/mattermost/platform/model/incoming_webhook.go
+++ b/vendor/github.com/mattermost/platform/model/incoming_webhook.go
@@ -29,13 +29,13 @@ type IncomingWebhook struct {
}
type IncomingWebhookRequest struct {
- Text string `json:"text"`
- Username string `json:"username"`
- IconURL string `json:"icon_url"`
- ChannelName string `json:"channel"`
- Props StringInterface `json:"props"`
- Attachments interface{} `json:"attachments"`
- Type string `json:"type"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ ChannelName string `json:"channel"`
+ Props StringInterface `json:"props"`
+ Attachments []*SlackAttachment `json:"attachments"`
+ Type string `json:"type"`
}
func (o *IncomingWebhook) ToJson() string {
@@ -212,31 +212,15 @@ func expandAnnouncement(text string) string {
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{})
+ for _, attachment := range i.Attachments {
+ attachment.Pretext = expandAnnouncement(attachment.Pretext)
+ attachment.Text = expandAnnouncement(attachment.Text)
+ attachment.Title = expandAnnouncement(attachment.Title)
- 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(fmt.Sprintf("%v", f["value"]))
- }
- }
+ for _, field := range attachment.Fields {
+ if field.Value != nil {
+ // Ensure the value is set to a string if it is set
+ field.Value = expandAnnouncement(fmt.Sprintf("%v", field.Value))
}
}
}
diff --git a/vendor/github.com/mattermost/platform/model/job.go b/vendor/github.com/mattermost/platform/model/job.go
index 229d5efd..09d74aa0 100644
--- a/vendor/github.com/mattermost/platform/model/job.go
+++ b/vendor/github.com/mattermost/platform/model/job.go
@@ -14,8 +14,8 @@ 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"`
+ function TaskFunc
+ timer *time.Timer
}
var tasks = make(map[string]*ScheduledTask)
diff --git a/vendor/github.com/mattermost/platform/model/license.go b/vendor/github.com/mattermost/platform/model/license.go
index 7115aa1a..09da61eb 100644
--- a/vendor/github.com/mattermost/platform/model/license.go
+++ b/vendor/github.com/mattermost/platform/model/license.go
@@ -8,6 +8,11 @@ import (
"io"
)
+const (
+ EXPIRED_LICENSE_ERROR = "api.license.add_license.expired.app_error"
+ INVALID_LICENSE_ERROR = "api.license.add_license.invalid.app_error"
+)
+
type LicenseRecord struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
diff --git a/vendor/github.com/mattermost/platform/model/post.go b/vendor/github.com/mattermost/platform/model/post.go
index 7097e031..a7729e0c 100644
--- a/vendor/github.com/mattermost/platform/model/post.go
+++ b/vendor/github.com/mattermost/platform/model/post.go
@@ -14,10 +14,15 @@ const (
POST_DEFAULT = ""
POST_SLACK_ATTACHMENT = "slack_attachment"
POST_SYSTEM_GENERIC = "system_generic"
- POST_JOIN_LEAVE = "system_join_leave"
- POST_ADD_REMOVE = "system_add_remove"
+ POST_JOIN_LEAVE = "system_join_leave" // Deprecated, use POST_JOIN_CHANNEL or POST_LEAVE_CHANNEL instead
+ POST_JOIN_CHANNEL = "system_join_channel"
+ POST_LEAVE_CHANNEL = "system_leave_channel"
+ POST_ADD_REMOVE = "system_add_remove" // Deprecated, use POST_ADD_TO_CHANNEL or POST_REMOVE_FROM_CHANNEL instead
+ POST_ADD_TO_CHANNEL = "system_add_to_channel"
+ POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel"
POST_HEADER_CHANGE = "system_header_change"
POST_DISPLAYNAME_CHANGE = "system_displayname_change"
+ POST_PURPOSE_CHANGE = "system_purpose_change"
POST_CHANNEL_DELETED = "system_channel_deleted"
POST_EPHEMERAL = "system_ephemeral"
POST_FILEIDS_MAX_RUNES = 150
@@ -31,6 +36,7 @@ type Post struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
+ EditAt int64 `json:"edit_at"`
DeleteAt int64 `json:"delete_at"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
@@ -119,7 +125,9 @@ func (o *Post) IsValid() *AppError {
// should be removed once more message types are supported
if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_ADD_REMOVE ||
- o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE ||
+ o.Type == POST_JOIN_CHANNEL || o.Type == POST_LEAVE_CHANNEL ||
+ o.Type == POST_REMOVE_FROM_CHANNEL || o.Type == POST_ADD_TO_CHANNEL ||
+ o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE || o.Type == POST_PURPOSE_CHANGE ||
o.Type == POST_DISPLAYNAME_CHANGE || o.Type == POST_CHANNEL_DELETED) {
return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type)
}
diff --git a/vendor/github.com/mattermost/platform/model/post_list.go b/vendor/github.com/mattermost/platform/model/post_list.go
index 4c0f5408..9b56f023 100644
--- a/vendor/github.com/mattermost/platform/model/post_list.go
+++ b/vendor/github.com/mattermost/platform/model/post_list.go
@@ -13,6 +13,13 @@ type PostList struct {
Posts map[string]*Post `json:"posts"`
}
+func NewPostList() *PostList {
+ return &PostList{
+ Order: make([]string, 0),
+ Posts: make(map[string]*Post),
+ }
+}
+
func (o *PostList) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
@@ -72,10 +79,18 @@ func (o *PostList) Etag() string {
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
+ } else if v.UpdateAt == t && v.Id > id {
+ t = v.UpdateAt
+ id = v.Id
}
}
- return Etag(id, t)
+ orderId := ""
+ if len(o.Order) > 0 {
+ orderId = o.Order[0]
+ }
+
+ return Etag(orderId, id, t)
}
func (o *PostList) IsChannelId(channelId string) bool {
diff --git a/vendor/github.com/mattermost/platform/model/push_notification.go b/vendor/github.com/mattermost/platform/model/push_notification.go
index 3c010fb7..753495b2 100644
--- a/vendor/github.com/mattermost/platform/model/push_notification.go
+++ b/vendor/github.com/mattermost/platform/model/push_notification.go
@@ -10,8 +10,10 @@ import (
)
const (
- PUSH_NOTIFY_APPLE = "apple"
- PUSH_NOTIFY_ANDROID = "android"
+ PUSH_NOTIFY_APPLE = "apple"
+ PUSH_NOTIFY_ANDROID = "android"
+ PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn"
+ PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn"
PUSH_TYPE_MESSAGE = "message"
PUSH_TYPE_CLEAR = "clear"
@@ -46,12 +48,12 @@ func (me *PushNotification) ToJson() string {
}
func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) {
- if strings.HasPrefix(deviceId, PUSH_NOTIFY_APPLE+":") {
- me.Platform = PUSH_NOTIFY_APPLE
- me.DeviceId = strings.TrimPrefix(deviceId, PUSH_NOTIFY_APPLE+":")
- } else if strings.HasPrefix(deviceId, PUSH_NOTIFY_ANDROID+":") {
- me.Platform = PUSH_NOTIFY_ANDROID
- me.DeviceId = strings.TrimPrefix(deviceId, PUSH_NOTIFY_ANDROID+":")
+
+ index := strings.Index(deviceId, ":")
+
+ if index > -1 {
+ me.Platform = deviceId[:index]
+ me.DeviceId = deviceId[index+1:]
}
}
diff --git a/vendor/github.com/mattermost/platform/model/push_response.go b/vendor/github.com/mattermost/platform/model/push_response.go
new file mode 100644
index 00000000..0271bc94
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/push_response.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ PUSH_STATUS = "status"
+ PUSH_STATUS_OK = "OK"
+ PUSH_STATUS_FAIL = "FAIL"
+ PUSH_STATUS_REMOVE = "REMOVE"
+ PUSH_STATUS_ERROR_MSG = "error"
+)
+
+type PushResponse map[string]string
+
+func NewOkPushResponse() PushResponse {
+ m := make(map[string]string)
+ m[PUSH_STATUS] = PUSH_STATUS_OK
+ return m
+}
+
+func NewRemovePushResponse() PushResponse {
+ m := make(map[string]string)
+ m[PUSH_STATUS] = PUSH_STATUS_REMOVE
+ return m
+}
+
+func NewErrorPushResponse(message string) PushResponse {
+ m := make(map[string]string)
+ m[PUSH_STATUS] = PUSH_STATUS_FAIL
+ m[PUSH_STATUS_ERROR_MSG] = message
+ return m
+}
+
+func (me *PushResponse) ToJson() string {
+ if b, err := json.Marshal(me); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func PushResponseFromJson(data io.Reader) PushResponse {
+ decoder := json.NewDecoder(data)
+
+ var objmap PushResponse
+ if err := decoder.Decode(&objmap); err != nil {
+ return make(map[string]string)
+ } else {
+ return objmap
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/session.go b/vendor/github.com/mattermost/platform/model/session.go
index a6a753e7..277e910f 100644
--- a/vendor/github.com/mattermost/platform/model/session.go
+++ b/vendor/github.com/mattermost/platform/model/session.go
@@ -11,7 +11,7 @@ import (
const (
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
- SESSION_CACHE_SIZE = 25000
+ SESSION_CACHE_SIZE = 35000
SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
@@ -111,8 +111,7 @@ func (me *Session) GetTeamByTeamId(teamId string) *TeamMember {
}
func (me *Session) IsMobileApp() bool {
- return len(me.DeviceId) > 0 &&
- (strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_ANDROID+":"))
+ return len(me.DeviceId) > 0
}
func (me *Session) GetUserRoles() []string {
diff --git a/vendor/github.com/mattermost/platform/model/slack_attachment.go b/vendor/github.com/mattermost/platform/model/slack_attachment.go
new file mode 100644
index 00000000..e1639f2a
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/slack_attachment.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+type SlackAttachment struct {
+ Id int64 `json:"id"`
+ Fallback string `json:"fallback"`
+ Color string `json:"color"`
+ Pretext string `json:"pretext"`
+ AuthorName string `json:"author_name"`
+ AuthorLink string `json:"author_link"`
+ AuthorIcon string `json:"author_icon"`
+ Title string `json:"title"`
+ TitleLink string `json:"title_link"`
+ Text string `json:"text"`
+ Fields []*SlackAttachmentField `json:"fields"`
+ ImageURL string `json:"image_url"`
+ ThumbURL string `json:"thumb_url"`
+ Footer string `json:"footer"`
+ FooterIcon string `json:"footer_icon"`
+ Timestamp interface{} `json:"ts"` // This is either a string or an int64
+}
+
+type SlackAttachmentField struct {
+ Title string `json:"title"`
+ Value interface{} `json:"value"`
+ Short bool `json:"short"`
+}
diff --git a/vendor/github.com/mattermost/platform/model/status.go b/vendor/github.com/mattermost/platform/model/status.go
index fec3a5f7..fc155788 100644
--- a/vendor/github.com/mattermost/platform/model/status.go
+++ b/vendor/github.com/mattermost/platform/model/status.go
@@ -12,7 +12,7 @@ const (
STATUS_OFFLINE = "offline"
STATUS_AWAY = "away"
STATUS_ONLINE = "online"
- STATUS_CACHE_SIZE = 25000
+ STATUS_CACHE_SIZE = SESSION_CACHE_SIZE
STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes
)
diff --git a/vendor/github.com/mattermost/platform/model/team.go b/vendor/github.com/mattermost/platform/model/team.go
index 3f05ce83..99444bc5 100644
--- a/vendor/github.com/mattermost/platform/model/team.go
+++ b/vendor/github.com/mattermost/platform/model/team.go
@@ -7,14 +7,22 @@ import (
"encoding/json"
"fmt"
"io"
+ "net/http"
"regexp"
"strings"
"unicode/utf8"
)
const (
- TEAM_OPEN = "O"
- TEAM_INVITE = "I"
+ TEAM_OPEN = "O"
+ TEAM_INVITE = "I"
+ TEAM_ALLOWED_DOMAINS_MAX_LENGTH = 500
+ TEAM_COMPANY_NAME_MAX_LENGTH = 64
+ TEAM_DESCRIPTION_MAX_LENGTH = 255
+ TEAM_DISPLAY_NAME_MAX_RUNES = 64
+ TEAM_EMAIL_MAX_LENGTH = 128
+ TEAM_NAME_MAX_LENGTH = 64
+ TEAM_NAME_MIN_LENGTH = 2
)
type Team struct {
@@ -48,6 +56,14 @@ func InvitesFromJson(data io.Reader) *Invites {
}
}
+func (o *Invites) ToEmailList() []string {
+ emailList := make([]string, len(o.Invites))
+ for _, invite := range o.Invites {
+ emailList = append(emailList, invite["email"])
+ }
+ return emailList
+}
+
func (o *Invites) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
@@ -97,6 +113,26 @@ func TeamMapFromJson(data io.Reader) map[string]*Team {
}
}
+func TeamListToJson(t []*Team) string {
+ b, err := json.Marshal(t)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func TeamListFromJson(data io.Reader) []*Team {
+ decoder := json.NewDecoder(data)
+ var teams []*Team
+ err := decoder.Decode(&teams)
+ if err == nil {
+ return teams
+ } else {
+ return nil
+ }
+}
+
func (o *Team) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
@@ -104,55 +140,55 @@ func (o *Team) Etag() string {
func (o *Team) IsValid() *AppError {
if len(o.Id) != 26 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "")
+ return NewAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id)
+ return NewAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id)
+ return NewAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
- if len(o.Email) > 128 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id)
+ if len(o.Email) > TEAM_EMAIL_MAX_LENGTH {
+ return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.Email) > 0 && !IsValidEmail(o.Email) {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id)
+ return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
- if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > 64 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id)
+ if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > TEAM_DISPLAY_NAME_MAX_RUNES {
+ return NewAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
- if len(o.Name) > 64 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id)
+ if len(o.Name) > TEAM_NAME_MAX_LENGTH {
+ return NewAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
- if len(o.Description) > 255 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id)
+ if len(o.Description) > TEAM_DESCRIPTION_MAX_LENGTH {
+ return NewAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if IsReservedTeamName(o.Name) {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id)
+ return NewAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !IsValidTeamName(o.Name) {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id)
+ return NewAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id)
+ return NewAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
- if len(o.CompanyName) > 64 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id)
+ if len(o.CompanyName) > TEAM_COMPANY_NAME_MAX_LENGTH {
+ return NewAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
- if len(o.AllowedDomains) > 500 {
- return NewLocAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id)
+ if len(o.AllowedDomains) > TEAM_ALLOWED_DOMAINS_MAX_LENGTH {
+ return NewAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
return nil
@@ -193,7 +229,7 @@ func IsValidTeamName(s string) bool {
return false
}
- if len(s) <= 1 {
+ if len(s) < TEAM_NAME_MIN_LENGTH {
return false
}
diff --git a/vendor/github.com/mattermost/platform/model/user.go b/vendor/github.com/mattermost/platform/model/user.go
index 76c3772c..d62ccccd 100644
--- a/vendor/github.com/mattermost/platform/model/user.go
+++ b/vendor/github.com/mattermost/platform/model/user.go
@@ -7,20 +7,36 @@ import (
"encoding/json"
"fmt"
"io"
+ "net/http"
"regexp"
"strings"
+ "unicode"
"unicode/utf8"
"golang.org/x/crypto/bcrypt"
)
const (
- USER_NOTIFY_ALL = "all"
- USER_NOTIFY_MENTION = "mention"
- USER_NOTIFY_NONE = "none"
+ USER_NOTIFY_ALL = "all"
+ USER_NOTIFY_MENTION = "mention"
+ USER_NOTIFY_NONE = "none"
+ DESKTOP_NOTIFY_PROP = "desktop"
+ MARK_UNREAD_NOTIFY_PROP = "mark_unread"
+ PUSH_NOTIFY_PROP = "push"
+ EMAIL_NOTIFY_PROP = "email"
+
DEFAULT_LOCALE = "en"
USER_AUTH_SERVICE_EMAIL = "email"
USER_AUTH_SERVICE_USERNAME = "username"
+
+ USER_EMAIL_MAX_LENGTH = 128
+ USER_NICKNAME_MAX_RUNES = 64
+ USER_POSITION_MAX_RUNES = 35
+ USER_FIRST_NAME_MAX_RUNES = 64
+ USER_LAST_NAME_MAX_RUNES = 64
+ USER_AUTH_DATA_MAX_LENGTH = 128
+ USER_NAME_MAX_LENGTH = 64
+ USER_NAME_MIN_LENGTH = 1
)
type User struct {
@@ -51,56 +67,68 @@ type User struct {
LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
}
+type UserPatch struct {
+ Username *string `json:"username"`
+ Nickname *string `json:"nickname"`
+ FirstName *string `json:"first_name"`
+ LastName *string `json:"last_name"`
+ Position *string `json:"position"`
+ Email *string `json:"email"`
+ Props *StringMap `json:"props,omitempty"`
+ NotifyProps *StringMap `json:"notify_props,omitempty"`
+ Locale *string `json:"locale"`
+}
+
// IsValid validates the user and returns an error if it isn't configured
// correctly.
func (u *User) IsValid() *AppError {
if len(u.Id) != 26 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.id.app_error", nil, "")
+ return NewAppError("User.IsValid", "model.user.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if u.CreateAt == 0 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.create_at.app_error", nil, "user_id="+u.Id)
+ return NewAppError("User.IsValid", "model.user.is_valid.create_at.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
if u.UpdateAt == 0 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id)
+ return NewAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
if !IsValidUsername(u.Username) {
- return NewLocAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id)
+ return NewAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
- if len(u.Email) > 128 || len(u.Email) == 0 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.email.app_error", nil, "user_id="+u.Id)
+ if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 {
+ return NewAppError("User.IsValid", "model.user.is_valid.email.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
- if utf8.RuneCountInString(u.Nickname) > 64 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id)
+ if utf8.RuneCountInString(u.Nickname) > USER_NICKNAME_MAX_RUNES {
+ return NewAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
- if utf8.RuneCountInString(u.Position) > 35 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.position.app_error", nil, "user_id="+u.Id)
+ if utf8.RuneCountInString(u.Position) > USER_POSITION_MAX_RUNES {
+ return NewAppError("User.IsValid", "model.user.is_valid.position.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
- if utf8.RuneCountInString(u.FirstName) > 64 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id)
+ if utf8.RuneCountInString(u.FirstName) > USER_FIRST_NAME_MAX_RUNES {
+ return NewAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
- if utf8.RuneCountInString(u.LastName) > 64 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id)
+ if utf8.RuneCountInString(u.LastName) > USER_LAST_NAME_MAX_RUNES {
+ return NewAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
- if u.AuthData != nil && len(*u.AuthData) > 128 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id)
+ if u.AuthData != nil && len(*u.AuthData) > USER_AUTH_DATA_MAX_LENGTH {
+ return NewAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_type.app_error", nil, "user_id="+u.Id)
+ return NewAppError("User.IsValid", "model.user.is_valid.auth_data_type.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 {
- return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
+ return NewAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id, http.StatusBadRequest)
}
return nil
@@ -115,7 +143,7 @@ func (u *User) PreSave() {
}
if u.Username == "" {
- u.Username = NewId()
+ u.Username = "n" + NewId()
}
if u.AuthData != nil && *u.AuthData == "" {
@@ -205,6 +233,44 @@ func (user *User) UpdateMentionKeysFromUsername(oldUsername string) {
}
}
+func (u *User) Patch(patch *UserPatch) {
+ if patch.Username != nil {
+ u.Username = *patch.Username
+ }
+
+ if patch.Nickname != nil {
+ u.Nickname = *patch.Nickname
+ }
+
+ if patch.FirstName != nil {
+ u.FirstName = *patch.FirstName
+ }
+
+ if patch.LastName != nil {
+ u.LastName = *patch.LastName
+ }
+
+ if patch.Position != nil {
+ u.Position = *patch.Position
+ }
+
+ if patch.Email != nil {
+ u.Email = *patch.Email
+ }
+
+ if patch.Props != nil {
+ u.Props = *patch.Props
+ }
+
+ if patch.NotifyProps != nil {
+ u.NotifyProps = *patch.NotifyProps
+ }
+
+ if patch.Locale != nil {
+ u.Locale = *patch.Locale
+ }
+}
+
// ToJson convert a User to a json string
func (u *User) ToJson() string {
b, err := json.Marshal(u)
@@ -215,6 +281,15 @@ func (u *User) ToJson() string {
}
}
+func (u *UserPatch) ToJson() string {
+ b, err := json.Marshal(u)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
// Generate a valid strong etag so the browser can cache the results
func (u *User) Etag(showFullName, showEmail bool) string {
return Etag(u.Id, u.UpdateAt, showFullName, showEmail)
@@ -376,6 +451,13 @@ func IsInRole(userRoles string, inRole string) bool {
return false
}
+func (u *User) IsSSOUser() bool {
+ if u.AuthService != "" && u.AuthService != USER_AUTH_SERVICE_EMAIL {
+ return true
+ }
+ return false
+}
+
func (u *User) IsOAuthUser() bool {
if u.AuthService == USER_AUTH_SERVICE_GITLAB {
return true
@@ -402,6 +484,17 @@ func UserFromJson(data io.Reader) *User {
}
}
+func UserPatchFromJson(data io.Reader) *UserPatch {
+ decoder := json.NewDecoder(data)
+ var user UserPatch
+ err := decoder.Decode(&user)
+ if err == nil {
+ return &user
+ } else {
+ return nil
+ }
+}
+
func UserMapToJson(u map[string]*User) string {
b, err := json.Marshal(u)
if err != nil {
@@ -472,7 +565,7 @@ var restrictedUsernames = []string{
}
func IsValidUsername(s string) bool {
- if len(s) == 0 || len(s) > 64 {
+ if len(s) < USER_NAME_MIN_LENGTH || len(s) > USER_NAME_MAX_LENGTH {
return false
}
@@ -480,6 +573,10 @@ func IsValidUsername(s string) bool {
return false
}
+ if !unicode.IsLetter(rune(s[0])) {
+ return false
+ }
+
for _, restrictedUsername := range restrictedUsernames {
if s == restrictedUsername {
return false
diff --git a/vendor/github.com/mattermost/platform/model/utils.go b/vendor/github.com/mattermost/platform/model/utils.go
index 0ce243fe..08809a47 100644
--- a/vendor/github.com/mattermost/platform/model/utils.go
+++ b/vendor/github.com/mattermost/platform/model/utils.go
@@ -34,14 +34,14 @@ type StringArray []string
type EncryptStringMap map[string]string
type AppError struct {
- Id string `json:"id"`
- Message string `json:"message"` // Message to be display to the end user without debugging information
- DetailedError string `json:"detailed_error"` // Internal error string to help the developer
- RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header
- StatusCode int `json:"status_code,omitempty"` // The http status code
- Where string `json:"-"` // The function where it happened in the form of Struct.Func
- IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific
- params map[string]interface{} `json:"-"`
+ Id string `json:"id"`
+ Message string `json:"message"` // Message to be display to the end user without debugging information
+ DetailedError string `json:"detailed_error"` // Internal error string to help the developer
+ RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header
+ StatusCode int `json:"status_code,omitempty"` // The http status code
+ Where string `json:"-"` // The function where it happened in the form of Struct.Func
+ IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific
+ params map[string]interface{}
}
func (er *AppError) Error() string {
@@ -93,6 +93,18 @@ func AppErrorFromJson(data io.Reader) *AppError {
}
}
+func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError {
+ ap := &AppError{}
+ ap.Id = id
+ ap.params = params
+ ap.Message = id
+ ap.Where = where
+ ap.DetailedError = details
+ ap.StatusCode = status
+ ap.IsOAuth = false
+ return ap
+}
+
func NewLocAppError(where string, id string, params map[string]interface{}, details string) *AppError {
ap := &AppError{}
ap.Id = id
@@ -268,7 +280,7 @@ func IsValidChannelIdentifier(s string) bool {
return false
}
- if len(s) < 2 {
+ if len(s) < CHANNEL_NAME_MIN_LENGTH {
return false
}
@@ -370,7 +382,7 @@ func ClearMentionTags(post string) string {
var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(?:\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(?:\?[a-z0-9+_~\-\.%=&]*)?)?(?:#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(?:\s+|$)$`)
var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+(?:\.[A-Za-z0-9]{3,})?)`)
-var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true}
+var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true, '^': true, '#': true, '$': true, '&': true}
func IsValidHttpUrl(rawUrl string) bool {
if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
diff --git a/vendor/github.com/mattermost/platform/model/version.go b/vendor/github.com/mattermost/platform/model/version.go
index 2a034dec..32b1c3f5 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.7.0",
"3.6.0",
"3.5.0",
"3.4.0",
diff --git a/vendor/github.com/mattermost/platform/model/websocket_client.go b/vendor/github.com/mattermost/platform/model/websocket_client.go
index 453ae49b..083fe110 100644
--- a/vendor/github.com/mattermost/platform/model/websocket_client.go
+++ b/vendor/github.com/mattermost/platform/model/websocket_client.go
@@ -8,6 +8,10 @@ import (
"github.com/gorilla/websocket"
)
+const (
+ SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB
+)
+
type WebSocketClient struct {
Url string // The location of the server like "ws://localhost:8065"
ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3"
@@ -22,14 +26,14 @@ type WebSocketClient struct {
// NewWebSocketClient constructs a new WebSocket client with convienence
// methods for talking to the server.
func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
- conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", nil)
+ conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX_V3+"/users/websocket", nil)
if err != nil {
return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
}
client := &WebSocketClient{
url,
- url + API_URL_SUFFIX,
+ url + API_URL_SUFFIX_V3,
conn,
authToken,
1,
diff --git a/vendor/github.com/mattermost/platform/model/websocket_message.go b/vendor/github.com/mattermost/platform/model/websocket_message.go
index 5c956d57..23820470 100644
--- a/vendor/github.com/mattermost/platform/model/websocket_message.go
+++ b/vendor/github.com/mattermost/platform/model/websocket_message.go
@@ -14,8 +14,9 @@ const (
WEBSOCKET_EVENT_POST_EDITED = "post_edited"
WEBSOCKET_EVENT_POST_DELETED = "post_deleted"
WEBSOCKET_EVENT_CHANNEL_DELETED = "channel_deleted"
- WEBSOCKET_EVENT_CHANNEL_VIEWED = "channel_viewed"
+ WEBSOCKET_EVENT_CHANNEL_CREATED = "channel_created"
WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added"
+ WEBSOCKET_EVENT_GROUP_ADDED = "group_added"
WEBSOCKET_EVENT_NEW_USER = "new_user"
WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team"
WEBSOCKET_EVENT_UPDATE_TEAM = "update_team"
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/LICENSE.txt b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/LICENSE.txt
new file mode 100644
index 00000000..e5875711
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/LICENSE.txt
@@ -0,0 +1,897 @@
+Mattermost Licensing
+
+SOFTWARE LICENSING
+
+You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
+
+- See MIT-COMPILED-LICENSE.md included in compiled versions for details
+
+You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:
+
+1. Under the Free Software Foundation’s GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
+2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
+
+You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
+webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
+
+We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
+link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
+(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
+a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license.
+
+MATTERMOST TRADEMARK GUIDELINES
+
+Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organization’s Trademark
+Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
+you have about using these trademarks, please email trademark@mattermost.com
+
+------------------------------------------------------------------------------------------------------------------------------
+
+ 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.
+
+------------------------------------------------------------------------------
+
+The software is released under the terms of the GNU Affero General Public
+License, version 3.
+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/config.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/config.go
new file mode 100644
index 00000000..577c3eb2
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/config.go
@@ -0,0 +1,288 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+package log4go
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strconv"
+ "strings"
+)
+
+type xmlProperty struct {
+ Name string `xml:"name,attr"`
+ Value string `xml:",chardata"`
+}
+
+type xmlFilter struct {
+ Enabled string `xml:"enabled,attr"`
+ Tag string `xml:"tag"`
+ Level string `xml:"level"`
+ Type string `xml:"type"`
+ Property []xmlProperty `xml:"property"`
+}
+
+type xmlLoggerConfig struct {
+ Filter []xmlFilter `xml:"filter"`
+}
+
+// Load XML configuration; see examples/example.xml for documentation
+func (log Logger) LoadConfiguration(filename string) {
+ log.Close()
+
+ // Open the configuration file
+ fd, err := os.Open(filename)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err)
+ os.Exit(1)
+ }
+
+ contents, err := ioutil.ReadAll(fd)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err)
+ os.Exit(1)
+ }
+
+ xc := new(xmlLoggerConfig)
+ if err := xml.Unmarshal(contents, xc); err != nil {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err)
+ os.Exit(1)
+ }
+
+ for _, xmlfilt := range xc.Filter {
+ var filt LogWriter
+ var lvl Level
+ bad, good, enabled := false, true, false
+
+ // Check required children
+ if len(xmlfilt.Enabled) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename)
+ bad = true
+ } else {
+ enabled = xmlfilt.Enabled != "false"
+ }
+ if len(xmlfilt.Tag) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename)
+ bad = true
+ }
+ if len(xmlfilt.Type) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename)
+ bad = true
+ }
+ if len(xmlfilt.Level) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename)
+ bad = true
+ }
+
+ switch xmlfilt.Level {
+ case "FINEST":
+ lvl = FINEST
+ case "FINE":
+ lvl = FINE
+ case "DEBUG":
+ lvl = DEBUG
+ case "TRACE":
+ lvl = TRACE
+ case "INFO":
+ lvl = INFO
+ case "WARNING":
+ lvl = WARNING
+ case "ERROR":
+ lvl = ERROR
+ case "CRITICAL":
+ lvl = CRITICAL
+ default:
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level)
+ bad = true
+ }
+
+ // Just so all of the required attributes are errored at the same time if missing
+ if bad {
+ os.Exit(1)
+ }
+
+ switch xmlfilt.Type {
+ case "console":
+ filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled)
+ case "file":
+ filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled)
+ case "xml":
+ filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled)
+ case "socket":
+ filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled)
+ default:
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type)
+ os.Exit(1)
+ }
+
+ // Just so all of the required params are errored at the same time if wrong
+ if !good {
+ os.Exit(1)
+ }
+
+ // If we're disabled (syntax and correctness checks only), don't add to logger
+ if !enabled {
+ continue
+ }
+
+ log[xmlfilt.Tag] = &Filter{lvl, filt}
+ }
+}
+
+func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) {
+ // Parse properties
+ for _, prop := range props {
+ switch prop.Name {
+ default:
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename)
+ }
+ }
+
+ // If it's disabled, we're just checking syntax
+ if !enabled {
+ return nil, true
+ }
+
+ return NewConsoleLogWriter(), true
+}
+
+// Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024)
+func strToNumSuffix(str string, mult int) int {
+ num := 1
+ if len(str) > 1 {
+ switch str[len(str)-1] {
+ case 'G', 'g':
+ num *= mult
+ fallthrough
+ case 'M', 'm':
+ num *= mult
+ fallthrough
+ case 'K', 'k':
+ num *= mult
+ str = str[0 : len(str)-1]
+ }
+ }
+ parsed, _ := strconv.Atoi(str)
+ return parsed * num
+}
+func xmlToFileLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) {
+ file := ""
+ format := "[%D %T] [%L] (%S) %M"
+ maxlines := 0
+ maxsize := 0
+ daily := false
+ rotate := false
+
+ // Parse properties
+ for _, prop := range props {
+ switch prop.Name {
+ case "filename":
+ file = strings.Trim(prop.Value, " \r\n")
+ case "format":
+ format = strings.Trim(prop.Value, " \r\n")
+ case "maxlines":
+ maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000)
+ case "maxsize":
+ maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024)
+ case "daily":
+ daily = strings.Trim(prop.Value, " \r\n") != "false"
+ case "rotate":
+ rotate = strings.Trim(prop.Value, " \r\n") != "false"
+ default:
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename)
+ }
+ }
+
+ // Check properties
+ if len(file) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename)
+ return nil, false
+ }
+
+ // If it's disabled, we're just checking syntax
+ if !enabled {
+ return nil, true
+ }
+
+ flw := NewFileLogWriter(file, rotate)
+ flw.SetFormat(format)
+ flw.SetRotateLines(maxlines)
+ flw.SetRotateSize(maxsize)
+ flw.SetRotateDaily(daily)
+ return flw, true
+}
+
+func xmlToXMLLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) {
+ file := ""
+ maxrecords := 0
+ maxsize := 0
+ daily := false
+ rotate := false
+
+ // Parse properties
+ for _, prop := range props {
+ switch prop.Name {
+ case "filename":
+ file = strings.Trim(prop.Value, " \r\n")
+ case "maxrecords":
+ maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000)
+ case "maxsize":
+ maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024)
+ case "daily":
+ daily = strings.Trim(prop.Value, " \r\n") != "false"
+ case "rotate":
+ rotate = strings.Trim(prop.Value, " \r\n") != "false"
+ default:
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename)
+ }
+ }
+
+ // Check properties
+ if len(file) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename)
+ return nil, false
+ }
+
+ // If it's disabled, we're just checking syntax
+ if !enabled {
+ return nil, true
+ }
+
+ xlw := NewXMLLogWriter(file, rotate)
+ xlw.SetRotateLines(maxrecords)
+ xlw.SetRotateSize(maxsize)
+ xlw.SetRotateDaily(daily)
+ return xlw, true
+}
+
+func xmlToSocketLogWriter(filename string, props []xmlProperty, enabled bool) (SocketLogWriter, bool) {
+ endpoint := ""
+ protocol := "udp"
+
+ // Parse properties
+ for _, prop := range props {
+ switch prop.Name {
+ case "endpoint":
+ endpoint = strings.Trim(prop.Value, " \r\n")
+ case "protocol":
+ protocol = strings.Trim(prop.Value, " \r\n")
+ default:
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename)
+ }
+ }
+
+ // Check properties
+ if len(endpoint) == 0 {
+ fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename)
+ return nil, false
+ }
+
+ // If it's disabled, we're just checking syntax
+ if !enabled {
+ return nil, true
+ }
+
+ return NewSocketLogWriter(protocol, endpoint), true
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go
new file mode 100644
index 00000000..698dd332
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "time"
+)
+
+import l4g "code.google.com/p/log4go"
+
+func main() {
+ log := l4g.NewLogger()
+ defer log.Close()
+ log.AddFilter("stdout", l4g.DEBUG, l4g.NewConsoleLogWriter())
+ log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go
new file mode 100644
index 00000000..efd596aa
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "time"
+)
+
+import l4g "code.google.com/p/log4go"
+
+const (
+ filename = "flw.log"
+)
+
+func main() {
+ // Get a new logger instance
+ log := l4g.NewLogger()
+
+ // Create a default logger that is logging messages of FINE or higher
+ log.AddFilter("file", l4g.FINE, l4g.NewFileLogWriter(filename, false))
+ log.Close()
+
+ /* Can also specify manually via the following: (these are the defaults) */
+ flw := l4g.NewFileLogWriter(filename, false)
+ flw.SetFormat("[%D %T] [%L] (%S) %M")
+ flw.SetRotate(false)
+ flw.SetRotateSize(0)
+ flw.SetRotateLines(0)
+ flw.SetRotateDaily(false)
+ log.AddFilter("file", l4g.FINE, flw)
+
+ // Log some experimental messages
+ log.Finest("Everything is created now (notice that I will not be printing to the file)")
+ log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
+ log.Critical("Time to close out!")
+
+ // Close the log
+ log.Close()
+
+ // Print what was logged to the file (yes, I know I'm skipping error checking)
+ fd, _ := os.Open(filename)
+ in := bufio.NewReader(fd)
+ fmt.Print("Messages logged to file were: (line numbers not included)\n")
+ for lineno := 1; ; lineno++ {
+ line, err := in.ReadString('\n')
+ if err == io.EOF {
+ break
+ }
+ fmt.Printf("%3d:\t%s", lineno, line)
+ }
+ fd.Close()
+
+ // Remove the file so it's not lying around
+ os.Remove(filename)
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go
new file mode 100644
index 00000000..83c80ad1
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "net"
+ "os"
+)
+
+var (
+ port = flag.String("p", "12124", "Port number to listen on")
+)
+
+func e(err error) {
+ if err != nil {
+ fmt.Printf("Erroring out: %s\n", err)
+ os.Exit(1)
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ // Bind to the port
+ bind, err := net.ResolveUDPAddr("0.0.0.0:" + *port)
+ e(err)
+
+ // Create listener
+ listener, err := net.ListenUDP("udp", bind)
+ e(err)
+
+ fmt.Printf("Listening to port %s...\n", *port)
+ for {
+ // read into a new buffer
+ buffer := make([]byte, 1024)
+ _, _, err := listener.ReadFrom(buffer)
+ e(err)
+
+ // log to standard output
+ fmt.Println(string(buffer))
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go
new file mode 100644
index 00000000..400b698c
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "time"
+)
+
+import l4g "code.google.com/p/log4go"
+
+func main() {
+ log := l4g.NewLogger()
+ log.AddFilter("network", l4g.FINEST, l4g.NewSocketLogWriter("udp", "192.168.1.255:12124"))
+
+ // Run `nc -u -l -p 12124` or similar before you run this to see the following message
+ log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02"))
+
+ // This makes sure the output stream buffer is written
+ log.Close()
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go
new file mode 100644
index 00000000..164c2add
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go
@@ -0,0 +1,13 @@
+package main
+
+import l4g "code.google.com/p/log4go"
+
+func main() {
+ // Load the configuration (isn't this easy?)
+ l4g.LoadConfiguration("example.xml")
+
+ // And now we're ready!
+ l4g.Finest("This will only go to those of you really cool UDP kids! If you change enabled=true.")
+ l4g.Debug("Oh no! %d + %d = %d!", 2, 2, 2+2)
+ l4g.Info("About that time, eh chaps?")
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/filelog.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/filelog.go
new file mode 100644
index 00000000..ee0ab0c0
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/filelog.go
@@ -0,0 +1,264 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+package log4go
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+// This log writer sends output to a file
+type FileLogWriter struct {
+ rec chan *LogRecord
+ rot chan bool
+
+ // The opened file
+ filename string
+ file *os.File
+
+ // The logging format
+ format string
+
+ // File header/trailer
+ header, trailer string
+
+ // Rotate at linecount
+ maxlines int
+ maxlines_curlines int
+
+ // Rotate at size
+ maxsize int
+ maxsize_cursize int
+
+ // Rotate daily
+ daily bool
+ daily_opendate int
+
+ // Keep old logfiles (.001, .002, etc)
+ rotate bool
+ maxbackup int
+}
+
+// This is the FileLogWriter's output method
+func (w *FileLogWriter) LogWrite(rec *LogRecord) {
+ w.rec <- rec
+}
+
+func (w *FileLogWriter) Close() {
+ close(w.rec)
+ w.file.Sync()
+}
+
+// NewFileLogWriter creates a new LogWriter which writes to the given file and
+// has rotation enabled if rotate is true.
+//
+// If rotate is true, any time a new log file is opened, the old one is renamed
+// with a .### extension to preserve it. The various Set* methods can be used
+// to configure log rotation based on lines, size, and daily.
+//
+// The standard log-line format is:
+// [%D %T] [%L] (%S) %M
+func NewFileLogWriter(fname string, rotate bool) *FileLogWriter {
+ w := &FileLogWriter{
+ rec: make(chan *LogRecord, LogBufferLength),
+ rot: make(chan bool),
+ filename: fname,
+ format: "[%D %T] [%L] (%S) %M",
+ rotate: rotate,
+ maxbackup: 999,
+ }
+
+ // open the file for the first time
+ if err := w.intRotate(); err != nil {
+ fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
+ return nil
+ }
+
+ go func() {
+ defer func() {
+ if w.file != nil {
+ fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
+ w.file.Close()
+ }
+ }()
+
+ for {
+ select {
+ case <-w.rot:
+ if err := w.intRotate(); err != nil {
+ fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
+ return
+ }
+ case rec, ok := <-w.rec:
+ if !ok {
+ return
+ }
+ now := time.Now()
+ if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
+ (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
+ (w.daily && now.Day() != w.daily_opendate) {
+ if err := w.intRotate(); err != nil {
+ fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
+ return
+ }
+ }
+
+ // Perform the write
+ n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
+ return
+ }
+
+ // Update the counts
+ w.maxlines_curlines++
+ w.maxsize_cursize += n
+ }
+ }
+ }()
+
+ return w
+}
+
+// Request that the logs rotate
+func (w *FileLogWriter) Rotate() {
+ w.rot <- true
+}
+
+// If this is called in a threaded context, it MUST be synchronized
+func (w *FileLogWriter) intRotate() error {
+ // Close any log file that may be open
+ if w.file != nil {
+ fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
+ w.file.Close()
+ }
+
+ // If we are keeping log files, move it to the next available number
+ if w.rotate {
+ _, err := os.Lstat(w.filename)
+ if err == nil { // file exists
+ // Find the next available number
+ num := 1
+ fname := ""
+ if w.daily && time.Now().Day() != w.daily_opendate {
+ yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
+
+ for ; err == nil && num <= 999; num++ {
+ fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num)
+ _, err = os.Lstat(fname)
+ }
+ // return error if the last file checked still existed
+ if err == nil {
+ return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
+ }
+ } else {
+ num = w.maxbackup - 1
+ for ; num >= 1; num-- {
+ fname = w.filename + fmt.Sprintf(".%d", num)
+ nfname := w.filename + fmt.Sprintf(".%d", num+1)
+ _, err = os.Lstat(fname)
+ if err == nil {
+ os.Rename(fname, nfname)
+ }
+ }
+ }
+
+ w.file.Close()
+ // Rename the file to its newfound home
+ err = os.Rename(w.filename, fname)
+ if err != nil {
+ return fmt.Errorf("Rotate: %s\n", err)
+ }
+ }
+ }
+
+ // Open the log file
+ fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
+ if err != nil {
+ return err
+ }
+ w.file = fd
+
+ now := time.Now()
+ fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now}))
+
+ // Set the daily open date to the current date
+ w.daily_opendate = now.Day()
+
+ // initialize rotation values
+ w.maxlines_curlines = 0
+ w.maxsize_cursize = 0
+
+ return nil
+}
+
+// Set the logging format (chainable). Must be called before the first log
+// message is written.
+func (w *FileLogWriter) SetFormat(format string) *FileLogWriter {
+ w.format = format
+ return w
+}
+
+// Set the logfile header and footer (chainable). Must be called before the first log
+// message is written. These are formatted similar to the FormatLogRecord (e.g.
+// you can use %D and %T in your header/footer for date and time).
+func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter {
+ w.header, w.trailer = head, foot
+ if w.maxlines_curlines == 0 {
+ fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()}))
+ }
+ return w
+}
+
+// Set rotate at linecount (chainable). Must be called before the first log
+// message is written.
+func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter {
+ //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines)
+ w.maxlines = maxlines
+ return w
+}
+
+// Set rotate at size (chainable). Must be called before the first log message
+// is written.
+func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter {
+ //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize)
+ w.maxsize = maxsize
+ return w
+}
+
+// Set rotate daily (chainable). Must be called before the first log message is
+// written.
+func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
+ //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily)
+ w.daily = daily
+ return w
+}
+
+// Set max backup files. Must be called before the first log message
+// is written.
+func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter {
+ w.maxbackup = maxbackup
+ return w
+}
+
+// SetRotate changes whether or not the old logs are kept. (chainable) Must be
+// called before the first log message is written. If rotate is false, the
+// files are overwritten; otherwise, they are rotated to another file before the
+// new log is opened.
+func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter {
+ //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate)
+ w.rotate = rotate
+ return w
+}
+
+// NewXMLLogWriter is a utility method for creating a FileLogWriter set up to
+// output XML record log messages instead of line-based ones.
+func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter {
+ return NewFileLogWriter(fname, rotate).SetFormat(
+ `
+ %D %T
+
+ %M
+ `).SetHeadFoot("", "")
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/log4go.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/log4go.go
new file mode 100644
index 00000000..822e890c
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/log4go.go
@@ -0,0 +1,484 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+// Package log4go provides level-based and highly configurable logging.
+//
+// Enhanced Logging
+//
+// This is inspired by the logging functionality in Java. Essentially, you create a Logger
+// object and create output filters for it. You can send whatever you want to the Logger,
+// and it will filter that based on your settings and send it to the outputs. This way, you
+// can put as much debug code in your program as you want, and when you're done you can filter
+// out the mundane messages so only the important ones show up.
+//
+// Utility functions are provided to make life easier. Here is some example code to get started:
+//
+// log := log4go.NewLogger()
+// log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter())
+// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true))
+// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
+//
+// The first two lines can be combined with the utility NewDefaultLogger:
+//
+// log := log4go.NewDefaultLogger(log4go.DEBUG)
+// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true))
+// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
+//
+// Usage notes:
+// - The ConsoleLogWriter does not display the source of the message to standard
+// output, but the FileLogWriter does.
+// - The utility functions (Info, Debug, Warn, etc) derive their source from the
+// calling function, and this incurs extra overhead.
+//
+// Changes from 2.0:
+// - The external interface has remained mostly stable, but a lot of the
+// internals have been changed, so if you depended on any of this or created
+// your own LogWriter, then you will probably have to update your code. In
+// particular, Logger is now a map and ConsoleLogWriter is now a channel
+// behind-the-scenes, and the LogWrite method no longer has return values.
+//
+// Future work: (please let me know if you think I should work on any of these particularly)
+// - Log file rotation
+// - Logging configuration files ala log4j
+// - Have the ability to remove filters?
+// - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows
+// for another method of logging
+// - Add an XML filter type
+package log4go
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+)
+
+// Version information
+const (
+ L4G_VERSION = "log4go-v3.0.1"
+ L4G_MAJOR = 3
+ L4G_MINOR = 0
+ L4G_BUILD = 1
+)
+
+/****** Constants ******/
+
+// These are the integer logging levels used by the logger
+type Level int
+
+const (
+ FINEST Level = iota
+ FINE
+ DEBUG
+ TRACE
+ INFO
+ WARNING
+ ERROR
+ CRITICAL
+)
+
+// Logging level strings
+var (
+ levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"}
+)
+
+func (l Level) String() string {
+ if l < 0 || int(l) > len(levelStrings) {
+ return "UNKNOWN"
+ }
+ return levelStrings[int(l)]
+}
+
+/****** Variables ******/
+var (
+ // LogBufferLength specifies how many log messages a particular log4go
+ // logger can buffer at a time before writing them.
+ LogBufferLength = 32
+)
+
+/****** LogRecord ******/
+
+// A LogRecord contains all of the pertinent information for each message
+type LogRecord struct {
+ Level Level // The log level
+ Created time.Time // The time at which the log message was created (nanoseconds)
+ Source string // The message source
+ Message string // The log message
+}
+
+/****** LogWriter ******/
+
+// This is an interface for anything that should be able to write logs
+type LogWriter interface {
+ // This will be called to log a LogRecord message.
+ LogWrite(rec *LogRecord)
+
+ // This should clean up anything lingering about the LogWriter, as it is called before
+ // the LogWriter is removed. LogWrite should not be called after Close.
+ Close()
+}
+
+/****** Logger ******/
+
+// A Filter represents the log level below which no log records are written to
+// the associated LogWriter.
+type Filter struct {
+ Level Level
+ LogWriter
+}
+
+// A Logger represents a collection of Filters through which log messages are
+// written.
+type Logger map[string]*Filter
+
+// Create a new logger.
+//
+// DEPRECATED: Use make(Logger) instead.
+func NewLogger() Logger {
+ os.Stderr.WriteString("warning: use of deprecated NewLogger\n")
+ return make(Logger)
+}
+
+// Create a new logger with a "stdout" filter configured to send log messages at
+// or above lvl to standard output.
+//
+// DEPRECATED: use NewDefaultLogger instead.
+func NewConsoleLogger(lvl Level) Logger {
+ os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n")
+ return Logger{
+ "stdout": &Filter{lvl, NewConsoleLogWriter()},
+ }
+}
+
+// Create a new logger with a "stdout" filter configured to send log messages at
+// or above lvl to standard output.
+func NewDefaultLogger(lvl Level) Logger {
+ return Logger{
+ "stdout": &Filter{lvl, NewConsoleLogWriter()},
+ }
+}
+
+// Closes all log writers in preparation for exiting the program or a
+// reconfiguration of logging. Calling this is not really imperative, unless
+// you want to guarantee that all log messages are written. Close removes
+// all filters (and thus all LogWriters) from the logger.
+func (log Logger) Close() {
+ // Close all open loggers
+ for name, filt := range log {
+ filt.Close()
+ delete(log, name)
+ }
+}
+
+// Add a new LogWriter to the Logger which will only log messages at lvl or
+// higher. This function should not be called from multiple goroutines.
+// Returns the logger for chaining.
+func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger {
+ log[name] = &Filter{lvl, writer}
+ return log
+}
+
+/******* Logging *******/
+// Send a formatted log message internally
+func (log Logger) intLogf(lvl Level, format string, args ...interface{}) {
+ skip := true
+
+ // Determine if any logging will be done
+ for _, filt := range log {
+ if lvl >= filt.Level {
+ skip = false
+ break
+ }
+ }
+ if skip {
+ return
+ }
+
+ // Determine caller func
+ pc, _, lineno, ok := runtime.Caller(2)
+ src := ""
+ if ok {
+ src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
+ }
+
+ msg := format
+ if len(args) > 0 {
+ msg = fmt.Sprintf(format, args...)
+ }
+
+ // Make the log record
+ rec := &LogRecord{
+ Level: lvl,
+ Created: time.Now(),
+ Source: src,
+ Message: msg,
+ }
+
+ // Dispatch the logs
+ for _, filt := range log {
+ if lvl < filt.Level {
+ continue
+ }
+ filt.LogWrite(rec)
+ }
+}
+
+// Send a closure log message internally
+func (log Logger) intLogc(lvl Level, closure func() string) {
+ skip := true
+
+ // Determine if any logging will be done
+ for _, filt := range log {
+ if lvl >= filt.Level {
+ skip = false
+ break
+ }
+ }
+ if skip {
+ return
+ }
+
+ // Determine caller func
+ pc, _, lineno, ok := runtime.Caller(2)
+ src := ""
+ if ok {
+ src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
+ }
+
+ // Make the log record
+ rec := &LogRecord{
+ Level: lvl,
+ Created: time.Now(),
+ Source: src,
+ Message: closure(),
+ }
+
+ // Dispatch the logs
+ for _, filt := range log {
+ if lvl < filt.Level {
+ continue
+ }
+ filt.LogWrite(rec)
+ }
+}
+
+// Send a log message with manual level, source, and message.
+func (log Logger) Log(lvl Level, source, message string) {
+ skip := true
+
+ // Determine if any logging will be done
+ for _, filt := range log {
+ if lvl >= filt.Level {
+ skip = false
+ break
+ }
+ }
+ if skip {
+ return
+ }
+
+ // Make the log record
+ rec := &LogRecord{
+ Level: lvl,
+ Created: time.Now(),
+ Source: source,
+ Message: message,
+ }
+
+ // Dispatch the logs
+ for _, filt := range log {
+ if lvl < filt.Level {
+ continue
+ }
+ filt.LogWrite(rec)
+ }
+}
+
+// Logf logs a formatted log message at the given log level, using the caller as
+// its source.
+func (log Logger) Logf(lvl Level, format string, args ...interface{}) {
+ log.intLogf(lvl, format, args...)
+}
+
+// Logc logs a string returned by the closure at the given log level, using the caller as
+// its source. If no log message would be written, the closure is never called.
+func (log Logger) Logc(lvl Level, closure func() string) {
+ log.intLogc(lvl, closure)
+}
+
+// Finest logs a message at the finest log level.
+// See Debug for an explanation of the arguments.
+func (log Logger) Finest(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = FINEST
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ log.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ log.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Fine logs a message at the fine log level.
+// See Debug for an explanation of the arguments.
+func (log Logger) Fine(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = FINE
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ log.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ log.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Debug is a utility method for debug log messages.
+// The behavior of Debug depends on the first argument:
+// - arg0 is a string
+// When given a string as the first argument, this behaves like Logf but with
+// the DEBUG log level: the first argument is interpreted as a format for the
+// latter arguments.
+// - arg0 is a func()string
+// When given a closure of type func()string, this logs the string returned by
+// the closure iff it will be logged. The closure runs at most one time.
+// - arg0 is interface{}
+// When given anything else, the log message will be each of the arguments
+// formatted with %v and separated by spaces (ala Sprint).
+func (log Logger) Debug(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = DEBUG
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ log.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ log.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Trace logs a message at the trace log level.
+// See Debug for an explanation of the arguments.
+func (log Logger) Trace(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = TRACE
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ log.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ log.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Info logs a message at the info log level.
+// See Debug for an explanation of the arguments.
+func (log Logger) Info(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = INFO
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ log.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ log.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Warn logs a message at the warning log level and returns the formatted error.
+// At the warning level and higher, there is no performance benefit if the
+// message is not actually logged, because all formats are processed and all
+// closures are executed to format the error message.
+// See Debug for further explanation of the arguments.
+func (log Logger) Warn(arg0 interface{}, args ...interface{}) error {
+ const (
+ lvl = WARNING
+ )
+ var msg string
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ msg = fmt.Sprintf(first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ msg = first()
+ default:
+ // Build a format string so that it will be similar to Sprint
+ msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
+ }
+ log.intLogf(lvl, msg)
+ return errors.New(msg)
+}
+
+// Error logs a message at the error log level and returns the formatted error,
+// See Warn for an explanation of the performance and Debug for an explanation
+// of the parameters.
+func (log Logger) Error(arg0 interface{}, args ...interface{}) error {
+ const (
+ lvl = ERROR
+ )
+ var msg string
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ msg = fmt.Sprintf(first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ msg = first()
+ default:
+ // Build a format string so that it will be similar to Sprint
+ msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
+ }
+ log.intLogf(lvl, msg)
+ return errors.New(msg)
+}
+
+// Critical logs a message at the critical log level and returns the formatted error,
+// See Warn for an explanation of the performance and Debug for an explanation
+// of the parameters.
+func (log Logger) Critical(arg0 interface{}, args ...interface{}) error {
+ const (
+ lvl = CRITICAL
+ )
+ var msg string
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ msg = fmt.Sprintf(first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ msg = first()
+ default:
+ // Build a format string so that it will be similar to Sprint
+ msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
+ }
+ log.intLogf(lvl, msg)
+ return errors.New(msg)
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/pattlog.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/pattlog.go
new file mode 100644
index 00000000..82b4e36b
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/pattlog.go
@@ -0,0 +1,126 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+package log4go
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+)
+
+const (
+ FORMAT_DEFAULT = "[%D %T] [%L] (%S) %M"
+ FORMAT_SHORT = "[%t %d] [%L] %M"
+ FORMAT_ABBREV = "[%L] %M"
+)
+
+type formatCacheType struct {
+ LastUpdateSeconds int64
+ shortTime, shortDate string
+ longTime, longDate string
+}
+
+var formatCache = &formatCacheType{}
+
+// Known format codes:
+// %T - Time (15:04:05 MST)
+// %t - Time (15:04)
+// %D - Date (2006/01/02)
+// %d - Date (01/02/06)
+// %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
+// %S - Source
+// %M - Message
+// Ignores unknown formats
+// Recommended: "[%D %T] [%L] (%S) %M"
+func FormatLogRecord(format string, rec *LogRecord) string {
+ if rec == nil {
+ return ""
+ }
+ if len(format) == 0 {
+ return ""
+ }
+
+ out := bytes.NewBuffer(make([]byte, 0, 64))
+ secs := rec.Created.UnixNano() / 1e9
+
+ cache := *formatCache
+ if cache.LastUpdateSeconds != secs {
+ month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year()
+ hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second()
+ zone, _ := rec.Created.Zone()
+ updated := &formatCacheType{
+ LastUpdateSeconds: secs,
+ shortTime: fmt.Sprintf("%02d:%02d", hour, minute),
+ shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100),
+ longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone),
+ longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day),
+ }
+ cache = *updated
+ formatCache = updated
+ }
+
+ // Split the string into pieces by % signs
+ pieces := bytes.Split([]byte(format), []byte{'%'})
+
+ // Iterate over the pieces, replacing known formats
+ for i, piece := range pieces {
+ if i > 0 && len(piece) > 0 {
+ switch piece[0] {
+ case 'T':
+ out.WriteString(cache.longTime)
+ case 't':
+ out.WriteString(cache.shortTime)
+ case 'D':
+ out.WriteString(cache.longDate)
+ case 'd':
+ out.WriteString(cache.shortDate)
+ case 'L':
+ out.WriteString(levelStrings[rec.Level])
+ case 'S':
+ out.WriteString(rec.Source)
+ case 's':
+ slice := strings.Split(rec.Source, "/")
+ out.WriteString(slice[len(slice)-1])
+ case 'M':
+ out.WriteString(rec.Message)
+ }
+ if len(piece) > 1 {
+ out.Write(piece[1:])
+ }
+ } else if len(piece) > 0 {
+ out.Write(piece)
+ }
+ }
+ out.WriteByte('\n')
+
+ return out.String()
+}
+
+// This is the standard writer that prints to standard output.
+type FormatLogWriter chan *LogRecord
+
+// This creates a new FormatLogWriter
+func NewFormatLogWriter(out io.Writer, format string) FormatLogWriter {
+ records := make(FormatLogWriter, LogBufferLength)
+ go records.run(out, format)
+ return records
+}
+
+func (w FormatLogWriter) run(out io.Writer, format string) {
+ for rec := range w {
+ fmt.Fprint(out, FormatLogRecord(format, rec))
+ }
+}
+
+// This is the FormatLogWriter's output method. This will block if the output
+// buffer is full.
+func (w FormatLogWriter) LogWrite(rec *LogRecord) {
+ w <- rec
+}
+
+// Close stops the logger from sending messages to standard output. Attempts to
+// send log messages to this logger after a Close have undefined behavior.
+func (w FormatLogWriter) Close() {
+ close(w)
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/socklog.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/socklog.go
new file mode 100644
index 00000000..1d224a99
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/socklog.go
@@ -0,0 +1,57 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+package log4go
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "os"
+)
+
+// This log writer sends output to a socket
+type SocketLogWriter chan *LogRecord
+
+// This is the SocketLogWriter's output method
+func (w SocketLogWriter) LogWrite(rec *LogRecord) {
+ w <- rec
+}
+
+func (w SocketLogWriter) Close() {
+ close(w)
+}
+
+func NewSocketLogWriter(proto, hostport string) SocketLogWriter {
+ sock, err := net.Dial(proto, hostport)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "NewSocketLogWriter(%q): %s\n", hostport, err)
+ return nil
+ }
+
+ w := SocketLogWriter(make(chan *LogRecord, LogBufferLength))
+
+ go func() {
+ defer func() {
+ if sock != nil && proto == "tcp" {
+ sock.Close()
+ }
+ }()
+
+ for rec := range w {
+ // Marshall into JSON
+ js, err := json.Marshal(rec)
+ if err != nil {
+ fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err)
+ return
+ }
+
+ _, err = sock.Write(js)
+ if err != nil {
+ fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err)
+ return
+ }
+ }
+ }()
+
+ return w
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/termlog.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/termlog.go
new file mode 100644
index 00000000..8a941e26
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/termlog.go
@@ -0,0 +1,49 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+package log4go
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "time"
+)
+
+var stdout io.Writer = os.Stdout
+
+// This is the standard writer that prints to standard output.
+type ConsoleLogWriter struct {
+ format string
+ w chan *LogRecord
+}
+
+// This creates a new ConsoleLogWriter
+func NewConsoleLogWriter() *ConsoleLogWriter {
+ consoleWriter := &ConsoleLogWriter{
+ format: "[%T %D] [%L] (%S) %M",
+ w: make(chan *LogRecord, LogBufferLength),
+ }
+ go consoleWriter.run(stdout)
+ return consoleWriter
+}
+func (c *ConsoleLogWriter) SetFormat(format string) {
+ c.format = format
+}
+func (c *ConsoleLogWriter) run(out io.Writer) {
+ for rec := range c.w {
+ fmt.Fprint(out, FormatLogRecord(c.format, rec))
+ }
+}
+
+// This is the ConsoleLogWriter's output method. This will block if the output
+// buffer is full.
+func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) {
+ c.w <- rec
+}
+
+// Close stops the logger from sending messages to standard output. Attempts to
+// send log messages to this logger after a Close have undefined behavior.
+func (c *ConsoleLogWriter) Close() {
+ close(c.w)
+ time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/wrapper.go b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/wrapper.go
new file mode 100644
index 00000000..2ae222b0
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/alecthomas/log4go/wrapper.go
@@ -0,0 +1,278 @@
+// Copyright (C) 2010, Kyle Lemons . All rights reserved.
+
+package log4go
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+)
+
+var (
+ Global Logger
+)
+
+func init() {
+ Global = NewDefaultLogger(DEBUG)
+}
+
+// Wrapper for (*Logger).LoadConfiguration
+func LoadConfiguration(filename string) {
+ Global.LoadConfiguration(filename)
+}
+
+// Wrapper for (*Logger).AddFilter
+func AddFilter(name string, lvl Level, writer LogWriter) {
+ Global.AddFilter(name, lvl, writer)
+}
+
+// Wrapper for (*Logger).Close (closes and removes all logwriters)
+func Close() {
+ Global.Close()
+}
+
+func Crash(args ...interface{}) {
+ if len(args) > 0 {
+ Global.intLogf(CRITICAL, strings.Repeat(" %v", len(args))[1:], args...)
+ }
+ panic(args)
+}
+
+// Logs the given message and crashes the program
+func Crashf(format string, args ...interface{}) {
+ Global.intLogf(CRITICAL, format, args...)
+ Global.Close() // so that hopefully the messages get logged
+ panic(fmt.Sprintf(format, args...))
+}
+
+// Compatibility with `log`
+func Exit(args ...interface{}) {
+ if len(args) > 0 {
+ Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...)
+ }
+ Global.Close() // so that hopefully the messages get logged
+ os.Exit(0)
+}
+
+// Compatibility with `log`
+func Exitf(format string, args ...interface{}) {
+ Global.intLogf(ERROR, format, args...)
+ Global.Close() // so that hopefully the messages get logged
+ os.Exit(0)
+}
+
+// Compatibility with `log`
+func Stderr(args ...interface{}) {
+ if len(args) > 0 {
+ Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...)
+ }
+}
+
+// Compatibility with `log`
+func Stderrf(format string, args ...interface{}) {
+ Global.intLogf(ERROR, format, args...)
+}
+
+// Compatibility with `log`
+func Stdout(args ...interface{}) {
+ if len(args) > 0 {
+ Global.intLogf(INFO, strings.Repeat(" %v", len(args))[1:], args...)
+ }
+}
+
+// Compatibility with `log`
+func Stdoutf(format string, args ...interface{}) {
+ Global.intLogf(INFO, format, args...)
+}
+
+// Send a log message manually
+// Wrapper for (*Logger).Log
+func Log(lvl Level, source, message string) {
+ Global.Log(lvl, source, message)
+}
+
+// Send a formatted log message easily
+// Wrapper for (*Logger).Logf
+func Logf(lvl Level, format string, args ...interface{}) {
+ Global.intLogf(lvl, format, args...)
+}
+
+// Send a closure log message
+// Wrapper for (*Logger).Logc
+func Logc(lvl Level, closure func() string) {
+ Global.intLogc(lvl, closure)
+}
+
+// Utility for finest log messages (see Debug() for parameter explanation)
+// Wrapper for (*Logger).Finest
+func Finest(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = FINEST
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ Global.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Utility for fine log messages (see Debug() for parameter explanation)
+// Wrapper for (*Logger).Fine
+func Fine(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = FINE
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ Global.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Utility for debug log messages
+// When given a string as the first argument, this behaves like Logf but with the DEBUG log level (e.g. the first argument is interpreted as a format for the latter arguments)
+// When given a closure of type func()string, this logs the string returned by the closure iff it will be logged. The closure runs at most one time.
+// When given anything else, the log message will be each of the arguments formatted with %v and separated by spaces (ala Sprint).
+// Wrapper for (*Logger).Debug
+func Debug(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = DEBUG
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ Global.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Utility for trace log messages (see Debug() for parameter explanation)
+// Wrapper for (*Logger).Trace
+func Trace(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = TRACE
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ Global.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Utility for info log messages (see Debug() for parameter explanation)
+// Wrapper for (*Logger).Info
+func Info(arg0 interface{}, args ...interface{}) {
+ const (
+ lvl = INFO
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ case func() string:
+ // Log the closure (no other arguments used)
+ Global.intLogc(lvl, first)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
+ }
+}
+
+// Utility for warn log messages (returns an error for easy function returns) (see Debug() for parameter explanation)
+// These functions will execute a closure exactly once, to build the error message for the return
+// Wrapper for (*Logger).Warn
+func Warn(arg0 interface{}, args ...interface{}) error {
+ const (
+ lvl = WARNING
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ return errors.New(fmt.Sprintf(first, args...))
+ case func() string:
+ // Log the closure (no other arguments used)
+ str := first()
+ Global.intLogf(lvl, "%s", str)
+ return errors.New(str)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
+ return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...))
+ }
+ return nil
+}
+
+// Utility for error log messages (returns an error for easy function returns) (see Debug() for parameter explanation)
+// These functions will execute a closure exactly once, to build the error message for the return
+// Wrapper for (*Logger).Error
+func Error(arg0 interface{}, args ...interface{}) error {
+ const (
+ lvl = ERROR
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ return errors.New(fmt.Sprintf(first, args...))
+ case func() string:
+ // Log the closure (no other arguments used)
+ str := first()
+ Global.intLogf(lvl, "%s", str)
+ return errors.New(str)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
+ return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...))
+ }
+ return nil
+}
+
+// Utility for critical log messages (returns an error for easy function returns) (see Debug() for parameter explanation)
+// These functions will execute a closure exactly once, to build the error message for the return
+// Wrapper for (*Logger).Critical
+func Critical(arg0 interface{}, args ...interface{}) error {
+ const (
+ lvl = CRITICAL
+ )
+ switch first := arg0.(type) {
+ case string:
+ // Use the string as a format string
+ Global.intLogf(lvl, first, args...)
+ return errors.New(fmt.Sprintf(first, args...))
+ case func() string:
+ // Log the closure (no other arguments used)
+ str := first()
+ Global.intLogf(lvl, "%s", str)
+ return errors.New(str)
+ default:
+ // Build a format string so that it will be similar to Sprint
+ Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
+ return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...))
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/LICENSE.txt b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/LICENSE.txt
new file mode 100644
index 00000000..e5875711
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/LICENSE.txt
@@ -0,0 +1,897 @@
+Mattermost Licensing
+
+SOFTWARE LICENSING
+
+You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
+
+- See MIT-COMPILED-LICENSE.md included in compiled versions for details
+
+You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:
+
+1. Under the Free Software Foundation’s GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
+2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
+
+You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
+webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
+
+We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
+link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
+(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
+a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license.
+
+MATTERMOST TRADEMARK GUIDELINES
+
+Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organization’s Trademark
+Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
+you have about using these trademarks, please email trademark@mattermost.com
+
+------------------------------------------------------------------------------------------------------------------------------
+
+ 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.
+
+------------------------------------------------------------------------------
+
+The software is released under the terms of the GNU Affero General Public
+License, version 3.
+
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/client.go
new file mode 100644
index 00000000..78d93287
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/client.go
@@ -0,0 +1,420 @@
+// Copyright 2013 The Gorilla WebSocket 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 websocket
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "encoding/base64"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// ErrBadHandshake is returned when the server response to opening handshake is
+// invalid.
+var ErrBadHandshake = errors.New("websocket: bad handshake")
+
+var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
+
+// NewClient creates a new client connection using the given net connection.
+// The URL u specifies the host and request URI. Use requestHeader to specify
+// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
+// (Cookie). Use the response.Header to get the selected subprotocol
+// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
+//
+// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
+// non-nil *http.Response so that callers can handle redirects, authentication,
+// etc.
+//
+// Deprecated: Use Dialer instead.
+func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
+ d := Dialer{
+ ReadBufferSize: readBufSize,
+ WriteBufferSize: writeBufSize,
+ NetDial: func(net, addr string) (net.Conn, error) {
+ return netConn, nil
+ },
+ }
+ return d.Dial(u.String(), requestHeader)
+}
+
+// A Dialer contains options for connecting to WebSocket server.
+type Dialer struct {
+ // NetDial specifies the dial function for creating TCP connections. If
+ // NetDial is nil, net.Dial is used.
+ NetDial func(network, addr string) (net.Conn, error)
+
+ // Proxy specifies a function to return a proxy for a given
+ // Request. If the function returns a non-nil error, the
+ // request is aborted with the provided error.
+ // If Proxy is nil or returns a nil *URL, no proxy is used.
+ Proxy func(*http.Request) (*url.URL, error)
+
+ // TLSClientConfig specifies the TLS configuration to use with tls.Client.
+ // If nil, the default configuration is used.
+ TLSClientConfig *tls.Config
+
+ // HandshakeTimeout specifies the duration for the handshake to complete.
+ HandshakeTimeout time.Duration
+
+ // Input and output buffer sizes. If the buffer size is zero, then a
+ // default value of 4096 is used.
+ ReadBufferSize, WriteBufferSize int
+
+ // Subprotocols specifies the client's requested subprotocols.
+ Subprotocols []string
+
+ // EnableCompression specifies if the client should attempt to negotiate
+ // per message compression (RFC 7692). Setting this value to true does not
+ // guarantee that compression will be supported. Currently only "no context
+ // takeover" modes are supported.
+ EnableCompression bool
+
+ // Jar specifies the cookie jar.
+ // If Jar is nil, cookies are not sent in requests and ignored
+ // in responses.
+ Jar http.CookieJar
+}
+
+var errMalformedURL = errors.New("malformed ws or wss URL")
+
+// parseURL parses the URL.
+//
+// This function is a replacement for the standard library url.Parse function.
+// In Go 1.4 and earlier, url.Parse loses information from the path.
+func parseURL(s string) (*url.URL, error) {
+ // From the RFC:
+ //
+ // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
+ // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
+ var u url.URL
+ switch {
+ case strings.HasPrefix(s, "ws://"):
+ u.Scheme = "ws"
+ s = s[len("ws://"):]
+ case strings.HasPrefix(s, "wss://"):
+ u.Scheme = "wss"
+ s = s[len("wss://"):]
+ default:
+ return nil, errMalformedURL
+ }
+
+ if i := strings.Index(s, "?"); i >= 0 {
+ u.RawQuery = s[i+1:]
+ s = s[:i]
+ }
+
+ if i := strings.Index(s, "/"); i >= 0 {
+ u.Opaque = s[i:]
+ s = s[:i]
+ } else {
+ u.Opaque = "/"
+ }
+
+ u.Host = s
+
+ if strings.Contains(u.Host, "@") {
+ // Don't bother parsing user information because user information is
+ // not allowed in websocket URIs.
+ return nil, errMalformedURL
+ }
+
+ return &u, nil
+}
+
+func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
+ hostPort = u.Host
+ hostNoPort = u.Host
+ if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
+ hostNoPort = hostNoPort[:i]
+ } else {
+ switch u.Scheme {
+ case "wss":
+ hostPort += ":443"
+ case "https":
+ hostPort += ":443"
+ default:
+ hostPort += ":80"
+ }
+ }
+ return hostPort, hostNoPort
+}
+
+// DefaultDialer is a dialer with all fields set to the default zero values.
+var DefaultDialer = &Dialer{
+ Proxy: http.ProxyFromEnvironment,
+}
+
+// Dial creates a new client connection. Use requestHeader to specify the
+// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
+// Use the response.Header to get the selected subprotocol
+// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
+//
+// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
+// non-nil *http.Response so that callers can handle redirects, authentication,
+// etcetera. The response body may not contain the entire response and does not
+// need to be closed by the application.
+func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
+
+ if d == nil {
+ d = &Dialer{
+ Proxy: http.ProxyFromEnvironment,
+ }
+ }
+
+ challengeKey, err := generateChallengeKey()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ u, err := parseURL(urlStr)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ switch u.Scheme {
+ case "ws":
+ u.Scheme = "http"
+ case "wss":
+ u.Scheme = "https"
+ default:
+ return nil, nil, errMalformedURL
+ }
+
+ if u.User != nil {
+ // User name and password are not allowed in websocket URIs.
+ return nil, nil, errMalformedURL
+ }
+
+ req := &http.Request{
+ Method: "GET",
+ URL: u,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Header: make(http.Header),
+ Host: u.Host,
+ }
+
+ // Set the cookies present in the cookie jar of the dialer
+ if d.Jar != nil {
+ for _, cookie := range d.Jar.Cookies(u) {
+ req.AddCookie(cookie)
+ }
+ }
+
+ // Set the request headers using the capitalization for names and values in
+ // RFC examples. Although the capitalization shouldn't matter, there are
+ // servers that depend on it. The Header.Set method is not used because the
+ // method canonicalizes the header names.
+ req.Header["Upgrade"] = []string{"websocket"}
+ req.Header["Connection"] = []string{"Upgrade"}
+ req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
+ req.Header["Sec-WebSocket-Version"] = []string{"13"}
+ if len(d.Subprotocols) > 0 {
+ req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
+ }
+ for k, vs := range requestHeader {
+ switch {
+ case k == "Host":
+ if len(vs) > 0 {
+ req.Host = vs[0]
+ }
+ case k == "Upgrade" ||
+ k == "Connection" ||
+ k == "Sec-Websocket-Key" ||
+ k == "Sec-Websocket-Version" ||
+ k == "Sec-Websocket-Extensions" ||
+ (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
+ return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
+ default:
+ req.Header[k] = vs
+ }
+ }
+
+ if d.EnableCompression {
+ req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
+ }
+
+ hostPort, hostNoPort := hostPortNoPort(u)
+
+ var proxyURL *url.URL
+ // Check wether the proxy method has been configured
+ if d.Proxy != nil {
+ proxyURL, err = d.Proxy(req)
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var targetHostPort string
+ if proxyURL != nil {
+ targetHostPort, _ = hostPortNoPort(proxyURL)
+ } else {
+ targetHostPort = hostPort
+ }
+
+ var deadline time.Time
+ if d.HandshakeTimeout != 0 {
+ deadline = time.Now().Add(d.HandshakeTimeout)
+ }
+
+ netDial := d.NetDial
+ if netDial == nil {
+ netDialer := &net.Dialer{Deadline: deadline}
+ netDial = netDialer.Dial
+ }
+
+ netConn, err := netDial("tcp", targetHostPort)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ defer func() {
+ if netConn != nil {
+ netConn.Close()
+ }
+ }()
+
+ if err := netConn.SetDeadline(deadline); err != nil {
+ return nil, nil, err
+ }
+
+ if proxyURL != nil {
+ connectHeader := make(http.Header)
+ if user := proxyURL.User; user != nil {
+ proxyUser := user.Username()
+ if proxyPassword, passwordSet := user.Password(); passwordSet {
+ credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
+ connectHeader.Set("Proxy-Authorization", "Basic "+credential)
+ }
+ }
+ connectReq := &http.Request{
+ Method: "CONNECT",
+ URL: &url.URL{Opaque: hostPort},
+ Host: hostPort,
+ Header: connectHeader,
+ }
+
+ connectReq.Write(netConn)
+
+ // Read response.
+ // Okay to use and discard buffered reader here, because
+ // TLS server will not speak until spoken to.
+ br := bufio.NewReader(netConn)
+ resp, err := http.ReadResponse(br, connectReq)
+ if err != nil {
+ return nil, nil, err
+ }
+ if resp.StatusCode != 200 {
+ f := strings.SplitN(resp.Status, " ", 2)
+ return nil, nil, errors.New(f[1])
+ }
+ }
+
+ if u.Scheme == "https" {
+ cfg := cloneTLSConfig(d.TLSClientConfig)
+ if cfg.ServerName == "" {
+ cfg.ServerName = hostNoPort
+ }
+ tlsConn := tls.Client(netConn, cfg)
+ netConn = tlsConn
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, nil, err
+ }
+ if !cfg.InsecureSkipVerify {
+ if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
+ return nil, nil, err
+ }
+ }
+ }
+
+ conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
+
+ if err := req.Write(netConn); err != nil {
+ return nil, nil, err
+ }
+
+ resp, err := http.ReadResponse(conn.br, req)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if d.Jar != nil {
+ if rc := resp.Cookies(); len(rc) > 0 {
+ d.Jar.SetCookies(u, rc)
+ }
+ }
+
+ if resp.StatusCode != 101 ||
+ !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
+ !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
+ resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
+ // Before closing the network connection on return from this
+ // function, slurp up some of the response to aid application
+ // debugging.
+ buf := make([]byte, 1024)
+ n, _ := io.ReadFull(resp.Body, buf)
+ resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
+ return nil, resp, ErrBadHandshake
+ }
+
+ for _, ext := range parseExtensions(req.Header) {
+ if ext[""] != "permessage-deflate" {
+ continue
+ }
+ _, snct := ext["server_no_context_takeover"]
+ _, cnct := ext["client_no_context_takeover"]
+ if !snct || !cnct {
+ return nil, resp, errInvalidCompression
+ }
+ conn.newCompressionWriter = compressNoContextTakeover
+ conn.newDecompressionReader = decompressNoContextTakeover
+ break
+ }
+
+ resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
+ conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
+
+ netConn.SetDeadline(time.Time{})
+ netConn = nil // to avoid close in defer.
+ return conn, resp, nil
+}
+
+// cloneTLSConfig clones all public fields except the fields
+// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
+// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
+// config in active use.
+func cloneTLSConfig(cfg *tls.Config) *tls.Config {
+ if cfg == nil {
+ return &tls.Config{}
+ }
+ return &tls.Config{
+ Rand: cfg.Rand,
+ Time: cfg.Time,
+ Certificates: cfg.Certificates,
+ NameToCertificate: cfg.NameToCertificate,
+ GetCertificate: cfg.GetCertificate,
+ RootCAs: cfg.RootCAs,
+ NextProtos: cfg.NextProtos,
+ ServerName: cfg.ServerName,
+ ClientAuth: cfg.ClientAuth,
+ ClientCAs: cfg.ClientCAs,
+ InsecureSkipVerify: cfg.InsecureSkipVerify,
+ CipherSuites: cfg.CipherSuites,
+ PreferServerCipherSuites: cfg.PreferServerCipherSuites,
+ ClientSessionCache: cfg.ClientSessionCache,
+ MinVersion: cfg.MinVersion,
+ MaxVersion: cfg.MaxVersion,
+ CurvePreferences: cfg.CurvePreferences,
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/compression.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/compression.go
new file mode 100644
index 00000000..72c166b2
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/compression.go
@@ -0,0 +1,105 @@
+// Copyright 2017 The Gorilla WebSocket 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 websocket
+
+import (
+ "compress/flate"
+ "errors"
+ "io"
+ "strings"
+ "sync"
+)
+
+var (
+ flateWriterPool = sync.Pool{}
+)
+
+func decompressNoContextTakeover(r io.Reader) io.Reader {
+ const tail =
+ // Add four bytes as specified in RFC
+ "\x00\x00\xff\xff" +
+ // Add final block to squelch unexpected EOF error from flate reader.
+ "\x01\x00\x00\xff\xff"
+ return flate.NewReader(io.MultiReader(r, strings.NewReader(tail)))
+}
+
+func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) {
+ tw := &truncWriter{w: w}
+ i := flateWriterPool.Get()
+ var fw *flate.Writer
+ var err error
+ if i == nil {
+ fw, err = flate.NewWriter(tw, 3)
+ } else {
+ fw = i.(*flate.Writer)
+ fw.Reset(tw)
+ }
+ return &flateWrapper{fw: fw, tw: tw}, err
+}
+
+// truncWriter is an io.Writer that writes all but the last four bytes of the
+// stream to another io.Writer.
+type truncWriter struct {
+ w io.WriteCloser
+ n int
+ p [4]byte
+}
+
+func (w *truncWriter) Write(p []byte) (int, error) {
+ n := 0
+
+ // fill buffer first for simplicity.
+ if w.n < len(w.p) {
+ n = copy(w.p[w.n:], p)
+ p = p[n:]
+ w.n += n
+ if len(p) == 0 {
+ return n, nil
+ }
+ }
+
+ m := len(p)
+ if m > len(w.p) {
+ m = len(w.p)
+ }
+
+ if nn, err := w.w.Write(w.p[:m]); err != nil {
+ return n + nn, err
+ }
+
+ copy(w.p[:], w.p[m:])
+ copy(w.p[len(w.p)-m:], p[len(p)-m:])
+ nn, err := w.w.Write(p[:len(p)-m])
+ return n + nn, err
+}
+
+type flateWrapper struct {
+ fw *flate.Writer
+ tw *truncWriter
+}
+
+func (w *flateWrapper) Write(p []byte) (int, error) {
+ if w.fw == nil {
+ return 0, errWriteClosed
+ }
+ return w.fw.Write(p)
+}
+
+func (w *flateWrapper) Close() error {
+ if w.fw == nil {
+ return errWriteClosed
+ }
+ err1 := w.fw.Flush()
+ flateWriterPool.Put(w.fw)
+ w.fw = nil
+ if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
+ return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
+ }
+ err2 := w.tw.w.Close()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn.go
new file mode 100644
index 00000000..ce7f0a61
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn.go
@@ -0,0 +1,1043 @@
+// Copyright 2013 The Gorilla WebSocket 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 websocket
+
+import (
+ "bufio"
+ "encoding/binary"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net"
+ "strconv"
+ "sync"
+ "time"
+ "unicode/utf8"
+)
+
+const (
+ // Frame header byte 0 bits from Section 5.2 of RFC 6455
+ finalBit = 1 << 7
+ rsv1Bit = 1 << 6
+ rsv2Bit = 1 << 5
+ rsv3Bit = 1 << 4
+
+ // Frame header byte 1 bits from Section 5.2 of RFC 6455
+ maskBit = 1 << 7
+
+ maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
+ maxControlFramePayloadSize = 125
+
+ writeWait = time.Second
+
+ defaultReadBufferSize = 4096
+ defaultWriteBufferSize = 4096
+
+ continuationFrame = 0
+ noFrame = -1
+)
+
+// Close codes defined in RFC 6455, section 11.7.
+const (
+ CloseNormalClosure = 1000
+ CloseGoingAway = 1001
+ CloseProtocolError = 1002
+ CloseUnsupportedData = 1003
+ CloseNoStatusReceived = 1005
+ CloseAbnormalClosure = 1006
+ CloseInvalidFramePayloadData = 1007
+ ClosePolicyViolation = 1008
+ CloseMessageTooBig = 1009
+ CloseMandatoryExtension = 1010
+ CloseInternalServerErr = 1011
+ CloseServiceRestart = 1012
+ CloseTryAgainLater = 1013
+ CloseTLSHandshake = 1015
+)
+
+// The message types are defined in RFC 6455, section 11.8.
+const (
+ // TextMessage denotes a text data message. The text message payload is
+ // interpreted as UTF-8 encoded text data.
+ TextMessage = 1
+
+ // BinaryMessage denotes a binary data message.
+ BinaryMessage = 2
+
+ // CloseMessage denotes a close control message. The optional message
+ // payload contains a numeric code and text. Use the FormatCloseMessage
+ // function to format a close message payload.
+ CloseMessage = 8
+
+ // PingMessage denotes a ping control message. The optional message payload
+ // is UTF-8 encoded text.
+ PingMessage = 9
+
+ // PongMessage denotes a ping control message. The optional message payload
+ // is UTF-8 encoded text.
+ PongMessage = 10
+)
+
+// ErrCloseSent is returned when the application writes a message to the
+// connection after sending a close message.
+var ErrCloseSent = errors.New("websocket: close sent")
+
+// ErrReadLimit is returned when reading a message that is larger than the
+// read limit set for the connection.
+var ErrReadLimit = errors.New("websocket: read limit exceeded")
+
+// netError satisfies the net Error interface.
+type netError struct {
+ msg string
+ temporary bool
+ timeout bool
+}
+
+func (e *netError) Error() string { return e.msg }
+func (e *netError) Temporary() bool { return e.temporary }
+func (e *netError) Timeout() bool { return e.timeout }
+
+// CloseError represents close frame.
+type CloseError struct {
+
+ // Code is defined in RFC 6455, section 11.7.
+ Code int
+
+ // Text is the optional text payload.
+ Text string
+}
+
+func (e *CloseError) Error() string {
+ s := []byte("websocket: close ")
+ s = strconv.AppendInt(s, int64(e.Code), 10)
+ switch e.Code {
+ case CloseNormalClosure:
+ s = append(s, " (normal)"...)
+ case CloseGoingAway:
+ s = append(s, " (going away)"...)
+ case CloseProtocolError:
+ s = append(s, " (protocol error)"...)
+ case CloseUnsupportedData:
+ s = append(s, " (unsupported data)"...)
+ case CloseNoStatusReceived:
+ s = append(s, " (no status)"...)
+ case CloseAbnormalClosure:
+ s = append(s, " (abnormal closure)"...)
+ case CloseInvalidFramePayloadData:
+ s = append(s, " (invalid payload data)"...)
+ case ClosePolicyViolation:
+ s = append(s, " (policy violation)"...)
+ case CloseMessageTooBig:
+ s = append(s, " (message too big)"...)
+ case CloseMandatoryExtension:
+ s = append(s, " (mandatory extension missing)"...)
+ case CloseInternalServerErr:
+ s = append(s, " (internal server error)"...)
+ case CloseTLSHandshake:
+ s = append(s, " (TLS handshake error)"...)
+ }
+ if e.Text != "" {
+ s = append(s, ": "...)
+ s = append(s, e.Text...)
+ }
+ return string(s)
+}
+
+// IsCloseError returns boolean indicating whether the error is a *CloseError
+// with one of the specified codes.
+func IsCloseError(err error, codes ...int) bool {
+ if e, ok := err.(*CloseError); ok {
+ for _, code := range codes {
+ if e.Code == code {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// IsUnexpectedCloseError returns boolean indicating whether the error is a
+// *CloseError with a code not in the list of expected codes.
+func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
+ if e, ok := err.(*CloseError); ok {
+ for _, code := range expectedCodes {
+ if e.Code == code {
+ return false
+ }
+ }
+ return true
+ }
+ return false
+}
+
+var (
+ errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true}
+ errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}
+ errBadWriteOpCode = errors.New("websocket: bad write message type")
+ errWriteClosed = errors.New("websocket: write closed")
+ errInvalidControlFrame = errors.New("websocket: invalid control frame")
+)
+
+func hideTempErr(err error) error {
+ if e, ok := err.(net.Error); ok && e.Temporary() {
+ err = &netError{msg: e.Error(), timeout: e.Timeout()}
+ }
+ return err
+}
+
+func isControl(frameType int) bool {
+ return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage
+}
+
+func isData(frameType int) bool {
+ return frameType == TextMessage || frameType == BinaryMessage
+}
+
+var validReceivedCloseCodes = map[int]bool{
+ // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
+
+ CloseNormalClosure: true,
+ CloseGoingAway: true,
+ CloseProtocolError: true,
+ CloseUnsupportedData: true,
+ CloseNoStatusReceived: false,
+ CloseAbnormalClosure: false,
+ CloseInvalidFramePayloadData: true,
+ ClosePolicyViolation: true,
+ CloseMessageTooBig: true,
+ CloseMandatoryExtension: true,
+ CloseInternalServerErr: true,
+ CloseServiceRestart: true,
+ CloseTryAgainLater: true,
+ CloseTLSHandshake: false,
+}
+
+func isValidReceivedCloseCode(code int) bool {
+ return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
+}
+
+// The Conn type represents a WebSocket connection.
+type Conn struct {
+ conn net.Conn
+ isServer bool
+ subprotocol string
+
+ // Write fields
+ mu chan bool // used as mutex to protect write to conn
+ writeBuf []byte // frame is constructed in this buffer.
+ writeDeadline time.Time
+ writer io.WriteCloser // the current writer returned to the application
+ isWriting bool // for best-effort concurrent write detection
+
+ writeErrMu sync.Mutex
+ writeErr error
+
+ enableWriteCompression bool
+ newCompressionWriter func(io.WriteCloser) (io.WriteCloser, error)
+
+ // Read fields
+ readErr error
+ br *bufio.Reader
+ readRemaining int64 // bytes remaining in current frame.
+ readFinal bool // true the current message has more frames.
+ readLength int64 // Message size.
+ readLimit int64 // Maximum message size.
+ readMaskPos int
+ readMaskKey [4]byte
+ handlePong func(string) error
+ handlePing func(string) error
+ handleClose func(int, string) error
+ readErrCount int
+ messageReader *messageReader // the current low-level reader
+
+ readDecompress bool // whether last read frame had RSV1 set
+ newDecompressionReader func(io.Reader) io.Reader
+}
+
+func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
+ mu := make(chan bool, 1)
+ mu <- true
+
+ if readBufferSize == 0 {
+ readBufferSize = defaultReadBufferSize
+ }
+ if readBufferSize < maxControlFramePayloadSize {
+ readBufferSize = maxControlFramePayloadSize
+ }
+ if writeBufferSize == 0 {
+ writeBufferSize = defaultWriteBufferSize
+ }
+
+ c := &Conn{
+ isServer: isServer,
+ br: bufio.NewReaderSize(conn, readBufferSize),
+ conn: conn,
+ mu: mu,
+ readFinal: true,
+ writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize),
+ enableWriteCompression: true,
+ }
+ c.SetCloseHandler(nil)
+ c.SetPingHandler(nil)
+ c.SetPongHandler(nil)
+ return c
+}
+
+// Subprotocol returns the negotiated protocol for the connection.
+func (c *Conn) Subprotocol() string {
+ return c.subprotocol
+}
+
+// Close closes the underlying network connection without sending or waiting for a close frame.
+func (c *Conn) Close() error {
+ return c.conn.Close()
+}
+
+// LocalAddr returns the local network address.
+func (c *Conn) LocalAddr() net.Addr {
+ return c.conn.LocalAddr()
+}
+
+// RemoteAddr returns the remote network address.
+func (c *Conn) RemoteAddr() net.Addr {
+ return c.conn.RemoteAddr()
+}
+
+// Write methods
+
+func (c *Conn) writeFatal(err error) error {
+ err = hideTempErr(err)
+ c.writeErrMu.Lock()
+ if c.writeErr == nil {
+ c.writeErr = err
+ }
+ c.writeErrMu.Unlock()
+ return err
+}
+
+func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
+ <-c.mu
+ defer func() { c.mu <- true }()
+
+ c.writeErrMu.Lock()
+ err := c.writeErr
+ c.writeErrMu.Unlock()
+ if err != nil {
+ return err
+ }
+
+ c.conn.SetWriteDeadline(deadline)
+ for _, buf := range bufs {
+ if len(buf) > 0 {
+ _, err := c.conn.Write(buf)
+ if err != nil {
+ return c.writeFatal(err)
+ }
+ }
+ }
+
+ if frameType == CloseMessage {
+ c.writeFatal(ErrCloseSent)
+ }
+ return nil
+}
+
+// WriteControl writes a control message with the given deadline. The allowed
+// message types are CloseMessage, PingMessage and PongMessage.
+func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error {
+ if !isControl(messageType) {
+ return errBadWriteOpCode
+ }
+ if len(data) > maxControlFramePayloadSize {
+ return errInvalidControlFrame
+ }
+
+ b0 := byte(messageType) | finalBit
+ b1 := byte(len(data))
+ if !c.isServer {
+ b1 |= maskBit
+ }
+
+ buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize)
+ buf = append(buf, b0, b1)
+
+ if c.isServer {
+ buf = append(buf, data...)
+ } else {
+ key := newMaskKey()
+ buf = append(buf, key[:]...)
+ buf = append(buf, data...)
+ maskBytes(key, 0, buf[6:])
+ }
+
+ d := time.Hour * 1000
+ if !deadline.IsZero() {
+ d = deadline.Sub(time.Now())
+ if d < 0 {
+ return errWriteTimeout
+ }
+ }
+
+ timer := time.NewTimer(d)
+ select {
+ case <-c.mu:
+ timer.Stop()
+ case <-timer.C:
+ return errWriteTimeout
+ }
+ defer func() { c.mu <- true }()
+
+ c.writeErrMu.Lock()
+ err := c.writeErr
+ c.writeErrMu.Unlock()
+ if err != nil {
+ return err
+ }
+
+ c.conn.SetWriteDeadline(deadline)
+ _, err = c.conn.Write(buf)
+ if err != nil {
+ return c.writeFatal(err)
+ }
+ if messageType == CloseMessage {
+ c.writeFatal(ErrCloseSent)
+ }
+ return err
+}
+
+func (c *Conn) prepWrite(messageType int) error {
+ // Close previous writer if not already closed by the application. It's
+ // probably better to return an error in this situation, but we cannot
+ // change this without breaking existing applications.
+ if c.writer != nil {
+ c.writer.Close()
+ c.writer = nil
+ }
+
+ if !isControl(messageType) && !isData(messageType) {
+ return errBadWriteOpCode
+ }
+
+ c.writeErrMu.Lock()
+ err := c.writeErr
+ c.writeErrMu.Unlock()
+ return err
+}
+
+// NextWriter returns a writer for the next message to send. The writer's Close
+// method flushes the complete message to the network.
+//
+// There can be at most one open writer on a connection. NextWriter closes the
+// previous writer if the application has not already done so.
+func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
+ if err := c.prepWrite(messageType); err != nil {
+ return nil, err
+ }
+
+ mw := &messageWriter{
+ c: c,
+ frameType: messageType,
+ pos: maxFrameHeaderSize,
+ }
+ c.writer = mw
+ if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
+ w, err := c.newCompressionWriter(c.writer)
+ if err != nil {
+ c.writer = nil
+ return nil, err
+ }
+ mw.compress = true
+ c.writer = w
+ }
+ return c.writer, nil
+}
+
+type messageWriter struct {
+ c *Conn
+ compress bool // whether next call to flushFrame should set RSV1
+ pos int // end of data in writeBuf.
+ frameType int // type of the current frame.
+ err error
+}
+
+func (w *messageWriter) fatal(err error) error {
+ if w.err != nil {
+ w.err = err
+ w.c.writer = nil
+ }
+ return err
+}
+
+// flushFrame writes buffered data and extra as a frame to the network. The
+// final argument indicates that this is the last frame in the message.
+func (w *messageWriter) flushFrame(final bool, extra []byte) error {
+ c := w.c
+ length := w.pos - maxFrameHeaderSize + len(extra)
+
+ // Check for invalid control frames.
+ if isControl(w.frameType) &&
+ (!final || length > maxControlFramePayloadSize) {
+ return w.fatal(errInvalidControlFrame)
+ }
+
+ b0 := byte(w.frameType)
+ if final {
+ b0 |= finalBit
+ }
+ if w.compress {
+ b0 |= rsv1Bit
+ }
+ w.compress = false
+
+ b1 := byte(0)
+ if !c.isServer {
+ b1 |= maskBit
+ }
+
+ // Assume that the frame starts at beginning of c.writeBuf.
+ framePos := 0
+ if c.isServer {
+ // Adjust up if mask not included in the header.
+ framePos = 4
+ }
+
+ switch {
+ case length >= 65536:
+ c.writeBuf[framePos] = b0
+ c.writeBuf[framePos+1] = b1 | 127
+ binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length))
+ case length > 125:
+ framePos += 6
+ c.writeBuf[framePos] = b0
+ c.writeBuf[framePos+1] = b1 | 126
+ binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length))
+ default:
+ framePos += 8
+ c.writeBuf[framePos] = b0
+ c.writeBuf[framePos+1] = b1 | byte(length)
+ }
+
+ if !c.isServer {
+ key := newMaskKey()
+ copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
+ maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
+ if len(extra) > 0 {
+ return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
+ }
+ }
+
+ // Write the buffers to the connection with best-effort detection of
+ // concurrent writes. See the concurrency section in the package
+ // documentation for more info.
+
+ if c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = true
+
+ err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra)
+
+ if !c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = false
+
+ if err != nil {
+ return w.fatal(err)
+ }
+
+ if final {
+ c.writer = nil
+ return nil
+ }
+
+ // Setup for next frame.
+ w.pos = maxFrameHeaderSize
+ w.frameType = continuationFrame
+ return nil
+}
+
+func (w *messageWriter) ncopy(max int) (int, error) {
+ n := len(w.c.writeBuf) - w.pos
+ if n <= 0 {
+ if err := w.flushFrame(false, nil); err != nil {
+ return 0, err
+ }
+ n = len(w.c.writeBuf) - w.pos
+ }
+ if n > max {
+ n = max
+ }
+ return n, nil
+}
+
+func (w *messageWriter) Write(p []byte) (int, error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+
+ if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
+ // Don't buffer large messages.
+ err := w.flushFrame(false, p)
+ if err != nil {
+ return 0, err
+ }
+ return len(p), nil
+ }
+
+ nn := len(p)
+ for len(p) > 0 {
+ n, err := w.ncopy(len(p))
+ if err != nil {
+ return 0, err
+ }
+ copy(w.c.writeBuf[w.pos:], p[:n])
+ w.pos += n
+ p = p[n:]
+ }
+ return nn, nil
+}
+
+func (w *messageWriter) WriteString(p string) (int, error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+
+ nn := len(p)
+ for len(p) > 0 {
+ n, err := w.ncopy(len(p))
+ if err != nil {
+ return 0, err
+ }
+ copy(w.c.writeBuf[w.pos:], p[:n])
+ w.pos += n
+ p = p[n:]
+ }
+ return nn, nil
+}
+
+func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
+ if w.err != nil {
+ return 0, w.err
+ }
+ for {
+ if w.pos == len(w.c.writeBuf) {
+ err = w.flushFrame(false, nil)
+ if err != nil {
+ break
+ }
+ }
+ var n int
+ n, err = r.Read(w.c.writeBuf[w.pos:])
+ w.pos += n
+ nn += int64(n)
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ break
+ }
+ }
+ return nn, err
+}
+
+func (w *messageWriter) Close() error {
+ if w.err != nil {
+ return w.err
+ }
+ if err := w.flushFrame(true, nil); err != nil {
+ return err
+ }
+ w.err = errWriteClosed
+ return nil
+}
+
+// WriteMessage is a helper method for getting a writer using NextWriter,
+// writing the message and closing the writer.
+func (c *Conn) WriteMessage(messageType int, data []byte) error {
+
+ if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
+
+ // Fast path with no allocations and single frame.
+
+ if err := c.prepWrite(messageType); err != nil {
+ return err
+ }
+ mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
+ n := copy(c.writeBuf[mw.pos:], data)
+ mw.pos += n
+ data = data[n:]
+ return mw.flushFrame(true, data)
+ }
+
+ w, err := c.NextWriter(messageType)
+ if err != nil {
+ return err
+ }
+ if _, err = w.Write(data); err != nil {
+ return err
+ }
+ return w.Close()
+}
+
+// SetWriteDeadline sets the write deadline on the underlying network
+// connection. After a write has timed out, the websocket state is corrupt and
+// all future writes will return an error. A zero value for t means writes will
+// not time out.
+func (c *Conn) SetWriteDeadline(t time.Time) error {
+ c.writeDeadline = t
+ return nil
+}
+
+// Read methods
+
+func (c *Conn) advanceFrame() (int, error) {
+
+ // 1. Skip remainder of previous frame.
+
+ if c.readRemaining > 0 {
+ if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil {
+ return noFrame, err
+ }
+ }
+
+ // 2. Read and parse first two bytes of frame header.
+
+ p, err := c.read(2)
+ if err != nil {
+ return noFrame, err
+ }
+
+ final := p[0]&finalBit != 0
+ frameType := int(p[0] & 0xf)
+ mask := p[1]&maskBit != 0
+ c.readRemaining = int64(p[1] & 0x7f)
+
+ c.readDecompress = false
+ if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
+ c.readDecompress = true
+ p[0] &^= rsv1Bit
+ }
+
+ if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {
+ return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16))
+ }
+
+ switch frameType {
+ case CloseMessage, PingMessage, PongMessage:
+ if c.readRemaining > maxControlFramePayloadSize {
+ return noFrame, c.handleProtocolError("control frame length > 125")
+ }
+ if !final {
+ return noFrame, c.handleProtocolError("control frame not final")
+ }
+ case TextMessage, BinaryMessage:
+ if !c.readFinal {
+ return noFrame, c.handleProtocolError("message start before final message frame")
+ }
+ c.readFinal = final
+ case continuationFrame:
+ if c.readFinal {
+ return noFrame, c.handleProtocolError("continuation after final message frame")
+ }
+ c.readFinal = final
+ default:
+ return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
+ }
+
+ // 3. Read and parse frame length.
+
+ switch c.readRemaining {
+ case 126:
+ p, err := c.read(2)
+ if err != nil {
+ return noFrame, err
+ }
+ c.readRemaining = int64(binary.BigEndian.Uint16(p))
+ case 127:
+ p, err := c.read(8)
+ if err != nil {
+ return noFrame, err
+ }
+ c.readRemaining = int64(binary.BigEndian.Uint64(p))
+ }
+
+ // 4. Handle frame masking.
+
+ if mask != c.isServer {
+ return noFrame, c.handleProtocolError("incorrect mask flag")
+ }
+
+ if mask {
+ c.readMaskPos = 0
+ p, err := c.read(len(c.readMaskKey))
+ if err != nil {
+ return noFrame, err
+ }
+ copy(c.readMaskKey[:], p)
+ }
+
+ // 5. For text and binary messages, enforce read limit and return.
+
+ if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
+
+ c.readLength += c.readRemaining
+ if c.readLimit > 0 && c.readLength > c.readLimit {
+ c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
+ return noFrame, ErrReadLimit
+ }
+
+ return frameType, nil
+ }
+
+ // 6. Read control frame payload.
+
+ var payload []byte
+ if c.readRemaining > 0 {
+ payload, err = c.read(int(c.readRemaining))
+ c.readRemaining = 0
+ if err != nil {
+ return noFrame, err
+ }
+ if c.isServer {
+ maskBytes(c.readMaskKey, 0, payload)
+ }
+ }
+
+ // 7. Process control frame payload.
+
+ switch frameType {
+ case PongMessage:
+ if err := c.handlePong(string(payload)); err != nil {
+ return noFrame, err
+ }
+ case PingMessage:
+ if err := c.handlePing(string(payload)); err != nil {
+ return noFrame, err
+ }
+ case CloseMessage:
+ closeCode := CloseNoStatusReceived
+ closeText := ""
+ if len(payload) >= 2 {
+ closeCode = int(binary.BigEndian.Uint16(payload))
+ if !isValidReceivedCloseCode(closeCode) {
+ return noFrame, c.handleProtocolError("invalid close code")
+ }
+ closeText = string(payload[2:])
+ if !utf8.ValidString(closeText) {
+ return noFrame, c.handleProtocolError("invalid utf8 payload in close frame")
+ }
+ }
+ if err := c.handleClose(closeCode, closeText); err != nil {
+ return noFrame, err
+ }
+ return noFrame, &CloseError{Code: closeCode, Text: closeText}
+ }
+
+ return frameType, nil
+}
+
+func (c *Conn) handleProtocolError(message string) error {
+ c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait))
+ return errors.New("websocket: " + message)
+}
+
+// NextReader returns the next data message received from the peer. The
+// returned messageType is either TextMessage or BinaryMessage.
+//
+// There can be at most one open reader on a connection. NextReader discards
+// the previous message if the application has not already consumed it.
+//
+// Applications must break out of the application's read loop when this method
+// returns a non-nil error value. Errors returned from this method are
+// permanent. Once this method returns a non-nil error, all subsequent calls to
+// this method return the same error.
+func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
+
+ c.messageReader = nil
+ c.readLength = 0
+
+ for c.readErr == nil {
+ frameType, err := c.advanceFrame()
+ if err != nil {
+ c.readErr = hideTempErr(err)
+ break
+ }
+ if frameType == TextMessage || frameType == BinaryMessage {
+ c.messageReader = &messageReader{c}
+ var r io.Reader = c.messageReader
+ if c.readDecompress {
+ r = c.newDecompressionReader(r)
+ }
+ return frameType, r, nil
+ }
+ }
+
+ // Applications that do handle the error returned from this method spin in
+ // tight loop on connection failure. To help application developers detect
+ // this error, panic on repeated reads to the failed connection.
+ c.readErrCount++
+ if c.readErrCount >= 1000 {
+ panic("repeated read on failed websocket connection")
+ }
+
+ return noFrame, nil, c.readErr
+}
+
+type messageReader struct{ c *Conn }
+
+func (r *messageReader) Read(b []byte) (int, error) {
+ c := r.c
+ if c.messageReader != r {
+ return 0, io.EOF
+ }
+
+ for c.readErr == nil {
+
+ if c.readRemaining > 0 {
+ if int64(len(b)) > c.readRemaining {
+ b = b[:c.readRemaining]
+ }
+ n, err := c.br.Read(b)
+ c.readErr = hideTempErr(err)
+ if c.isServer {
+ c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
+ }
+ c.readRemaining -= int64(n)
+ if c.readRemaining > 0 && c.readErr == io.EOF {
+ c.readErr = errUnexpectedEOF
+ }
+ return n, c.readErr
+ }
+
+ if c.readFinal {
+ c.messageReader = nil
+ return 0, io.EOF
+ }
+
+ frameType, err := c.advanceFrame()
+ switch {
+ case err != nil:
+ c.readErr = hideTempErr(err)
+ case frameType == TextMessage || frameType == BinaryMessage:
+ c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
+ }
+ }
+
+ err := c.readErr
+ if err == io.EOF && c.messageReader == r {
+ err = errUnexpectedEOF
+ }
+ return 0, err
+}
+
+// ReadMessage is a helper method for getting a reader using NextReader and
+// reading from that reader to a buffer.
+func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
+ var r io.Reader
+ messageType, r, err = c.NextReader()
+ if err != nil {
+ return messageType, nil, err
+ }
+ p, err = ioutil.ReadAll(r)
+ return messageType, p, err
+}
+
+// SetReadDeadline sets the read deadline on the underlying network connection.
+// After a read has timed out, the websocket connection state is corrupt and
+// all future reads will return an error. A zero value for t means reads will
+// not time out.
+func (c *Conn) SetReadDeadline(t time.Time) error {
+ return c.conn.SetReadDeadline(t)
+}
+
+// SetReadLimit sets the maximum size for a message read from the peer. If a
+// message exceeds the limit, the connection sends a close frame to the peer
+// and returns ErrReadLimit to the application.
+func (c *Conn) SetReadLimit(limit int64) {
+ c.readLimit = limit
+}
+
+// CloseHandler returns the current close handler
+func (c *Conn) CloseHandler() func(code int, text string) error {
+ return c.handleClose
+}
+
+// SetCloseHandler sets the handler for close messages received from the peer.
+// The code argument to h is the received close code or CloseNoStatusReceived
+// if the close message is empty. The default close handler sends a close frame
+// back to the peer.
+func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
+ if h == nil {
+ h = func(code int, text string) error {
+ message := []byte{}
+ if code != CloseNoStatusReceived {
+ message = FormatCloseMessage(code, "")
+ }
+ c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
+ return nil
+ }
+ }
+ c.handleClose = h
+}
+
+// PingHandler returns the current ping handler
+func (c *Conn) PingHandler() func(appData string) error {
+ return c.handlePing
+}
+
+// SetPingHandler sets the handler for ping messages received from the peer.
+// The appData argument to h is the PING frame application data. The default
+// ping handler sends a pong to the peer.
+func (c *Conn) SetPingHandler(h func(appData string) error) {
+ if h == nil {
+ h = func(message string) error {
+ err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
+ if err == ErrCloseSent {
+ return nil
+ } else if e, ok := err.(net.Error); ok && e.Temporary() {
+ return nil
+ }
+ return err
+ }
+ }
+ c.handlePing = h
+}
+
+// PongHandler returns the current pong handler
+func (c *Conn) PongHandler() func(appData string) error {
+ return c.handlePong
+}
+
+// SetPongHandler sets the handler for pong messages received from the peer.
+// The appData argument to h is the PONG frame application data. The default
+// pong handler does nothing.
+func (c *Conn) SetPongHandler(h func(appData string) error) {
+ if h == nil {
+ h = func(string) error { return nil }
+ }
+ c.handlePong = h
+}
+
+// UnderlyingConn returns the internal net.Conn. This can be used to further
+// modifications to connection specific flags.
+func (c *Conn) UnderlyingConn() net.Conn {
+ return c.conn
+}
+
+// EnableWriteCompression enables and disables write compression of
+// subsequent text and binary messages. This function is a noop if
+// compression was not negotiated with the peer.
+func (c *Conn) EnableWriteCompression(enable bool) {
+ c.enableWriteCompression = enable
+}
+
+// FormatCloseMessage formats closeCode and text as a WebSocket close message.
+func FormatCloseMessage(closeCode int, text string) []byte {
+ buf := make([]byte, 2+len(text))
+ binary.BigEndian.PutUint16(buf, uint16(closeCode))
+ copy(buf[2:], text)
+ return buf
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn_read.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn_read.go
new file mode 100644
index 00000000..1ea15059
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn_read.go
@@ -0,0 +1,18 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.5
+
+package websocket
+
+import "io"
+
+func (c *Conn) read(n int) ([]byte, error) {
+ p, err := c.br.Peek(n)
+ if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ c.br.Discard(len(p))
+ return p, err
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn_read_legacy.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn_read_legacy.go
new file mode 100644
index 00000000..018541cf
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/conn_read_legacy.go
@@ -0,0 +1,21 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5
+
+package websocket
+
+import "io"
+
+func (c *Conn) read(n int) ([]byte, error) {
+ p, err := c.br.Peek(n)
+ if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ if len(p) > 0 {
+ // advance over the bytes just read
+ io.ReadFull(c.br, p)
+ }
+ return p, err
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/doc.go
new file mode 100644
index 00000000..610acf71
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/doc.go
@@ -0,0 +1,173 @@
+// Copyright 2013 The Gorilla WebSocket 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 websocket implements the WebSocket protocol defined in RFC 6455.
+//
+// Overview
+//
+// The Conn type represents a WebSocket connection. A server application uses
+// the Upgrade function from an Upgrader object with a HTTP request handler
+// to get a pointer to a Conn:
+//
+// var upgrader = websocket.Upgrader{
+// ReadBufferSize: 1024,
+// WriteBufferSize: 1024,
+// }
+//
+// func handler(w http.ResponseWriter, r *http.Request) {
+// conn, err := upgrader.Upgrade(w, r, nil)
+// if err != nil {
+// log.Println(err)
+// return
+// }
+// ... Use conn to send and receive messages.
+// }
+//
+// Call the connection's WriteMessage and ReadMessage methods to send and
+// receive messages as a slice of bytes. This snippet of code shows how to echo
+// messages using these methods:
+//
+// for {
+// messageType, p, err := conn.ReadMessage()
+// if err != nil {
+// return
+// }
+// if err = conn.WriteMessage(messageType, p); err != nil {
+// return err
+// }
+// }
+//
+// In above snippet of code, p is a []byte and messageType is an int with value
+// websocket.BinaryMessage or websocket.TextMessage.
+//
+// An application can also send and receive messages using the io.WriteCloser
+// and io.Reader interfaces. To send a message, call the connection NextWriter
+// method to get an io.WriteCloser, write the message to the writer and close
+// the writer when done. To receive a message, call the connection NextReader
+// method to get an io.Reader and read until io.EOF is returned. This snippet
+// shows how to echo messages using the NextWriter and NextReader methods:
+//
+// for {
+// messageType, r, err := conn.NextReader()
+// if err != nil {
+// return
+// }
+// w, err := conn.NextWriter(messageType)
+// if err != nil {
+// return err
+// }
+// if _, err := io.Copy(w, r); err != nil {
+// return err
+// }
+// if err := w.Close(); err != nil {
+// return err
+// }
+// }
+//
+// Data Messages
+//
+// The WebSocket protocol distinguishes between text and binary data messages.
+// Text messages are interpreted as UTF-8 encoded text. The interpretation of
+// binary messages is left to the application.
+//
+// This package uses the TextMessage and BinaryMessage integer constants to
+// identify the two data message types. The ReadMessage and NextReader methods
+// return the type of the received message. The messageType argument to the
+// WriteMessage and NextWriter methods specifies the type of a sent message.
+//
+// It is the application's responsibility to ensure that text messages are
+// valid UTF-8 encoded text.
+//
+// Control Messages
+//
+// The WebSocket protocol defines three types of control messages: close, ping
+// and pong. Call the connection WriteControl, WriteMessage or NextWriter
+// methods to send a control message to the peer.
+//
+// Connections handle received close messages by sending a close message to the
+// peer and returning a *CloseError from the the NextReader, ReadMessage or the
+// message Read method.
+//
+// Connections handle received ping and pong messages by invoking callback
+// functions set with SetPingHandler and SetPongHandler methods. The callback
+// functions are called from the NextReader, ReadMessage and the message Read
+// methods.
+//
+// The default ping handler sends a pong to the peer. The application's reading
+// goroutine can block for a short time while the handler writes the pong data
+// to the connection.
+//
+// The application must read the connection to process ping, pong and close
+// messages sent from the peer. If the application is not otherwise interested
+// in messages from the peer, then the application should start a goroutine to
+// read and discard messages from the peer. A simple example is:
+//
+// func readLoop(c *websocket.Conn) {
+// for {
+// if _, _, err := c.NextReader(); err != nil {
+// c.Close()
+// break
+// }
+// }
+// }
+//
+// Concurrency
+//
+// Connections support one concurrent reader and one concurrent writer.
+//
+// Applications are responsible for ensuring that no more than one goroutine
+// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
+// WriteJSON) concurrently and that no more than one goroutine calls the read
+// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler,
+// SetPingHandler) concurrently.
+//
+// The Close and WriteControl methods can be called concurrently with all other
+// methods.
+//
+// Origin Considerations
+//
+// Web browsers allow Javascript applications to open a WebSocket connection to
+// any host. It's up to the server to enforce an origin policy using the Origin
+// request header sent by the browser.
+//
+// The Upgrader calls the function specified in the CheckOrigin field to check
+// the origin. If the CheckOrigin function returns false, then the Upgrade
+// method fails the WebSocket handshake with HTTP status 403.
+//
+// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
+// the handshake if the Origin request header is present and not equal to the
+// Host request header.
+//
+// An application can allow connections from any origin by specifying a
+// function that always returns true:
+//
+// var upgrader = websocket.Upgrader{
+// CheckOrigin: func(r *http.Request) bool { return true },
+// }
+//
+// The deprecated Upgrade function does not enforce an origin policy. It's the
+// application's responsibility to check the Origin header before calling
+// Upgrade.
+//
+// Compression [Experimental]
+//
+// Per message compression extensions (RFC 7692) are experimentally supported
+// by this package in a limited capacity. Setting the EnableCompression option
+// to true in Dialer or Upgrader will attempt to negotiate per message deflate
+// support. If compression was successfully negotiated with the connection's
+// peer, any message received in compressed form will be automatically
+// decompressed. All Read methods will return uncompressed bytes.
+//
+// Per message compression of messages written to a connection can be enabled
+// or disabled by calling the corresponding Conn method:
+//
+// conn.EnableWriteCompression(true)
+//
+// Currently this package does not support compression with "context takeover".
+// This means that messages must be compressed and decompressed in isolation,
+// without retaining sliding window or dictionary state across messages. For
+// more details refer to RFC 7692.
+//
+// Use of compression is experimental and may result in decreased performance.
+package websocket
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/autobahn/server.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/autobahn/server.go
new file mode 100644
index 00000000..e98563be
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/autobahn/server.go
@@ -0,0 +1,248 @@
+// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command server is a test server for the Autobahn WebSockets Test Suite.
+package main
+
+import (
+ "errors"
+ "flag"
+ "io"
+ "log"
+ "net/http"
+ "time"
+ "unicode/utf8"
+
+ "github.com/gorilla/websocket"
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 4096,
+ WriteBufferSize: 4096,
+ EnableCompression: true,
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+}
+
+// echoCopy echoes messages from the client using io.Copy.
+func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println("Upgrade:", err)
+ return
+ }
+ defer conn.Close()
+ for {
+ mt, r, err := conn.NextReader()
+ if err != nil {
+ if err != io.EOF {
+ log.Println("NextReader:", err)
+ }
+ return
+ }
+ if mt == websocket.TextMessage {
+ r = &validator{r: r}
+ }
+ w, err := conn.NextWriter(mt)
+ if err != nil {
+ log.Println("NextWriter:", err)
+ return
+ }
+ if mt == websocket.TextMessage {
+ r = &validator{r: r}
+ }
+ if writerOnly {
+ _, err = io.Copy(struct{ io.Writer }{w}, r)
+ } else {
+ _, err = io.Copy(w, r)
+ }
+ if err != nil {
+ if err == errInvalidUTF8 {
+ conn.WriteControl(websocket.CloseMessage,
+ websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
+ time.Time{})
+ }
+ log.Println("Copy:", err)
+ return
+ }
+ err = w.Close()
+ if err != nil {
+ log.Println("Close:", err)
+ return
+ }
+ }
+}
+
+func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) {
+ echoCopy(w, r, true)
+}
+
+func echoCopyFull(w http.ResponseWriter, r *http.Request) {
+ echoCopy(w, r, false)
+}
+
+// echoReadAll echoes messages from the client by reading the entire message
+// with ioutil.ReadAll.
+func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println("Upgrade:", err)
+ return
+ }
+ defer conn.Close()
+ for {
+ mt, b, err := conn.ReadMessage()
+ if err != nil {
+ if err != io.EOF {
+ log.Println("NextReader:", err)
+ }
+ return
+ }
+ if mt == websocket.TextMessage {
+ if !utf8.Valid(b) {
+ conn.WriteControl(websocket.CloseMessage,
+ websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
+ time.Time{})
+ log.Println("ReadAll: invalid utf8")
+ }
+ }
+ if writeMessage {
+ err = conn.WriteMessage(mt, b)
+ if err != nil {
+ log.Println("WriteMessage:", err)
+ }
+ } else {
+ w, err := conn.NextWriter(mt)
+ if err != nil {
+ log.Println("NextWriter:", err)
+ return
+ }
+ if _, err := w.Write(b); err != nil {
+ log.Println("Writer:", err)
+ return
+ }
+ if err := w.Close(); err != nil {
+ log.Println("Close:", err)
+ return
+ }
+ }
+ }
+}
+
+func echoReadAllWriter(w http.ResponseWriter, r *http.Request) {
+ echoReadAll(w, r, false)
+}
+
+func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) {
+ echoReadAll(w, r, true)
+}
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found.", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ io.WriteString(w, "Echo Server")
+}
+
+var addr = flag.String("addr", ":9000", "http service address")
+
+func main() {
+ flag.Parse()
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/c", echoCopyWriterOnly)
+ http.HandleFunc("/f", echoCopyFull)
+ http.HandleFunc("/r", echoReadAllWriter)
+ http.HandleFunc("/m", echoReadAllWriteMessage)
+ err := http.ListenAndServe(*addr, nil)
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
+
+type validator struct {
+ state int
+ x rune
+ r io.Reader
+}
+
+var errInvalidUTF8 = errors.New("invalid utf8")
+
+func (r *validator) Read(p []byte) (int, error) {
+ n, err := r.r.Read(p)
+ state := r.state
+ x := r.x
+ for _, b := range p[:n] {
+ state, x = decode(state, x, b)
+ if state == utf8Reject {
+ break
+ }
+ }
+ r.state = state
+ r.x = x
+ if state == utf8Reject || (err == io.EOF && state != utf8Accept) {
+ return n, errInvalidUTF8
+ }
+ return n, err
+}
+
+// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+//
+// Copyright (c) 2008-2009 Bjoern Hoehrmann
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+var utf8d = [...]byte{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
+ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
+ 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
+ 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
+ 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
+ 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
+ 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
+ 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
+}
+
+const (
+ utf8Accept = 0
+ utf8Reject = 1
+)
+
+func decode(state int, x rune, b byte) (int, rune) {
+ t := utf8d[b]
+ if state != utf8Accept {
+ x = rune(b&0x3f) | (x << 6)
+ } else {
+ x = rune((0xff >> t) & b)
+ }
+ state = int(utf8d[256+state*16+int(t)])
+ return state, x
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/client.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/client.go
new file mode 100644
index 00000000..26468477
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/client.go
@@ -0,0 +1,134 @@
+// Copyright 2013 The Gorilla WebSocket 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 main
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+const (
+ // Time allowed to write a message to the peer.
+ writeWait = 10 * time.Second
+
+ // Time allowed to read the next pong message from the peer.
+ pongWait = 60 * time.Second
+
+ // Send pings to peer with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+
+ // Maximum message size allowed from peer.
+ maxMessageSize = 512
+)
+
+var (
+ newline = []byte{'\n'}
+ space = []byte{' '}
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+}
+
+// Client is a middleman between the websocket connection and the hub.
+type Client struct {
+ hub *Hub
+
+ // The websocket connection.
+ conn *websocket.Conn
+
+ // Buffered channel of outbound messages.
+ send chan []byte
+}
+
+// readPump pumps messages from the websocket connection to the hub.
+//
+// The application runs readPump in a per-connection goroutine. The application
+// ensures that there is at most one reader on a connection by executing all
+// reads from this goroutine.
+func (c *Client) readPump() {
+ defer func() {
+ c.hub.unregister <- c
+ c.conn.Close()
+ }()
+ c.conn.SetReadLimit(maxMessageSize)
+ c.conn.SetReadDeadline(time.Now().Add(pongWait))
+ c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, message, err := c.conn.ReadMessage()
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
+ log.Printf("error: %v", err)
+ }
+ break
+ }
+ message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
+ c.hub.broadcast <- message
+ }
+}
+
+// writePump pumps messages from the hub to the websocket connection.
+//
+// A goroutine running writePump is started for each connection. The
+// application ensures that there is at most one writer to a connection by
+// executing all writes from this goroutine.
+func (c *Client) writePump() {
+ ticker := time.NewTicker(pingPeriod)
+ defer func() {
+ ticker.Stop()
+ c.conn.Close()
+ }()
+ for {
+ select {
+ case message, ok := <-c.send:
+ c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+ if !ok {
+ // The hub closed the channel.
+ c.conn.WriteMessage(websocket.CloseMessage, []byte{})
+ return
+ }
+
+ w, err := c.conn.NextWriter(websocket.TextMessage)
+ if err != nil {
+ return
+ }
+ w.Write(message)
+
+ // Add queued chat messages to the current websocket message.
+ n := len(c.send)
+ for i := 0; i < n; i++ {
+ w.Write(newline)
+ w.Write(<-c.send)
+ }
+
+ if err := w.Close(); err != nil {
+ return
+ }
+ case <-ticker.C:
+ c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
+ return
+ }
+ }
+ }
+}
+
+// serveWs handles websocket requests from the peer.
+func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
+ client.hub.register <- client
+ go client.writePump()
+ client.readPump()
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/hub.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/hub.go
new file mode 100644
index 00000000..7f07ea07
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/hub.go
@@ -0,0 +1,53 @@
+// Copyright 2013 The Gorilla WebSocket 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 main
+
+// hub maintains the set of active clients and broadcasts messages to the
+// clients.
+type Hub struct {
+ // Registered clients.
+ clients map[*Client]bool
+
+ // Inbound messages from the clients.
+ broadcast chan []byte
+
+ // Register requests from the clients.
+ register chan *Client
+
+ // Unregister requests from clients.
+ unregister chan *Client
+}
+
+func newHub() *Hub {
+ return &Hub{
+ broadcast: make(chan []byte),
+ register: make(chan *Client),
+ unregister: make(chan *Client),
+ clients: make(map[*Client]bool),
+ }
+}
+
+func (h *Hub) run() {
+ for {
+ select {
+ case client := <-h.register:
+ h.clients[client] = true
+ case client := <-h.unregister:
+ if _, ok := h.clients[client]; ok {
+ delete(h.clients, client)
+ close(client.send)
+ }
+ case message := <-h.broadcast:
+ for client := range h.clients {
+ select {
+ case client.send <- message:
+ default:
+ close(client.send)
+ delete(h.clients, client)
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/main.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/main.go
new file mode 100644
index 00000000..a865ffec
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/chat/main.go
@@ -0,0 +1,43 @@
+// Copyright 2013 The Gorilla WebSocket 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 main
+
+import (
+ "flag"
+ "log"
+ "net/http"
+ "text/template"
+)
+
+var addr = flag.String("addr", ":8080", "http service address")
+var homeTemplate = template.Must(template.ParseFiles("home.html"))
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ log.Println(r.URL)
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ homeTemplate.Execute(w, r.Host)
+}
+
+func main() {
+ flag.Parse()
+ hub := newHub()
+ go hub.run()
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
+ serveWs(hub, w, r)
+ })
+ err := http.ListenAndServe(*addr, nil)
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/command/main.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/command/main.go
new file mode 100644
index 00000000..438fb832
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/command/main.go
@@ -0,0 +1,196 @@
+// Copyright 2015 The Gorilla WebSocket 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 main
+
+import (
+ "bufio"
+ "flag"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "text/template"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+var (
+ addr = flag.String("addr", "127.0.0.1:8080", "http service address")
+ cmdPath string
+ homeTempl = template.Must(template.ParseFiles("home.html"))
+)
+
+const (
+ // Time allowed to write a message to the peer.
+ writeWait = 10 * time.Second
+
+ // Maximum message size allowed from peer.
+ maxMessageSize = 8192
+
+ // Time allowed to read the next pong message from the peer.
+ pongWait = 60 * time.Second
+
+ // Send pings to peer with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+
+ // Time to wait before force close on connection.
+ closeGracePeriod = 10 * time.Second
+)
+
+func pumpStdin(ws *websocket.Conn, w io.Writer) {
+ defer ws.Close()
+ ws.SetReadLimit(maxMessageSize)
+ ws.SetReadDeadline(time.Now().Add(pongWait))
+ ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, message, err := ws.ReadMessage()
+ if err != nil {
+ break
+ }
+ message = append(message, '\n')
+ if _, err := w.Write(message); err != nil {
+ break
+ }
+ }
+}
+
+func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
+ defer func() {
+ }()
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
+ ws.Close()
+ break
+ }
+ }
+ if s.Err() != nil {
+ log.Println("scan:", s.Err())
+ }
+ close(done)
+
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ time.Sleep(closeGracePeriod)
+ ws.Close()
+}
+
+func ping(ws *websocket.Conn, done chan struct{}) {
+ ticker := time.NewTicker(pingPeriod)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
+ log.Println("ping:", err)
+ }
+ case <-done:
+ return
+ }
+ }
+}
+
+func internalError(ws *websocket.Conn, msg string, err error) {
+ log.Println(msg, err)
+ ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
+}
+
+var upgrader = websocket.Upgrader{}
+
+func serveWs(w http.ResponseWriter, r *http.Request) {
+ ws, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println("upgrade:", err)
+ return
+ }
+
+ defer ws.Close()
+
+ outr, outw, err := os.Pipe()
+ if err != nil {
+ internalError(ws, "stdout:", err)
+ return
+ }
+ defer outr.Close()
+ defer outw.Close()
+
+ inr, inw, err := os.Pipe()
+ if err != nil {
+ internalError(ws, "stdin:", err)
+ return
+ }
+ defer inr.Close()
+ defer inw.Close()
+
+ proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
+ Files: []*os.File{inr, outw, outw},
+ })
+ if err != nil {
+ internalError(ws, "start:", err)
+ return
+ }
+
+ inr.Close()
+ outw.Close()
+
+ stdoutDone := make(chan struct{})
+ go pumpStdout(ws, outr, stdoutDone)
+ go ping(ws, stdoutDone)
+
+ pumpStdin(ws, inw)
+
+ // Some commands will exit when stdin is closed.
+ inw.Close()
+
+ // Other commands need a bonk on the head.
+ if err := proc.Signal(os.Interrupt); err != nil {
+ log.Println("inter:", err)
+ }
+
+ select {
+ case <-stdoutDone:
+ case <-time.After(time.Second):
+ // A bigger bonk on the head.
+ if err := proc.Signal(os.Kill); err != nil {
+ log.Println("term:", err)
+ }
+ <-stdoutDone
+ }
+
+ if _, err := proc.Wait(); err != nil {
+ log.Println("wait:", err)
+ }
+}
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ homeTempl.Execute(w, r.Host)
+}
+
+func main() {
+ flag.Parse()
+ if len(flag.Args()) < 1 {
+ log.Fatal("must specify at least one argument")
+ }
+ var err error
+ cmdPath, err = exec.LookPath(flag.Args()[0])
+ if err != nil {
+ log.Fatal(err)
+ }
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", serveWs)
+ log.Fatal(http.ListenAndServe(*addr, nil))
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/echo/client.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/echo/client.go
new file mode 100644
index 00000000..6578094e
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/echo/client.go
@@ -0,0 +1,81 @@
+// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import (
+ "flag"
+ "log"
+ "net/url"
+ "os"
+ "os/signal"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+var addr = flag.String("addr", "localhost:8080", "http service address")
+
+func main() {
+ flag.Parse()
+ log.SetFlags(0)
+
+ interrupt := make(chan os.Signal, 1)
+ signal.Notify(interrupt, os.Interrupt)
+
+ u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
+ log.Printf("connecting to %s", u.String())
+
+ c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
+ if err != nil {
+ log.Fatal("dial:", err)
+ }
+ defer c.Close()
+
+ done := make(chan struct{})
+
+ go func() {
+ defer c.Close()
+ defer close(done)
+ for {
+ _, message, err := c.ReadMessage()
+ if err != nil {
+ log.Println("read:", err)
+ return
+ }
+ log.Printf("recv: %s", message)
+ }
+ }()
+
+ ticker := time.NewTicker(time.Second)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case t := <-ticker.C:
+ err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
+ if err != nil {
+ log.Println("write:", err)
+ return
+ }
+ case <-interrupt:
+ log.Println("interrupt")
+ // To cleanly close a connection, a client should send a close
+ // frame and wait for the server to close the connection.
+ err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ if err != nil {
+ log.Println("write close:", err)
+ return
+ }
+ select {
+ case <-done:
+ case <-time.After(time.Second):
+ }
+ c.Close()
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/echo/server.go b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/echo/server.go
new file mode 100644
index 00000000..a685b097
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/vendor/github.com/gorilla/websocket/examples/echo/server.go
@@ -0,0 +1,132 @@
+// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import (
+ "flag"
+ "html/template"
+ "log"
+ "net/http"
+
+ "github.com/gorilla/websocket"
+)
+
+var addr = flag.String("addr", "localhost:8080", "http service address")
+
+var upgrader = websocket.Upgrader{} // use default options
+
+func echo(w http.ResponseWriter, r *http.Request) {
+ c, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Print("upgrade:", err)
+ return
+ }
+ defer c.Close()
+ for {
+ mt, message, err := c.ReadMessage()
+ if err != nil {
+ log.Println("read:", err)
+ break
+ }
+ log.Printf("recv: %s", message)
+ err = c.WriteMessage(mt, message)
+ if err != nil {
+ log.Println("write:", err)
+ break
+ }
+ }
+}
+
+func home(w http.ResponseWriter, r *http.Request) {
+ homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
+}
+
+func main() {
+ flag.Parse()
+ log.SetFlags(0)
+ http.HandleFunc("/echo", echo)
+ http.HandleFunc("/", home)
+ log.Fatal(http.ListenAndServe(*addr, nil))
+}
+
+var homeTemplate = template.Must(template.New("").Parse(`
+
+
+
+
+
+
+
+
+
Click "Open" to create a connection to the server,
+"Send" to send a message to the server and "Close" to close the connection.
+You can change the message and send multiple times.
+