summaryrefslogtreecommitdiffstats
path: root/vendor/github.com
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE202
-rw-r--r--vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go441
-rw-r--r--vendor/github.com/42wim/matterbridge-plus/bridge/config.go65
-rw-r--r--vendor/github.com/42wim/matterbridge-plus/bridge/helper.go59
-rw-r--r--vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE202
-rw-r--r--vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go316
-rw-r--r--vendor/github.com/42wim/matterbridge/matterhook/LICENSE202
-rw-r--r--vendor/github.com/42wim/matterbridge/matterhook/matterhook.go154
-rw-r--r--vendor/github.com/Sirupsen/logrus/LICENSE21
-rw-r--r--vendor/github.com/Sirupsen/logrus/doc.go26
-rw-r--r--vendor/github.com/Sirupsen/logrus/entry.go264
-rw-r--r--vendor/github.com/Sirupsen/logrus/examples/basic/basic.go50
-rw-r--r--vendor/github.com/Sirupsen/logrus/examples/hook/hook.go30
-rw-r--r--vendor/github.com/Sirupsen/logrus/exported.go193
-rw-r--r--vendor/github.com/Sirupsen/logrus/formatter.go48
-rw-r--r--vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go61
-rw-r--r--vendor/github.com/Sirupsen/logrus/hooks.go34
-rw-r--r--vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go54
-rw-r--r--vendor/github.com/Sirupsen/logrus/hooks/test/test.go67
-rw-r--r--vendor/github.com/Sirupsen/logrus/json_formatter.go41
-rw-r--r--vendor/github.com/Sirupsen/logrus/logger.go212
-rw-r--r--vendor/github.com/Sirupsen/logrus/logrus.go143
-rw-r--r--vendor/github.com/Sirupsen/logrus/terminal_bsd.go9
-rw-r--r--vendor/github.com/Sirupsen/logrus/terminal_linux.go12
-rw-r--r--vendor/github.com/Sirupsen/logrus/terminal_notwindows.go21
-rw-r--r--vendor/github.com/Sirupsen/logrus/terminal_solaris.go15
-rw-r--r--vendor/github.com/Sirupsen/logrus/terminal_windows.go27
-rw-r--r--vendor/github.com/Sirupsen/logrus/text_formatter.go161
-rw-r--r--vendor/github.com/Sirupsen/logrus/writer.go31
-rw-r--r--vendor/github.com/alecthomas/log4go/LICENSE13
-rw-r--r--vendor/github.com/alecthomas/log4go/config.go288
-rw-r--r--vendor/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go14
-rw-r--r--vendor/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go57
-rw-r--r--vendor/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go42
-rw-r--r--vendor/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go18
-rw-r--r--vendor/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go13
-rw-r--r--vendor/github.com/alecthomas/log4go/filelog.go264
-rw-r--r--vendor/github.com/alecthomas/log4go/log4go.go484
-rw-r--r--vendor/github.com/alecthomas/log4go/pattlog.go126
-rw-r--r--vendor/github.com/alecthomas/log4go/socklog.go57
-rw-r--r--vendor/github.com/alecthomas/log4go/termlog.go49
-rw-r--r--vendor/github.com/alecthomas/log4go/wrapper.go278
-rw-r--r--vendor/github.com/gorilla/schema/LICENSE27
-rw-r--r--vendor/github.com/gorilla/schema/cache.go245
-rw-r--r--vendor/github.com/gorilla/schema/converter.go145
-rw-r--r--vendor/github.com/gorilla/schema/decoder.go299
-rw-r--r--vendor/github.com/gorilla/schema/doc.go148
-rw-r--r--vendor/github.com/gorilla/websocket/LICENSE22
-rw-r--r--vendor/github.com/gorilla/websocket/client.go350
-rw-r--r--vendor/github.com/gorilla/websocket/conn.go915
-rw-r--r--vendor/github.com/gorilla/websocket/doc.go152
-rw-r--r--vendor/github.com/gorilla/websocket/examples/autobahn/server.go246
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/conn.go105
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/hub.go51
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/main.go39
-rw-r--r--vendor/github.com/gorilla/websocket/examples/command/main.go188
-rw-r--r--vendor/github.com/gorilla/websocket/examples/echo/client.go81
-rw-r--r--vendor/github.com/gorilla/websocket/examples/echo/server.go132
-rw-r--r--vendor/github.com/gorilla/websocket/examples/filewatch/main.go193
-rw-r--r--vendor/github.com/gorilla/websocket/json.go55
-rw-r--r--vendor/github.com/gorilla/websocket/server.go260
-rw-r--r--vendor/github.com/gorilla/websocket/util.go44
-rw-r--r--vendor/github.com/jpillora/backoff/backoff.go69
-rw-r--r--vendor/github.com/mattermost/platform/model/LICENSE.txt897
-rw-r--r--vendor/github.com/mattermost/platform/model/access.go93
-rw-r--r--vendor/github.com/mattermost/platform/model/analytics_row.go55
-rw-r--r--vendor/github.com/mattermost/platform/model/audit.go39
-rw-r--r--vendor/github.com/mattermost/platform/model/audits.go39
-rw-r--r--vendor/github.com/mattermost/platform/model/authorize.go103
-rw-r--r--vendor/github.com/mattermost/platform/model/channel.go136
-rw-r--r--vendor/github.com/mattermost/platform/model/channel_count.go63
-rw-r--r--vendor/github.com/mattermost/platform/model/channel_data.go43
-rw-r--r--vendor/github.com/mattermost/platform/model/channel_extra.go49
-rw-r--r--vendor/github.com/mattermost/platform/model/channel_list.go77
-rw-r--r--vendor/github.com/mattermost/platform/model/channel_member.go108
-rw-r--r--vendor/github.com/mattermost/platform/model/client.go1144
-rw-r--r--vendor/github.com/mattermost/platform/model/command.go153
-rw-r--r--vendor/github.com/mattermost/platform/model/command_response.go41
-rw-r--r--vendor/github.com/mattermost/platform/model/compliance.go132
-rw-r--r--vendor/github.com/mattermost/platform/model/compliance_post.go104
-rw-r--r--vendor/github.com/mattermost/platform/model/config.go554
-rw-r--r--vendor/github.com/mattermost/platform/model/file.go43
-rw-r--r--vendor/github.com/mattermost/platform/model/file_info.go77
-rw-r--r--vendor/github.com/mattermost/platform/model/gitlab/gitlab.go107
-rw-r--r--vendor/github.com/mattermost/platform/model/incoming_webhook.go137
-rw-r--r--vendor/github.com/mattermost/platform/model/ldap.go8
-rw-r--r--vendor/github.com/mattermost/platform/model/license.go123
-rw-r--r--vendor/github.com/mattermost/platform/model/message.go58
-rw-r--r--vendor/github.com/mattermost/platform/model/oauth.go159
-rw-r--r--vendor/github.com/mattermost/platform/model/outgoing_webhook.go151
-rw-r--r--vendor/github.com/mattermost/platform/model/post.go169
-rw-r--r--vendor/github.com/mattermost/platform/model/post_list.go100
-rw-r--r--vendor/github.com/mattermost/platform/model/preference.go66
-rw-r--r--vendor/github.com/mattermost/platform/model/preferences.go31
-rw-r--r--vendor/github.com/mattermost/platform/model/push_notification.go49
-rw-r--r--vendor/github.com/mattermost/platform/model/search_params.go170
-rw-r--r--vendor/github.com/mattermost/platform/model/security_bulletin.go55
-rw-r--r--vendor/github.com/mattermost/platform/model/session.go115
-rw-r--r--vendor/github.com/mattermost/platform/model/suggest_command.go34
-rw-r--r--vendor/github.com/mattermost/platform/model/system.go42
-rw-r--r--vendor/github.com/mattermost/platform/model/team.go243
-rw-r--r--vendor/github.com/mattermost/platform/model/team_signup.go40
-rw-r--r--vendor/github.com/mattermost/platform/model/user.go481
-rw-r--r--vendor/github.com/mattermost/platform/model/utils.go381
-rw-r--r--vendor/github.com/mattermost/platform/model/version.go127
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE19
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go315
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go152
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go132
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go143
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go99
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go119
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go40
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go74
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go567
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go78
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go57
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go61
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go83
-rw-r--r--vendor/github.com/pborman/uuid/LICENSE27
-rw-r--r--vendor/github.com/pborman/uuid/dce.go84
-rw-r--r--vendor/github.com/pborman/uuid/doc.go8
-rw-r--r--vendor/github.com/pborman/uuid/hash.go53
-rw-r--r--vendor/github.com/pborman/uuid/json.go34
-rw-r--r--vendor/github.com/pborman/uuid/node.go117
-rw-r--r--vendor/github.com/pborman/uuid/sql.go66
-rw-r--r--vendor/github.com/pborman/uuid/time.go132
-rw-r--r--vendor/github.com/pborman/uuid/util.go43
-rw-r--r--vendor/github.com/pborman/uuid/uuid.go201
-rw-r--r--vendor/github.com/pborman/uuid/version1.go41
-rw-r--r--vendor/github.com/pborman/uuid/version4.go25
-rw-r--r--vendor/github.com/peterhellberg/giphy/client.go117
-rw-r--r--vendor/github.com/peterhellberg/giphy/cmd/giphy/main.go140
-rw-r--r--vendor/github.com/peterhellberg/giphy/env.go34
-rw-r--r--vendor/github.com/peterhellberg/giphy/errors.go17
-rw-r--r--vendor/github.com/peterhellberg/giphy/gif.go44
-rw-r--r--vendor/github.com/peterhellberg/giphy/random.go37
-rw-r--r--vendor/github.com/peterhellberg/giphy/search.go24
-rw-r--r--vendor/github.com/peterhellberg/giphy/translate.go37
-rw-r--r--vendor/github.com/peterhellberg/giphy/trending.go23
-rw-r--r--vendor/github.com/peterhellberg/giphy/types.go116
-rw-r--r--vendor/github.com/sorcix/irc/LICENSE22
-rw-r--r--vendor/github.com/sorcix/irc/constants.go298
-rw-r--r--vendor/github.com/sorcix/irc/ctcp/ctcp.go144
-rw-r--r--vendor/github.com/sorcix/irc/ctcp/doc.go31
-rw-r--r--vendor/github.com/sorcix/irc/doc.go36
-rw-r--r--vendor/github.com/sorcix/irc/message.go308
-rw-r--r--vendor/github.com/sorcix/irc/stream.go134
-rw-r--r--vendor/github.com/sorcix/irc/strings.go17
-rw-r--r--vendor/github.com/sorcix/irc/strings_legacy.go22
-rw-r--r--vendor/github.com/thoj/go-ircevent/LICENSE27
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc.go473
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc_callback.go221
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc_struct.go68
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go14
155 files changed, 20600 insertions, 0 deletions
diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE b/vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE
new file mode 100644
index 00000000..8f71f43f
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go b/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
new file mode 100644
index 00000000..ebd5456d
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
@@ -0,0 +1,441 @@
+package bridge
+
+import (
+ "crypto/tls"
+ "github.com/42wim/matterbridge-plus/matterclient"
+ "github.com/42wim/matterbridge/matterhook"
+ log "github.com/Sirupsen/logrus"
+ "github.com/peterhellberg/giphy"
+ ircm "github.com/sorcix/irc"
+ "github.com/thoj/go-ircevent"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+)
+
+//type Bridge struct {
+type MMhook struct {
+ mh *matterhook.Client
+}
+
+type MMapi struct {
+ mc *matterclient.MMClient
+ mmMap map[string]string
+}
+
+type MMirc struct {
+ i *irc.Connection
+ ircNick string
+ ircMap map[string]string
+ names map[string][]string
+}
+
+type MMMessage struct {
+ Text string
+ Channel string
+ Username string
+}
+
+type Bridge struct {
+ MMhook
+ MMapi
+ MMirc
+ *Config
+ kind string
+}
+
+type FancyLog struct {
+ irc *log.Entry
+ mm *log.Entry
+}
+
+var flog FancyLog
+
+func initFLog() {
+ flog.irc = log.WithFields(log.Fields{"module": "irc"})
+ flog.mm = log.WithFields(log.Fields{"module": "mattermost"})
+}
+
+func NewBridge(name string, config *Config, kind string) *Bridge {
+ initFLog()
+ b := &Bridge{}
+ b.Config = config
+ b.kind = kind
+ b.ircNick = b.Config.IRC.Nick
+ b.ircMap = make(map[string]string)
+ b.MMirc.names = make(map[string][]string)
+ if kind == "legacy" {
+ if len(b.Config.Token) > 0 {
+ for _, val := range b.Config.Token {
+ b.ircMap[val.IRCChannel] = val.MMChannel
+ }
+ }
+
+ b.mh = matterhook.New(b.Config.Mattermost.URL,
+ matterhook.Config{Port: b.Config.Mattermost.Port, Token: b.Config.Mattermost.Token,
+ InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify,
+ BindAddress: b.Config.Mattermost.BindAddress})
+ } else {
+ b.mmMap = make(map[string]string)
+ if len(b.Config.Channel) > 0 {
+ for _, val := range b.Config.Channel {
+ b.ircMap[val.IRC] = val.Mattermost
+ b.mmMap[val.Mattermost] = val.IRC
+ }
+ }
+ b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password,
+ b.Config.Mattermost.Team, b.Config.Mattermost.Server)
+ err := b.mc.Login()
+ if err != nil {
+ flog.mm.Fatal("can not connect", err)
+ }
+ b.mc.JoinChannel(b.Config.Mattermost.Channel)
+ if len(b.Config.Channel) > 0 {
+ for _, val := range b.Config.Channel {
+ b.mc.JoinChannel(val.Mattermost)
+ }
+ }
+ go b.mc.WsReceiver()
+ }
+ b.i = b.createIRC(name)
+ go b.handleMatter()
+ return b
+}
+
+func (b *Bridge) createIRC(name string) *irc.Connection {
+ i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick)
+ i.UseTLS = b.Config.IRC.UseTLS
+ i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify}
+ if b.Config.IRC.Password != "" {
+ i.Password = b.Config.IRC.Password
+ }
+ i.AddCallback("*", b.handleOther)
+ i.Connect(b.Config.IRC.Server + ":" + strconv.Itoa(b.Config.IRC.Port))
+ return i
+}
+
+func (b *Bridge) handleNewConnection(event *irc.Event) {
+ b.ircNick = event.Arguments[0]
+ b.setupChannels()
+}
+
+func (b *Bridge) setupChannels() {
+ i := b.i
+ flog.irc.Info("Joining ", b.Config.IRC.Channel, " as ", b.ircNick)
+ i.Join(b.Config.IRC.Channel)
+ if b.kind == "legacy" {
+ for _, val := range b.Config.Token {
+ flog.irc.Info("Joining ", val.IRCChannel, " as ", b.ircNick)
+ i.Join(val.IRCChannel)
+ }
+ } else {
+ for _, val := range b.Config.Channel {
+ flog.irc.Info("Joining ", val.IRC, " as ", b.ircNick)
+ i.Join(val.IRC)
+ }
+ }
+ i.AddCallback("PRIVMSG", b.handlePrivMsg)
+ i.AddCallback("CTCP_ACTION", b.handlePrivMsg)
+ if b.Config.Mattermost.ShowJoinPart {
+ i.AddCallback("JOIN", b.handleJoinPart)
+ i.AddCallback("PART", b.handleJoinPart)
+ }
+}
+
+func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool {
+ parts := strings.Fields(event.Message())
+ exp, _ := regexp.Compile("[:,]+$")
+ channel := event.Arguments[0]
+ command := ""
+ if len(parts) == 2 {
+ command = parts[1]
+ }
+ if exp.ReplaceAllString(parts[0], "") == b.ircNick {
+ switch command {
+ case "users":
+ usernames := b.mc.UsernamesInChannel(b.getMMChannel(channel))
+ sort.Strings(usernames)
+ b.i.Privmsg(channel, "Users on Mattermost: "+strings.Join(usernames, ", "))
+ default:
+ b.i.Privmsg(channel, "Valid commands are: [users, help]")
+ }
+ return true
+ }
+ return false
+}
+
+func (b *Bridge) ircNickFormat(nick string) string {
+ if nick == b.ircNick {
+ return nick
+ }
+ if b.Config.Mattermost.RemoteNickFormat == nil {
+ return "irc-" + nick
+ }
+ return strings.Replace(*b.Config.Mattermost.RemoteNickFormat, "{NICK}", nick, -1)
+}
+
+func (b *Bridge) handlePrivMsg(event *irc.Event) {
+ if b.handleIrcBotCommand(event) {
+ return
+ }
+ msg := ""
+ if event.Code == "CTCP_ACTION" {
+ msg = event.Nick + " "
+ }
+ msg += event.Message()
+ b.Send(b.ircNickFormat(event.Nick), msg, b.getMMChannel(event.Arguments[0]))
+}
+
+func (b *Bridge) handleJoinPart(event *irc.Event) {
+ b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0]))
+}
+
+func (b *Bridge) handleNotice(event *irc.Event) {
+ if strings.Contains(event.Message(), "This nickname is registered") {
+ b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword)
+ }
+}
+
+func (b *Bridge) nicksPerRow() int {
+ if b.Config.Mattermost.NicksPerRow < 1 {
+ return 4
+ }
+ return b.Config.Mattermost.NicksPerRow
+}
+
+func (b *Bridge) formatnicks(nicks []string, continued bool) string {
+ switch b.Config.Mattermost.NickFormatter {
+ case "table":
+ return tableformatter(nicks, b.nicksPerRow(), continued)
+ default:
+ return plainformatter(nicks, b.nicksPerRow())
+ }
+}
+
+func (b *Bridge) storeNames(event *irc.Event) {
+ channel := event.Arguments[2]
+ b.MMirc.names[channel] = append(
+ b.MMirc.names[channel],
+ strings.Split(strings.TrimSpace(event.Message()), " ")...)
+}
+
+func (b *Bridge) endNames(event *irc.Event) {
+ channel := event.Arguments[1]
+ sort.Strings(b.MMirc.names[channel])
+ maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
+ continued := false
+ for len(b.MMirc.names[channel]) > maxNamesPerPost {
+ b.Send(
+ b.ircNick,
+ b.formatnicks(b.MMirc.names[channel][0:maxNamesPerPost], continued),
+ b.getMMChannel(channel))
+ b.MMirc.names[channel] = b.MMirc.names[channel][maxNamesPerPost:]
+ continued = true
+ }
+ b.Send(b.ircNick, b.formatnicks(b.MMirc.names[channel], continued), b.getMMChannel(channel))
+ b.MMirc.names[channel] = nil
+}
+
+func (b *Bridge) handleTopicWhoTime(event *irc.Event) bool {
+ parts := strings.Split(event.Arguments[2], "!")
+ t_i, err := strconv.ParseInt(event.Arguments[3], 10, 64)
+ if err != nil {
+ flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3])
+ return false
+ }
+ user := parts[0]
+ if len(parts) > 1 {
+ user += " [" + parts[1] + "]"
+ }
+ flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t_i, 0))
+ return true
+}
+
+func (b *Bridge) handleOther(event *irc.Event) {
+ flog.irc.Debugf("%#v", event)
+ switch event.Code {
+ case ircm.RPL_WELCOME:
+ b.handleNewConnection(event)
+ case ircm.RPL_ENDOFNAMES:
+ b.endNames(event)
+ case ircm.RPL_NAMREPLY:
+ b.storeNames(event)
+ case ircm.RPL_ISUPPORT:
+ fallthrough
+ case ircm.RPL_LUSEROP:
+ fallthrough
+ case ircm.RPL_LUSERUNKNOWN:
+ fallthrough
+ case ircm.RPL_LUSERCHANNELS:
+ fallthrough
+ case ircm.RPL_MYINFO:
+ flog.irc.Infof("%s: %s", event.Code, strings.Join(event.Arguments[1:], " "))
+ case ircm.RPL_YOURHOST:
+ fallthrough
+ case ircm.RPL_CREATED:
+ fallthrough
+ case ircm.RPL_STATSDLINE:
+ fallthrough
+ case ircm.RPL_LUSERCLIENT:
+ fallthrough
+ case ircm.RPL_LUSERME:
+ fallthrough
+ case ircm.RPL_LOCALUSERS:
+ fallthrough
+ case ircm.RPL_GLOBALUSERS:
+ fallthrough
+ case ircm.RPL_MOTD:
+ flog.irc.Infof("%s: %s", event.Code, event.Message())
+ // flog.irc.Info(event.Message())
+ case ircm.RPL_TOPIC:
+ flog.irc.Infof("%s: Topic for %s: %s", event.Code, event.Arguments[1], event.Message())
+ case ircm.RPL_TOPICWHOTIME:
+ if !b.handleTopicWhoTime(event) {
+ break
+ }
+ case ircm.MODE:
+ flog.irc.Infof("%s: %s %s", event.Code, event.Arguments[1], event.Arguments[0])
+ case ircm.JOIN:
+ fallthrough
+ case ircm.PING:
+ fallthrough
+ case ircm.PONG:
+ flog.irc.Infof("%s: %s", event.Code, event.Message())
+ case ircm.RPL_ENDOFMOTD:
+ case ircm.RPL_MOTDSTART:
+ case ircm.ERR_NICKNAMEINUSE:
+ flog.irc.Warn(event.Message())
+ case ircm.NOTICE:
+ b.handleNotice(event)
+ default:
+ flog.irc.Infof("UNKNOWN EVENT: %#v", event)
+ return
+ }
+}
+
+func (b *Bridge) Send(nick string, message string, channel string) error {
+ return b.SendType(nick, message, channel, "")
+}
+
+func (b *Bridge) SendType(nick string, message string, channel string, mtype string) error {
+ if b.Config.Mattermost.PrefixMessagesWithNick {
+ if IsMarkup(message) {
+ message = nick + "\n\n" + message
+ } else {
+ message = nick + " " + message
+ }
+ }
+ if b.kind == "legacy" {
+ matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL}
+ matterMessage.Channel = channel
+ matterMessage.UserName = nick
+ matterMessage.Type = mtype
+ matterMessage.Text = message
+ err := b.mh.Send(matterMessage)
+ if err != nil {
+ flog.mm.Info(err)
+ return err
+ }
+ return nil
+ }
+ flog.mm.Debug("->mattermost channel: ", channel, " ", message)
+ b.mc.PostMessage(channel, message)
+ return nil
+}
+
+func (b *Bridge) handleMatterHook(mchan chan *MMMessage) {
+ for {
+ message := b.mh.Receive()
+ m := &MMMessage{}
+ m.Username = message.UserName
+ m.Text = message.Text
+ m.Channel = message.Token
+ mchan <- m
+ }
+}
+
+func (b *Bridge) handleMatterClient(mchan chan *MMMessage) {
+ for message := range b.mc.MessageChan {
+ // do not post our own messages back to irc
+ if message.Raw.Action == "posted" && b.mc.User.Username != message.Username {
+ m := &MMMessage{}
+ m.Username = message.Username
+ m.Channel = message.Channel
+ m.Text = message.Text
+ flog.mm.Debugf("<-mattermost channel: %s %#v %#v", message.Channel, message.Post, message.Raw)
+ mchan <- m
+ }
+ }
+}
+
+func (b *Bridge) handleMatter() {
+ mchan := make(chan *MMMessage)
+ if b.kind == "legacy" {
+ go b.handleMatterHook(mchan)
+ } else {
+ go b.handleMatterClient(mchan)
+ }
+ for message := range mchan {
+ var username string
+ username = message.Username + ": "
+ if b.Config.IRC.RemoteNickFormat != "" {
+ username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1)
+ } else if b.Config.IRC.UseSlackCircumfix {
+ username = "<" + message.Username + "> "
+ }
+ cmd := strings.Fields(message.Text)[0]
+ switch cmd {
+ case "!users":
+ flog.mm.Info("received !users from ", message.Username)
+ b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel))
+ continue
+ case "!gif":
+ message.Text = b.giphyRandom(strings.Fields(strings.Replace(message.Text, "!gif ", "", 1)))
+ b.Send(b.ircNick, message.Text, b.getIRCChannel(message.Channel))
+ continue
+ }
+ texts := strings.Split(message.Text, "\n")
+ for _, text := range texts {
+ flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel)
+ b.i.Privmsg(b.getIRCChannel(message.Channel), username+text)
+ }
+ }
+}
+
+func (b *Bridge) giphyRandom(query []string) string {
+ g := giphy.DefaultClient
+ if b.Config.General.GiphyAPIKey != "" {
+ g.APIKey = b.Config.General.GiphyAPIKey
+ }
+ res, err := g.Random(query)
+ if err != nil {
+ return "error"
+ }
+ return res.Data.FixedHeightDownsampledURL
+}
+
+func (b *Bridge) getMMChannel(ircChannel string) string {
+ mmchannel, ok := b.ircMap[ircChannel]
+ if !ok {
+ mmchannel = b.Config.Mattermost.Channel
+ }
+ return mmchannel
+}
+
+func (b *Bridge) getIRCChannel(channel string) string {
+ if b.kind == "legacy" {
+ ircchannel := b.Config.IRC.Channel
+ _, ok := b.Config.Token[channel]
+ if ok {
+ ircchannel = b.Config.Token[channel].IRCChannel
+ }
+ return ircchannel
+ }
+ ircchannel, ok := b.mmMap[channel]
+ if !ok {
+ ircchannel = b.Config.IRC.Channel
+ }
+ return ircchannel
+}
diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/config.go b/vendor/github.com/42wim/matterbridge-plus/bridge/config.go
new file mode 100644
index 00000000..ca4eb225
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge-plus/bridge/config.go
@@ -0,0 +1,65 @@
+package bridge
+
+import (
+ "gopkg.in/gcfg.v1"
+ "io/ioutil"
+ "log"
+)
+
+type Config struct {
+ IRC struct {
+ UseTLS bool
+ SkipTLSVerify bool
+ Server string
+ Port int
+ Nick string
+ Password string
+ Channel string
+ UseSlackCircumfix bool
+ NickServNick string
+ NickServPassword string
+ RemoteNickFormat string
+ }
+ Mattermost struct {
+ URL string
+ Port int
+ ShowJoinPart bool
+ Token string
+ IconURL string
+ SkipTLSVerify bool
+ BindAddress string
+ Channel string
+ PrefixMessagesWithNick bool
+ NicksPerRow int
+ NickFormatter string
+ Server string
+ Team string
+ Login string
+ Password string
+ RemoteNickFormat *string
+ }
+ Token map[string]*struct {
+ IRCChannel string
+ MMChannel string
+ }
+ Channel map[string]*struct {
+ IRC string
+ Mattermost string
+ }
+ General struct {
+ GiphyAPIKey string
+ }
+}
+
+func NewConfig(cfgfile string) *Config {
+ var cfg Config
+ content, err := ioutil.ReadFile(cfgfile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = gcfg.ReadStringInto(&cfg, string(content))
+ if err != nil {
+ log.Fatal("Failed to parse "+cfgfile+":", err)
+ }
+ return &cfg
+}
diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/helper.go b/vendor/github.com/42wim/matterbridge-plus/bridge/helper.go
new file mode 100644
index 00000000..7669ad57
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge-plus/bridge/helper.go
@@ -0,0 +1,59 @@
+package bridge
+
+import (
+ "strings"
+)
+
+func tableformatter(nicks []string, nicksPerRow int, continued bool) string {
+ result := "|IRC users"
+ if continued {
+ result = "|(continued)"
+ }
+ for i := 0; i < 2; i++ {
+ for j := 1; j <= nicksPerRow && j <= len(nicks); j++ {
+ if i == 0 {
+ result += "|"
+ } else {
+ result += ":-|"
+ }
+ }
+ result += "\r\n|"
+ }
+ result += nicks[0] + "|"
+ for i := 1; i < len(nicks); i++ {
+ if i%nicksPerRow == 0 {
+ result += "\r\n|" + nicks[i] + "|"
+ } else {
+ result += nicks[i] + "|"
+ }
+ }
+ return result
+}
+
+func plainformatter(nicks []string, nicksPerRow int) string {
+ return strings.Join(nicks, ", ") + " currently on IRC"
+}
+
+func IsMarkup(message string) bool {
+ switch message[0] {
+ case '|':
+ fallthrough
+ case '#':
+ fallthrough
+ case '_':
+ fallthrough
+ case '*':
+ fallthrough
+ case '~':
+ fallthrough
+ case '-':
+ fallthrough
+ case ':':
+ fallthrough
+ case '>':
+ fallthrough
+ case '=':
+ return true
+ }
+ return false
+}
diff --git a/vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE b/vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE
new file mode 100644
index 00000000..8f71f43f
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go b/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
new file mode 100644
index 00000000..11891d07
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
@@ -0,0 +1,316 @@
+package matterclient
+
+import (
+ "errors"
+ log "github.com/Sirupsen/logrus"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "github.com/jpillora/backoff"
+ "github.com/mattermost/platform/model"
+)
+
+type Credentials struct {
+ Login string
+ Team string
+ Pass string
+ Server string
+ NoTLS bool
+}
+
+type Message struct {
+ Raw *model.Message
+ Post *model.Post
+ Team string
+ Channel string
+ Username string
+ Text string
+}
+
+type MMClient struct {
+ *Credentials
+ Client *model.Client
+ WsClient *websocket.Conn
+ Channels *model.ChannelList
+ MoreChannels *model.ChannelList
+ User *model.User
+ Users map[string]*model.User
+ MessageChan chan *Message
+ Team *model.Team
+ log *log.Entry
+}
+
+func New(login, pass, team, server string) *MMClient {
+ cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server}
+ mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100)}
+ mmclient.log = log.WithFields(log.Fields{"module": "matterclient"})
+ log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
+ return mmclient
+}
+
+func (m *MMClient) SetLogLevel(level string) {
+ l, err := log.ParseLevel(level)
+ if err != nil {
+ log.SetLevel(log.InfoLevel)
+ return
+ }
+ log.SetLevel(l)
+}
+
+func (m *MMClient) Login() error {
+ b := &backoff.Backoff{
+ Min: time.Second,
+ Max: 5 * time.Minute,
+ Jitter: true,
+ }
+ uriScheme := "https://"
+ wsScheme := "wss://"
+ if m.NoTLS {
+ uriScheme = "http://"
+ wsScheme = "ws://"
+ }
+ // login to mattermost
+ m.Client = model.NewClient(uriScheme + m.Credentials.Server)
+ var myinfo *model.Result
+ var appErr *model.AppError
+ var logmsg = "trying login"
+ for {
+ m.log.Debugf(logmsg+" %s %s %s", m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
+ myinfo, appErr = m.Client.LoginByEmail(m.Credentials.Team, m.Credentials.Login, m.Credentials.Pass)
+ if appErr != nil {
+ d := b.Duration()
+ m.log.Debug(appErr.DetailedError)
+ if !strings.Contains(appErr.DetailedError, "connection refused") &&
+ !strings.Contains(appErr.DetailedError, "invalid character") {
+ if appErr.Message == "" {
+ return errors.New(appErr.DetailedError)
+ }
+ return errors.New(appErr.Message)
+ }
+ m.log.Debug("LOGIN: %s, reconnecting in %s", appErr, d)
+ time.Sleep(d)
+ logmsg = "retrying login"
+ continue
+ }
+ break
+ }
+ // reset timer
+ b.Reset()
+ m.User = myinfo.Data.(*model.User)
+ myinfo, _ = m.Client.GetMyTeam("")
+ m.Team = myinfo.Data.(*model.Team)
+
+ // setup websocket connection
+ wsurl := wsScheme + m.Credentials.Server + "/api/v1/websocket"
+ header := http.Header{}
+ header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken)
+
+ var WsClient *websocket.Conn
+ var err error
+ for {
+ WsClient, _, err = websocket.DefaultDialer.Dial(wsurl, header)
+ if err != nil {
+ d := b.Duration()
+ log.Printf("WSS: %s, reconnecting in %s", err, d)
+ time.Sleep(d)
+ continue
+ }
+ break
+ }
+ b.Reset()
+
+ m.WsClient = WsClient
+
+ // populating users
+ m.UpdateUsers()
+
+ // populating channels
+ m.UpdateChannels()
+
+ return nil
+}
+
+func (m *MMClient) WsReceiver() {
+ var rmsg model.Message
+ for {
+ if err := m.WsClient.ReadJSON(&rmsg); err != nil {
+ log.Println("error:", err)
+ // reconnect
+ m.Login()
+ }
+ //log.Printf("WsReceiver: %#v", rmsg)
+ msg := &Message{Raw: &rmsg, Team: m.Credentials.Team}
+ m.parseMessage(msg)
+ m.MessageChan <- msg
+ }
+
+}
+
+func (m *MMClient) parseMessage(rmsg *Message) {
+ switch rmsg.Raw.Action {
+ case model.ACTION_POSTED:
+ m.parseActionPost(rmsg)
+ /*
+ case model.ACTION_USER_REMOVED:
+ m.handleWsActionUserRemoved(&rmsg)
+ case model.ACTION_USER_ADDED:
+ m.handleWsActionUserAdded(&rmsg)
+ */
+ }
+}
+
+func (m *MMClient) parseActionPost(rmsg *Message) {
+ data := model.PostFromJson(strings.NewReader(rmsg.Raw.Props["post"]))
+ // log.Println("receiving userid", data.UserId)
+ // we don't have the user, refresh the userlist
+ if m.Users[data.UserId] == nil {
+ m.UpdateUsers()
+ }
+ rmsg.Username = m.Users[data.UserId].Username
+ rmsg.Channel = m.GetChannelName(data.ChannelId)
+ // direct message
+ if strings.Contains(rmsg.Channel, "__") {
+ //log.Println("direct message")
+ rcvusers := strings.Split(rmsg.Channel, "__")
+ if rcvusers[0] != m.User.Id {
+ rmsg.Channel = m.Users[rcvusers[0]].Username
+ } else {
+ rmsg.Channel = m.Users[rcvusers[1]].Username
+ }
+ }
+ rmsg.Text = data.Message
+ rmsg.Post = data
+ return
+}
+
+func (m *MMClient) UpdateUsers() error {
+ mmusers, _ := m.Client.GetProfiles(m.User.TeamId, "")
+ m.Users = mmusers.Data.(map[string]*model.User)
+ return nil
+}
+
+func (m *MMClient) UpdateChannels() error {
+ mmchannels, _ := m.Client.GetChannels("")
+ m.Channels = mmchannels.Data.(*model.ChannelList)
+ mmchannels, _ = m.Client.GetMoreChannels("")
+ m.MoreChannels = mmchannels.Data.(*model.ChannelList)
+ return nil
+}
+
+func (m *MMClient) GetChannelName(id string) string {
+ for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) {
+ if channel.Id == id {
+ return channel.Name
+ }
+ }
+ // not found? could be a new direct message from mattermost. Try to update and check again
+ m.UpdateChannels()
+ for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) {
+ if channel.Id == id {
+ return channel.Name
+ }
+ }
+ return ""
+}
+
+func (m *MMClient) GetChannelId(name string) string {
+ for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) {
+ if channel.Name == name {
+ return channel.Id
+ }
+ }
+ return ""
+}
+
+func (m *MMClient) GetChannelHeader(id string) string {
+ for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) {
+ if channel.Id == id {
+ return channel.Header
+ }
+ }
+ return ""
+}
+
+func (m *MMClient) PostMessage(channel string, text string) {
+ post := &model.Post{ChannelId: m.GetChannelId(channel), Message: text}
+ m.Client.CreatePost(post)
+}
+
+func (m *MMClient) JoinChannel(channel string) error {
+ cleanChan := strings.Replace(channel, "#", "", 1)
+ if m.GetChannelId(cleanChan) == "" {
+ return errors.New("failed to join")
+ }
+ for _, c := range m.Channels.Channels {
+ if c.Name == cleanChan {
+ m.log.Debug("Not joining ", cleanChan, " already joined.")
+ return nil
+ }
+ }
+ m.log.Debug("Joining ", cleanChan)
+ _, err := m.Client.JoinChannel(m.GetChannelId(cleanChan))
+ if err != nil {
+ return errors.New("failed to join")
+ }
+ // m.SyncChannel(m.getMMChannelId(strings.Replace(channel, "#", "", 1)), strings.Replace(channel, "#", "", 1))
+ return nil
+}
+
+func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList {
+ res, err := m.Client.GetPostsSince(channelId, time)
+ if err != nil {
+ return nil
+ }
+ return res.Data.(*model.PostList)
+}
+
+func (m *MMClient) SearchPosts(query string) *model.PostList {
+ res, err := m.Client.SearchPosts(query)
+ if err != nil {
+ return nil
+ }
+ return res.Data.(*model.PostList)
+}
+
+func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList {
+ res, err := m.Client.GetPosts(channelId, 0, limit, "")
+ if err != nil {
+ return nil
+ }
+ return res.Data.(*model.PostList)
+}
+
+func (m *MMClient) UpdateChannelHeader(channelId string, header string) {
+ data := make(map[string]string)
+ data["channel_id"] = channelId
+ data["channel_header"] = header
+ log.Printf("updating channelheader %#v, %#v", channelId, header)
+ _, err := m.Client.UpdateChannelHeader(data)
+ if err != nil {
+ log.Print(err)
+ }
+}
+
+func (m *MMClient) UpdateLastViewed(channelId string) {
+ log.Printf("posting lastview %#v", channelId)
+ _, err := m.Client.UpdateLastViewedAt(channelId)
+ if err != nil {
+ log.Print(err)
+ }
+}
+
+func (m *MMClient) UsernamesInChannel(channelName string) []string {
+ ceiRes, err := m.Client.GetChannelExtraInfo(m.GetChannelId(channelName), 5000, "")
+ if err != nil {
+ log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err)
+ return []string{}
+ }
+ extra := ceiRes.Data.(*model.ChannelExtra)
+ result := []string{}
+ for _, member := range extra.Members {
+ result = append(result, member.Username)
+ }
+ return result
+}
diff --git a/vendor/github.com/42wim/matterbridge/matterhook/LICENSE b/vendor/github.com/42wim/matterbridge/matterhook/LICENSE
new file mode 100644
index 00000000..8f71f43f
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge/matterhook/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/github.com/42wim/matterbridge/matterhook/matterhook.go b/vendor/github.com/42wim/matterbridge/matterhook/matterhook.go
new file mode 100644
index 00000000..0b023d39
--- /dev/null
+++ b/vendor/github.com/42wim/matterbridge/matterhook/matterhook.go
@@ -0,0 +1,154 @@
+//Package matterhook provides interaction with mattermost incoming/outgoing webhooks
+package matterhook
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "github.com/gorilla/schema"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "strconv"
+)
+
+// OMessage for mattermost incoming webhook. (send to mattermost)
+type OMessage struct {
+ Channel string `json:"channel,omitempty"`
+ IconURL string `json:"icon_url,omitempty"`
+ IconEmoji string `json:"icon_emoji,omitempty"`
+ UserName string `json:"username,omitempty"`
+ Text string `json:"text"`
+ Attachments interface{} `json:"attachments,omitempty"`
+ Type string `json:"type,omitempty"`
+}
+
+// IMessage for mattermost outgoing webhook. (received from mattermost)
+type IMessage struct {
+ Token string `schema:"token"`
+ TeamID string `schema:"team_id"`
+ TeamDomain string `schema:"team_domain"`
+ ChannelID string `schema:"channel_id"`
+ ServiceID string `schema:"service_id"`
+ ChannelName string `schema:"channel_name"`
+ Timestamp string `schema:"timestamp"`
+ UserID string `schema:"user_id"`
+ UserName string `schema:"user_name"`
+ Text string `schema:"text"`
+ TriggerWord string `schema:"trigger_word"`
+}
+
+// Client for Mattermost.
+type Client struct {
+ Url string // URL for incoming webhooks on mattermost.
+ In chan IMessage
+ Out chan OMessage
+ httpclient *http.Client
+ Config
+}
+
+// Config for client.
+type Config struct {
+ Port int // Port to listen on.
+ BindAddress string // Address to listen on
+ Token string // Only allow this token from Mattermost. (Allow everything when empty)
+ InsecureSkipVerify bool // disable certificate checking
+ DisableServer bool // Do not start server for outgoing webhooks from Mattermost.
+}
+
+// New Mattermost client.
+func New(url string, config Config) *Client {
+ c := &Client{Url: url, In: make(chan IMessage), Out: make(chan OMessage), Config: config}
+ if c.Port == 0 {
+ c.Port = 9999
+ }
+ c.BindAddress += ":"
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
+ }
+ c.httpclient = &http.Client{Transport: tr}
+ if !c.DisableServer {
+ go c.StartServer()
+ }
+ return c
+}
+
+// StartServer starts a webserver listening for incoming mattermost POSTS.
+func (c *Client) StartServer() {
+ mux := http.NewServeMux()
+ mux.Handle("/", c)
+ log.Printf("Listening on http://%v:%v...\n", c.BindAddress, c.Port)
+ if err := http.ListenAndServe((c.BindAddress + strconv.Itoa(c.Port)), mux); err != nil {
+ log.Fatal(err)
+ }
+}
+
+// ServeHTTP implementation.
+func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr)
+ http.NotFound(w, r)
+ return
+ }
+ msg := IMessage{}
+ err := r.ParseForm()
+ if err != nil {
+ log.Println(err)
+ http.NotFound(w, r)
+ return
+ }
+ defer r.Body.Close()
+ decoder := schema.NewDecoder()
+ err = decoder.Decode(&msg, r.PostForm)
+ if err != nil {
+ log.Println(err)
+ http.NotFound(w, r)
+ return
+ }
+ if msg.Token == "" {
+ log.Println("no token from " + r.RemoteAddr)
+ http.NotFound(w, r)
+ return
+ }
+ if c.Token != "" {
+ if msg.Token != c.Token {
+ log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
+ http.NotFound(w, r)
+ return
+ }
+ }
+ c.In <- msg
+}
+
+// Receive returns an incoming message from mattermost outgoing webhooks URL.
+func (c *Client) Receive() IMessage {
+ for {
+ select {
+ case msg := <-c.In:
+ return msg
+ }
+ }
+}
+
+// Send sends a msg to mattermost incoming webhooks URL.
+func (c *Client) Send(msg OMessage) error {
+ buf, err := json.Marshal(msg)
+ if err != nil {
+ return err
+ }
+ resp, err := c.httpclient.Post(c.Url, "application/json", bytes.NewReader(buf))
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ // Read entire body to completion to re-use keep-alive connections.
+ io.Copy(ioutil.Discard, resp.Body)
+
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+ }
+ return nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/LICENSE b/vendor/github.com/Sirupsen/logrus/LICENSE
new file mode 100644
index 00000000..f090cb42
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+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.
diff --git a/vendor/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/Sirupsen/logrus/doc.go
new file mode 100644
index 00000000..dddd5f87
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/doc.go
@@ -0,0 +1,26 @@
+/*
+Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
+
+
+The simplest way to use Logrus is simply the package-level exported logger:
+
+ package main
+
+ import (
+ log "github.com/Sirupsen/logrus"
+ )
+
+ func main() {
+ log.WithFields(log.Fields{
+ "animal": "walrus",
+ "number": 1,
+ "size": 10,
+ }).Info("A walrus appears")
+ }
+
+Output:
+ time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
+
+For a full guide visit https://github.com/Sirupsen/logrus
+*/
+package logrus
diff --git a/vendor/github.com/Sirupsen/logrus/entry.go b/vendor/github.com/Sirupsen/logrus/entry.go
new file mode 100644
index 00000000..89e966e7
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/entry.go
@@ -0,0 +1,264 @@
+package logrus
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "time"
+)
+
+// Defines the key when adding errors using WithError.
+var ErrorKey = "error"
+
+// An entry is the final or intermediate Logrus logging entry. It contains all
+// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
+// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
+// passed around as much as you wish to avoid field duplication.
+type Entry struct {
+ Logger *Logger
+
+ // Contains all the fields set by the user.
+ Data Fields
+
+ // Time at which the log entry was created
+ Time time.Time
+
+ // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
+ Level Level
+
+ // Message passed to Debug, Info, Warn, Error, Fatal or Panic
+ Message string
+}
+
+func NewEntry(logger *Logger) *Entry {
+ return &Entry{
+ Logger: logger,
+ // Default is three fields, give a little extra room
+ Data: make(Fields, 5),
+ }
+}
+
+// Returns a reader for the entry, which is a proxy to the formatter.
+func (entry *Entry) Reader() (*bytes.Buffer, error) {
+ serialized, err := entry.Logger.Formatter.Format(entry)
+ return bytes.NewBuffer(serialized), err
+}
+
+// Returns the string representation from the reader and ultimately the
+// formatter.
+func (entry *Entry) String() (string, error) {
+ reader, err := entry.Reader()
+ if err != nil {
+ return "", err
+ }
+
+ return reader.String(), err
+}
+
+// Add an error as single field (using the key defined in ErrorKey) to the Entry.
+func (entry *Entry) WithError(err error) *Entry {
+ return entry.WithField(ErrorKey, err)
+}
+
+// Add a single field to the Entry.
+func (entry *Entry) WithField(key string, value interface{}) *Entry {
+ return entry.WithFields(Fields{key: value})
+}
+
+// Add a map of fields to the Entry.
+func (entry *Entry) WithFields(fields Fields) *Entry {
+ data := make(Fields, len(entry.Data)+len(fields))
+ for k, v := range entry.Data {
+ data[k] = v
+ }
+ for k, v := range fields {
+ data[k] = v
+ }
+ return &Entry{Logger: entry.Logger, Data: data}
+}
+
+// This function is not declared with a pointer value because otherwise
+// race conditions will occur when using multiple goroutines
+func (entry Entry) log(level Level, msg string) {
+ entry.Time = time.Now()
+ entry.Level = level
+ entry.Message = msg
+
+ if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
+ entry.Logger.mu.Lock()
+ fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
+ entry.Logger.mu.Unlock()
+ }
+
+ reader, err := entry.Reader()
+ if err != nil {
+ entry.Logger.mu.Lock()
+ fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
+ entry.Logger.mu.Unlock()
+ }
+
+ entry.Logger.mu.Lock()
+ defer entry.Logger.mu.Unlock()
+
+ _, err = io.Copy(entry.Logger.Out, reader)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
+ }
+
+ // To avoid Entry#log() returning a value that only would make sense for
+ // panic() to use in Entry#Panic(), we avoid the allocation by checking
+ // directly here.
+ if level <= PanicLevel {
+ panic(&entry)
+ }
+}
+
+func (entry *Entry) Debug(args ...interface{}) {
+ if entry.Logger.Level >= DebugLevel {
+ entry.log(DebugLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Print(args ...interface{}) {
+ entry.Info(args...)
+}
+
+func (entry *Entry) Info(args ...interface{}) {
+ if entry.Logger.Level >= InfoLevel {
+ entry.log(InfoLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Warn(args ...interface{}) {
+ if entry.Logger.Level >= WarnLevel {
+ entry.log(WarnLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Warning(args ...interface{}) {
+ entry.Warn(args...)
+}
+
+func (entry *Entry) Error(args ...interface{}) {
+ if entry.Logger.Level >= ErrorLevel {
+ entry.log(ErrorLevel, fmt.Sprint(args...))
+ }
+}
+
+func (entry *Entry) Fatal(args ...interface{}) {
+ if entry.Logger.Level >= FatalLevel {
+ entry.log(FatalLevel, fmt.Sprint(args...))
+ }
+ os.Exit(1)
+}
+
+func (entry *Entry) Panic(args ...interface{}) {
+ if entry.Logger.Level >= PanicLevel {
+ entry.log(PanicLevel, fmt.Sprint(args...))
+ }
+ panic(fmt.Sprint(args...))
+}
+
+// Entry Printf family functions
+
+func (entry *Entry) Debugf(format string, args ...interface{}) {
+ if entry.Logger.Level >= DebugLevel {
+ entry.Debug(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Infof(format string, args ...interface{}) {
+ if entry.Logger.Level >= InfoLevel {
+ entry.Info(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Printf(format string, args ...interface{}) {
+ entry.Infof(format, args...)
+}
+
+func (entry *Entry) Warnf(format string, args ...interface{}) {
+ if entry.Logger.Level >= WarnLevel {
+ entry.Warn(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Warningf(format string, args ...interface{}) {
+ entry.Warnf(format, args...)
+}
+
+func (entry *Entry) Errorf(format string, args ...interface{}) {
+ if entry.Logger.Level >= ErrorLevel {
+ entry.Error(fmt.Sprintf(format, args...))
+ }
+}
+
+func (entry *Entry) Fatalf(format string, args ...interface{}) {
+ if entry.Logger.Level >= FatalLevel {
+ entry.Fatal(fmt.Sprintf(format, args...))
+ }
+ os.Exit(1)
+}
+
+func (entry *Entry) Panicf(format string, args ...interface{}) {
+ if entry.Logger.Level >= PanicLevel {
+ entry.Panic(fmt.Sprintf(format, args...))
+ }
+}
+
+// Entry Println family functions
+
+func (entry *Entry) Debugln(args ...interface{}) {
+ if entry.Logger.Level >= DebugLevel {
+ entry.Debug(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Infoln(args ...interface{}) {
+ if entry.Logger.Level >= InfoLevel {
+ entry.Info(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Println(args ...interface{}) {
+ entry.Infoln(args...)
+}
+
+func (entry *Entry) Warnln(args ...interface{}) {
+ if entry.Logger.Level >= WarnLevel {
+ entry.Warn(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Warningln(args ...interface{}) {
+ entry.Warnln(args...)
+}
+
+func (entry *Entry) Errorln(args ...interface{}) {
+ if entry.Logger.Level >= ErrorLevel {
+ entry.Error(entry.sprintlnn(args...))
+ }
+}
+
+func (entry *Entry) Fatalln(args ...interface{}) {
+ if entry.Logger.Level >= FatalLevel {
+ entry.Fatal(entry.sprintlnn(args...))
+ }
+ os.Exit(1)
+}
+
+func (entry *Entry) Panicln(args ...interface{}) {
+ if entry.Logger.Level >= PanicLevel {
+ entry.Panic(entry.sprintlnn(args...))
+ }
+}
+
+// Sprintlnn => Sprint no newline. This is to get the behavior of how
+// fmt.Sprintln where spaces are always added between operands, regardless of
+// their type. Instead of vendoring the Sprintln implementation to spare a
+// string allocation, we do the simplest thing.
+func (entry *Entry) sprintlnn(args ...interface{}) string {
+ msg := fmt.Sprintln(args...)
+ return msg[:len(msg)-1]
+}
diff --git a/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go b/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
new file mode 100644
index 00000000..a1623ec0
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "github.com/Sirupsen/logrus"
+)
+
+var log = logrus.New()
+
+func init() {
+ log.Formatter = new(logrus.JSONFormatter)
+ log.Formatter = new(logrus.TextFormatter) // default
+ log.Level = logrus.DebugLevel
+}
+
+func main() {
+ defer func() {
+ err := recover()
+ if err != nil {
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "err": err,
+ "number": 100,
+ }).Fatal("The ice breaks!")
+ }
+ }()
+
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "number": 8,
+ }).Debug("Started observing beach")
+
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "size": 10,
+ }).Info("A group of walrus emerges from the ocean")
+
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "number": 122,
+ }).Warn("The group's number increased tremendously!")
+
+ log.WithFields(logrus.Fields{
+ "temperature": -4,
+ }).Debug("Temperature changes")
+
+ log.WithFields(logrus.Fields{
+ "animal": "orca",
+ "size": 9009,
+ }).Panic("It's over 9000!")
+}
diff --git a/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go b/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
new file mode 100644
index 00000000..3187f6d3
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+ "github.com/Sirupsen/logrus"
+ "gopkg.in/gemnasium/logrus-airbrake-hook.v2"
+)
+
+var log = logrus.New()
+
+func init() {
+ log.Formatter = new(logrus.TextFormatter) // default
+ log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
+}
+
+func main() {
+ log.WithFields(logrus.Fields{
+ "animal": "walrus",
+ "size": 10,
+ }).Info("A group of walrus emerges from the ocean")
+
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "number": 122,
+ }).Warn("The group's number increased tremendously!")
+
+ log.WithFields(logrus.Fields{
+ "omg": true,
+ "number": 100,
+ }).Fatal("The ice breaks!")
+}
diff --git a/vendor/github.com/Sirupsen/logrus/exported.go b/vendor/github.com/Sirupsen/logrus/exported.go
new file mode 100644
index 00000000..9a0120ac
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/exported.go
@@ -0,0 +1,193 @@
+package logrus
+
+import (
+ "io"
+)
+
+var (
+ // std is the name of the standard logger in stdlib `log`
+ std = New()
+)
+
+func StandardLogger() *Logger {
+ return std
+}
+
+// SetOutput sets the standard logger output.
+func SetOutput(out io.Writer) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Out = out
+}
+
+// SetFormatter sets the standard logger formatter.
+func SetFormatter(formatter Formatter) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Formatter = formatter
+}
+
+// SetLevel sets the standard logger level.
+func SetLevel(level Level) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Level = level
+}
+
+// GetLevel returns the standard logger level.
+func GetLevel() Level {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ return std.Level
+}
+
+// AddHook adds a hook to the standard logger hooks.
+func AddHook(hook Hook) {
+ std.mu.Lock()
+ defer std.mu.Unlock()
+ std.Hooks.Add(hook)
+}
+
+// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
+func WithError(err error) *Entry {
+ return std.WithField(ErrorKey, err)
+}
+
+// WithField creates an entry from the standard logger and adds a field to
+// it. If you want multiple fields, use `WithFields`.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithField(key string, value interface{}) *Entry {
+ return std.WithField(key, value)
+}
+
+// WithFields creates an entry from the standard logger and adds multiple
+// fields to it. This is simply a helper for `WithField`, invoking it
+// once for each field.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithFields(fields Fields) *Entry {
+ return std.WithFields(fields)
+}
+
+// Debug logs a message at level Debug on the standard logger.
+func Debug(args ...interface{}) {
+ std.Debug(args...)
+}
+
+// Print logs a message at level Info on the standard logger.
+func Print(args ...interface{}) {
+ std.Print(args...)
+}
+
+// Info logs a message at level Info on the standard logger.
+func Info(args ...interface{}) {
+ std.Info(args...)
+}
+
+// Warn logs a message at level Warn on the standard logger.
+func Warn(args ...interface{}) {
+ std.Warn(args...)
+}
+
+// Warning logs a message at level Warn on the standard logger.
+func Warning(args ...interface{}) {
+ std.Warning(args...)
+}
+
+// Error logs a message at level Error on the standard logger.
+func Error(args ...interface{}) {
+ std.Error(args...)
+}
+
+// Panic logs a message at level Panic on the standard logger.
+func Panic(args ...interface{}) {
+ std.Panic(args...)
+}
+
+// Fatal logs a message at level Fatal on the standard logger.
+func Fatal(args ...interface{}) {
+ std.Fatal(args...)
+}
+
+// Debugf logs a message at level Debug on the standard logger.
+func Debugf(format string, args ...interface{}) {
+ std.Debugf(format, args...)
+}
+
+// Printf logs a message at level Info on the standard logger.
+func Printf(format string, args ...interface{}) {
+ std.Printf(format, args...)
+}
+
+// Infof logs a message at level Info on the standard logger.
+func Infof(format string, args ...interface{}) {
+ std.Infof(format, args...)
+}
+
+// Warnf logs a message at level Warn on the standard logger.
+func Warnf(format string, args ...interface{}) {
+ std.Warnf(format, args...)
+}
+
+// Warningf logs a message at level Warn on the standard logger.
+func Warningf(format string, args ...interface{}) {
+ std.Warningf(format, args...)
+}
+
+// Errorf logs a message at level Error on the standard logger.
+func Errorf(format string, args ...interface{}) {
+ std.Errorf(format, args...)
+}
+
+// Panicf logs a message at level Panic on the standard logger.
+func Panicf(format string, args ...interface{}) {
+ std.Panicf(format, args...)
+}
+
+// Fatalf logs a message at level Fatal on the standard logger.
+func Fatalf(format string, args ...interface{}) {
+ std.Fatalf(format, args...)
+}
+
+// Debugln logs a message at level Debug on the standard logger.
+func Debugln(args ...interface{}) {
+ std.Debugln(args...)
+}
+
+// Println logs a message at level Info on the standard logger.
+func Println(args ...interface{}) {
+ std.Println(args...)
+}
+
+// Infoln logs a message at level Info on the standard logger.
+func Infoln(args ...interface{}) {
+ std.Infoln(args...)
+}
+
+// Warnln logs a message at level Warn on the standard logger.
+func Warnln(args ...interface{}) {
+ std.Warnln(args...)
+}
+
+// Warningln logs a message at level Warn on the standard logger.
+func Warningln(args ...interface{}) {
+ std.Warningln(args...)
+}
+
+// Errorln logs a message at level Error on the standard logger.
+func Errorln(args ...interface{}) {
+ std.Errorln(args...)
+}
+
+// Panicln logs a message at level Panic on the standard logger.
+func Panicln(args ...interface{}) {
+ std.Panicln(args...)
+}
+
+// Fatalln logs a message at level Fatal on the standard logger.
+func Fatalln(args ...interface{}) {
+ std.Fatalln(args...)
+}
diff --git a/vendor/github.com/Sirupsen/logrus/formatter.go b/vendor/github.com/Sirupsen/logrus/formatter.go
new file mode 100644
index 00000000..104d689f
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/formatter.go
@@ -0,0 +1,48 @@
+package logrus
+
+import "time"
+
+const DefaultTimestampFormat = time.RFC3339
+
+// The Formatter interface is used to implement a custom Formatter. It takes an
+// `Entry`. It exposes all the fields, including the default ones:
+//
+// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
+// * `entry.Data["time"]`. The timestamp.
+// * `entry.Data["level"]. The level the entry was logged at.
+//
+// Any additional fields added with `WithField` or `WithFields` are also in
+// `entry.Data`. Format is expected to return an array of bytes which are then
+// logged to `logger.Out`.
+type Formatter interface {
+ Format(*Entry) ([]byte, error)
+}
+
+// This is to not silently overwrite `time`, `msg` and `level` fields when
+// dumping it. If this code wasn't there doing:
+//
+// logrus.WithField("level", 1).Info("hello")
+//
+// Would just silently drop the user provided level. Instead with this code
+// it'll logged as:
+//
+// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
+//
+// It's not exported because it's still using Data in an opinionated way. It's to
+// avoid code duplication between the two default formatters.
+func prefixFieldClashes(data Fields) {
+ _, ok := data["time"]
+ if ok {
+ data["fields.time"] = data["time"]
+ }
+
+ _, ok = data["msg"]
+ if ok {
+ data["fields.msg"] = data["msg"]
+ }
+
+ _, ok = data["level"]
+ if ok {
+ data["fields.level"] = data["level"]
+ }
+}
diff --git a/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
new file mode 100644
index 00000000..aad646ab
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go
@@ -0,0 +1,61 @@
+package logstash
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/Sirupsen/logrus"
+)
+
+// Formatter generates json in logstash format.
+// Logstash site: http://logstash.net/
+type LogstashFormatter struct {
+ Type string // if not empty use for logstash type field.
+
+ // TimestampFormat sets the format used for timestamps.
+ TimestampFormat string
+}
+
+func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ fields := make(logrus.Fields)
+ for k, v := range entry.Data {
+ fields[k] = v
+ }
+
+ fields["@version"] = 1
+
+ if f.TimestampFormat == "" {
+ f.TimestampFormat = logrus.DefaultTimestampFormat
+ }
+
+ fields["@timestamp"] = entry.Time.Format(f.TimestampFormat)
+
+ // set message field
+ v, ok := entry.Data["message"]
+ if ok {
+ fields["fields.message"] = v
+ }
+ fields["message"] = entry.Message
+
+ // set level field
+ v, ok = entry.Data["level"]
+ if ok {
+ fields["fields.level"] = v
+ }
+ fields["level"] = entry.Level.String()
+
+ // set type field
+ if f.Type != "" {
+ v, ok = entry.Data["type"]
+ if ok {
+ fields["fields.type"] = v
+ }
+ fields["type"] = f.Type
+ }
+
+ serialized, err := json.Marshal(fields)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ }
+ return append(serialized, '\n'), nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/hooks.go b/vendor/github.com/Sirupsen/logrus/hooks.go
new file mode 100644
index 00000000..3f151cdc
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/hooks.go
@@ -0,0 +1,34 @@
+package logrus
+
+// A hook to be fired when logging on the logging levels returned from
+// `Levels()` on your implementation of the interface. Note that this is not
+// fired in a goroutine or a channel with workers, you should handle such
+// functionality yourself if your call is non-blocking and you don't wish for
+// the logging calls for levels returned from `Levels()` to block.
+type Hook interface {
+ Levels() []Level
+ Fire(*Entry) error
+}
+
+// Internal type for storing the hooks on a logger instance.
+type LevelHooks map[Level][]Hook
+
+// Add a hook to an instance of logger. This is called with
+// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
+func (hooks LevelHooks) Add(hook Hook) {
+ for _, level := range hook.Levels() {
+ hooks[level] = append(hooks[level], hook)
+ }
+}
+
+// Fire all the hooks for the passed level. Used by `entry.log` to fire
+// appropriate hooks for a log entry.
+func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
+ for _, hook := range hooks[level] {
+ if err := hook.Fire(entry); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go b/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
new file mode 100644
index 00000000..a36e2003
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go
@@ -0,0 +1,54 @@
+// +build !windows,!nacl,!plan9
+
+package logrus_syslog
+
+import (
+ "fmt"
+ "github.com/Sirupsen/logrus"
+ "log/syslog"
+ "os"
+)
+
+// SyslogHook to send logs via syslog.
+type SyslogHook struct {
+ Writer *syslog.Writer
+ SyslogNetwork string
+ SyslogRaddr string
+}
+
+// Creates a hook to be added to an instance of logger. This is called with
+// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
+// `if err == nil { log.Hooks.Add(hook) }`
+func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
+ w, err := syslog.Dial(network, raddr, priority, tag)
+ return &SyslogHook{w, network, raddr}, err
+}
+
+func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
+ line, err := entry.String()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
+ return err
+ }
+
+ switch entry.Level {
+ case logrus.PanicLevel:
+ return hook.Writer.Crit(line)
+ case logrus.FatalLevel:
+ return hook.Writer.Crit(line)
+ case logrus.ErrorLevel:
+ return hook.Writer.Err(line)
+ case logrus.WarnLevel:
+ return hook.Writer.Warning(line)
+ case logrus.InfoLevel:
+ return hook.Writer.Info(line)
+ case logrus.DebugLevel:
+ return hook.Writer.Debug(line)
+ default:
+ return nil
+ }
+}
+
+func (hook *SyslogHook) Levels() []logrus.Level {
+ return logrus.AllLevels
+}
diff --git a/vendor/github.com/Sirupsen/logrus/hooks/test/test.go b/vendor/github.com/Sirupsen/logrus/hooks/test/test.go
new file mode 100644
index 00000000..06881253
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/hooks/test/test.go
@@ -0,0 +1,67 @@
+package test
+
+import (
+ "io/ioutil"
+
+ "github.com/Sirupsen/logrus"
+)
+
+// test.Hook is a hook designed for dealing with logs in test scenarios.
+type Hook struct {
+ Entries []*logrus.Entry
+}
+
+// Installs a test hook for the global logger.
+func NewGlobal() *Hook {
+
+ hook := new(Hook)
+ logrus.AddHook(hook)
+
+ return hook
+
+}
+
+// Installs a test hook for a given local logger.
+func NewLocal(logger *logrus.Logger) *Hook {
+
+ hook := new(Hook)
+ logger.Hooks.Add(hook)
+
+ return hook
+
+}
+
+// Creates a discarding logger and installs the test hook.
+func NewNullLogger() (*logrus.Logger, *Hook) {
+
+ logger := logrus.New()
+ logger.Out = ioutil.Discard
+
+ return logger, NewLocal(logger)
+
+}
+
+func (t *Hook) Fire(e *logrus.Entry) error {
+ t.Entries = append(t.Entries, e)
+ return nil
+}
+
+func (t *Hook) Levels() []logrus.Level {
+ return logrus.AllLevels
+}
+
+// LastEntry returns the last entry that was logged or nil.
+func (t *Hook) LastEntry() (l *logrus.Entry) {
+
+ if i := len(t.Entries) - 1; i < 0 {
+ return nil
+ } else {
+ return t.Entries[i]
+ }
+
+}
+
+// Reset removes all Entries from this test hook.
+func (t *Hook) Reset() {
+ t.Entries = make([]*logrus.Entry, 0)
+}
diff --git a/vendor/github.com/Sirupsen/logrus/json_formatter.go b/vendor/github.com/Sirupsen/logrus/json_formatter.go
new file mode 100644
index 00000000..2ad6dc5c
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/json_formatter.go
@@ -0,0 +1,41 @@
+package logrus
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+type JSONFormatter struct {
+ // TimestampFormat sets the format used for marshaling timestamps.
+ TimestampFormat string
+}
+
+func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
+ data := make(Fields, len(entry.Data)+3)
+ for k, v := range entry.Data {
+ switch v := v.(type) {
+ case error:
+ // Otherwise errors are ignored by `encoding/json`
+ // https://github.com/Sirupsen/logrus/issues/137
+ data[k] = v.Error()
+ default:
+ data[k] = v
+ }
+ }
+ prefixFieldClashes(data)
+
+ timestampFormat := f.TimestampFormat
+ if timestampFormat == "" {
+ timestampFormat = DefaultTimestampFormat
+ }
+
+ data["time"] = entry.Time.Format(timestampFormat)
+ data["msg"] = entry.Message
+ data["level"] = entry.Level.String()
+
+ serialized, err := json.Marshal(data)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ }
+ return append(serialized, '\n'), nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/logger.go b/vendor/github.com/Sirupsen/logrus/logger.go
new file mode 100644
index 00000000..2fdb2317
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/logger.go
@@ -0,0 +1,212 @@
+package logrus
+
+import (
+ "io"
+ "os"
+ "sync"
+)
+
+type Logger struct {
+ // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
+ // file, or leave it default which is `os.Stderr`. You can also set this to
+ // something more adventorous, such as logging to Kafka.
+ Out io.Writer
+ // Hooks for the logger instance. These allow firing events based on logging
+ // levels and log entries. For example, to send errors to an error tracking
+ // service, log to StatsD or dump the core on fatal errors.
+ Hooks LevelHooks
+ // All log entries pass through the formatter before logged to Out. The
+ // included formatters are `TextFormatter` and `JSONFormatter` for which
+ // TextFormatter is the default. In development (when a TTY is attached) it
+ // logs with colors, but to a file it wouldn't. You can easily implement your
+ // own that implements the `Formatter` interface, see the `README` or included
+ // formatters for examples.
+ Formatter Formatter
+ // The logging level the logger should log at. This is typically (and defaults
+ // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
+ // logged. `logrus.Debug` is useful in
+ Level Level
+ // Used to sync writing to the log.
+ mu sync.Mutex
+}
+
+// Creates a new logger. Configuration should be set by changing `Formatter`,
+// `Out` and `Hooks` directly on the default logger instance. You can also just
+// instantiate your own:
+//
+// var log = &Logger{
+// Out: os.Stderr,
+// Formatter: new(JSONFormatter),
+// Hooks: make(LevelHooks),
+// Level: logrus.DebugLevel,
+// }
+//
+// It's recommended to make this a global instance called `log`.
+func New() *Logger {
+ return &Logger{
+ Out: os.Stderr,
+ Formatter: new(TextFormatter),
+ Hooks: make(LevelHooks),
+ Level: InfoLevel,
+ }
+}
+
+// Adds a field to the log entry, note that you it doesn't log until you call
+// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
+// If you want multiple fields, use `WithFields`.
+func (logger *Logger) WithField(key string, value interface{}) *Entry {
+ return NewEntry(logger).WithField(key, value)
+}
+
+// Adds a struct of fields to the log entry. All it does is call `WithField` for
+// each `Field`.
+func (logger *Logger) WithFields(fields Fields) *Entry {
+ return NewEntry(logger).WithFields(fields)
+}
+
+// Add an error as single field to the log entry. All it does is call
+// `WithError` for the given `error`.
+func (logger *Logger) WithError(err error) *Entry {
+ return NewEntry(logger).WithError(err)
+}
+
+func (logger *Logger) Debugf(format string, args ...interface{}) {
+ if logger.Level >= DebugLevel {
+ NewEntry(logger).Debugf(format, args...)
+ }
+}
+
+func (logger *Logger) Infof(format string, args ...interface{}) {
+ if logger.Level >= InfoLevel {
+ NewEntry(logger).Infof(format, args...)
+ }
+}
+
+func (logger *Logger) Printf(format string, args ...interface{}) {
+ NewEntry(logger).Printf(format, args...)
+}
+
+func (logger *Logger) Warnf(format string, args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnf(format, args...)
+ }
+}
+
+func (logger *Logger) Warningf(format string, args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnf(format, args...)
+ }
+}
+
+func (logger *Logger) Errorf(format string, args ...interface{}) {
+ if logger.Level >= ErrorLevel {
+ NewEntry(logger).Errorf(format, args...)
+ }
+}
+
+func (logger *Logger) Fatalf(format string, args ...interface{}) {
+ if logger.Level >= FatalLevel {
+ NewEntry(logger).Fatalf(format, args...)
+ }
+ os.Exit(1)
+}
+
+func (logger *Logger) Panicf(format string, args ...interface{}) {
+ if logger.Level >= PanicLevel {
+ NewEntry(logger).Panicf(format, args...)
+ }
+}
+
+func (logger *Logger) Debug(args ...interface{}) {
+ if logger.Level >= DebugLevel {
+ NewEntry(logger).Debug(args...)
+ }
+}
+
+func (logger *Logger) Info(args ...interface{}) {
+ if logger.Level >= InfoLevel {
+ NewEntry(logger).Info(args...)
+ }
+}
+
+func (logger *Logger) Print(args ...interface{}) {
+ NewEntry(logger).Info(args...)
+}
+
+func (logger *Logger) Warn(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warn(args...)
+ }
+}
+
+func (logger *Logger) Warning(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warn(args...)
+ }
+}
+
+func (logger *Logger) Error(args ...interface{}) {
+ if logger.Level >= ErrorLevel {
+ NewEntry(logger).Error(args...)
+ }
+}
+
+func (logger *Logger) Fatal(args ...interface{}) {
+ if logger.Level >= FatalLevel {
+ NewEntry(logger).Fatal(args...)
+ }
+ os.Exit(1)
+}
+
+func (logger *Logger) Panic(args ...interface{}) {
+ if logger.Level >= PanicLevel {
+ NewEntry(logger).Panic(args...)
+ }
+}
+
+func (logger *Logger) Debugln(args ...interface{}) {
+ if logger.Level >= DebugLevel {
+ NewEntry(logger).Debugln(args...)
+ }
+}
+
+func (logger *Logger) Infoln(args ...interface{}) {
+ if logger.Level >= InfoLevel {
+ NewEntry(logger).Infoln(args...)
+ }
+}
+
+func (logger *Logger) Println(args ...interface{}) {
+ NewEntry(logger).Println(args...)
+}
+
+func (logger *Logger) Warnln(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnln(args...)
+ }
+}
+
+func (logger *Logger) Warningln(args ...interface{}) {
+ if logger.Level >= WarnLevel {
+ NewEntry(logger).Warnln(args...)
+ }
+}
+
+func (logger *Logger) Errorln(args ...interface{}) {
+ if logger.Level >= ErrorLevel {
+ NewEntry(logger).Errorln(args...)
+ }
+}
+
+func (logger *Logger) Fatalln(args ...interface{}) {
+ if logger.Level >= FatalLevel {
+ NewEntry(logger).Fatalln(args...)
+ }
+ os.Exit(1)
+}
+
+func (logger *Logger) Panicln(args ...interface{}) {
+ if logger.Level >= PanicLevel {
+ NewEntry(logger).Panicln(args...)
+ }
+}
diff --git a/vendor/github.com/Sirupsen/logrus/logrus.go b/vendor/github.com/Sirupsen/logrus/logrus.go
new file mode 100644
index 00000000..e5966911
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/logrus.go
@@ -0,0 +1,143 @@
+package logrus
+
+import (
+ "fmt"
+ "log"
+ "strings"
+)
+
+// Fields type, used to pass to `WithFields`.
+type Fields map[string]interface{}
+
+// Level type
+type Level uint8
+
+// Convert the Level to a string. E.g. PanicLevel becomes "panic".
+func (level Level) String() string {
+ switch level {
+ case DebugLevel:
+ return "debug"
+ case InfoLevel:
+ return "info"
+ case WarnLevel:
+ return "warning"
+ case ErrorLevel:
+ return "error"
+ case FatalLevel:
+ return "fatal"
+ case PanicLevel:
+ return "panic"
+ }
+
+ return "unknown"
+}
+
+// ParseLevel takes a string level and returns the Logrus log level constant.
+func ParseLevel(lvl string) (Level, error) {
+ switch strings.ToLower(lvl) {
+ case "panic":
+ return PanicLevel, nil
+ case "fatal":
+ return FatalLevel, nil
+ case "error":
+ return ErrorLevel, nil
+ case "warn", "warning":
+ return WarnLevel, nil
+ case "info":
+ return InfoLevel, nil
+ case "debug":
+ return DebugLevel, nil
+ }
+
+ var l Level
+ return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
+}
+
+// A constant exposing all logging levels
+var AllLevels = []Level{
+ PanicLevel,
+ FatalLevel,
+ ErrorLevel,
+ WarnLevel,
+ InfoLevel,
+ DebugLevel,
+}
+
+// These are the different logging levels. You can set the logging level to log
+// on your instance of logger, obtained with `logrus.New()`.
+const (
+ // PanicLevel level, highest level of severity. Logs and then calls panic with the
+ // message passed to Debug, Info, ...
+ PanicLevel Level = iota
+ // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
+ // logging level is set to Panic.
+ FatalLevel
+ // ErrorLevel level. Logs. Used for errors that should definitely be noted.
+ // Commonly used for hooks to send errors to an error tracking service.
+ ErrorLevel
+ // WarnLevel level. Non-critical entries that deserve eyes.
+ WarnLevel
+ // InfoLevel level. General operational entries about what's going on inside the
+ // application.
+ InfoLevel
+ // DebugLevel level. Usually only enabled when debugging. Very verbose logging.
+ DebugLevel
+)
+
+// Won't compile if StdLogger can't be realized by a log.Logger
+var (
+ _ StdLogger = &log.Logger{}
+ _ StdLogger = &Entry{}
+ _ StdLogger = &Logger{}
+)
+
+// StdLogger is what your logrus-enabled library should take, that way
+// it'll accept a stdlib logger and a logrus logger. There's no standard
+// interface, this is the closest we get, unfortunately.
+type StdLogger interface {
+ Print(...interface{})
+ Printf(string, ...interface{})
+ Println(...interface{})
+
+ Fatal(...interface{})
+ Fatalf(string, ...interface{})
+ Fatalln(...interface{})
+
+ Panic(...interface{})
+ Panicf(string, ...interface{})
+ Panicln(...interface{})
+}
+
+// The FieldLogger interface generalizes the Entry and Logger types
+type FieldLogger interface {
+ WithField(key string, value interface{}) *Entry
+ WithFields(fields Fields) *Entry
+ WithError(err error) *Entry
+
+ Debugf(format string, args ...interface{})
+ Infof(format string, args ...interface{})
+ Printf(format string, args ...interface{})
+ Warnf(format string, args ...interface{})
+ Warningf(format string, args ...interface{})
+ Errorf(format string, args ...interface{})
+ Fatalf(format string, args ...interface{})
+ Panicf(format string, args ...interface{})
+
+ Debug(args ...interface{})
+ Info(args ...interface{})
+ Print(args ...interface{})
+ Warn(args ...interface{})
+ Warning(args ...interface{})
+ Error(args ...interface{})
+ Fatal(args ...interface{})
+ Panic(args ...interface{})
+
+ Debugln(args ...interface{})
+ Infoln(args ...interface{})
+ Println(args ...interface{})
+ Warnln(args ...interface{})
+ Warningln(args ...interface{})
+ Errorln(args ...interface{})
+ Fatalln(args ...interface{})
+ Panicln(args ...interface{})
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_bsd.go b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go
new file mode 100644
index 00000000..71f8d67a
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_bsd.go
@@ -0,0 +1,9 @@
+// +build darwin freebsd openbsd netbsd dragonfly
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TIOCGETA
+
+type Termios syscall.Termios
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_linux.go b/vendor/github.com/Sirupsen/logrus/terminal_linux.go
new file mode 100644
index 00000000..a2c0b40d
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_linux.go
@@ -0,0 +1,12 @@
+// Based on ssh/terminal:
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package logrus
+
+import "syscall"
+
+const ioctlReadTermios = syscall.TCGETS
+
+type Termios syscall.Termios
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go b/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
new file mode 100644
index 00000000..b343b3a3
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
@@ -0,0 +1,21 @@
+// Based on ssh/terminal:
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build linux darwin freebsd openbsd netbsd dragonfly
+
+package logrus
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+// IsTerminal returns true if stderr's file descriptor is a terminal.
+func IsTerminal() bool {
+ fd := syscall.Stderr
+ var termios Termios
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
+ return err == 0
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_solaris.go b/vendor/github.com/Sirupsen/logrus/terminal_solaris.go
new file mode 100644
index 00000000..3e70bf7b
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_solaris.go
@@ -0,0 +1,15 @@
+// +build solaris
+
+package logrus
+
+import (
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+func IsTerminal() bool {
+ _, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
+ return err == nil
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_windows.go b/vendor/github.com/Sirupsen/logrus/terminal_windows.go
new file mode 100644
index 00000000..0146845d
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/terminal_windows.go
@@ -0,0 +1,27 @@
+// Based on ssh/terminal:
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package logrus
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+var kernel32 = syscall.NewLazyDLL("kernel32.dll")
+
+var (
+ procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+)
+
+// IsTerminal returns true if stderr's file descriptor is a terminal.
+func IsTerminal() bool {
+ fd := syscall.Stderr
+ var st uint32
+ r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
+ return r != 0 && e == 0
+}
diff --git a/vendor/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/Sirupsen/logrus/text_formatter.go
new file mode 100644
index 00000000..06ef2023
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/text_formatter.go
@@ -0,0 +1,161 @@
+package logrus
+
+import (
+ "bytes"
+ "fmt"
+ "runtime"
+ "sort"
+ "strings"
+ "time"
+)
+
+const (
+ nocolor = 0
+ red = 31
+ green = 32
+ yellow = 33
+ blue = 34
+ gray = 37
+)
+
+var (
+ baseTimestamp time.Time
+ isTerminal bool
+)
+
+func init() {
+ baseTimestamp = time.Now()
+ isTerminal = IsTerminal()
+}
+
+func miniTS() int {
+ return int(time.Since(baseTimestamp) / time.Second)
+}
+
+type TextFormatter struct {
+ // Set to true to bypass checking for a TTY before outputting colors.
+ ForceColors bool
+
+ // Force disabling colors.
+ DisableColors bool
+
+ // Disable timestamp logging. useful when output is redirected to logging
+ // system that already adds timestamps.
+ DisableTimestamp bool
+
+ // Enable logging the full timestamp when a TTY is attached instead of just
+ // the time passed since beginning of execution.
+ FullTimestamp bool
+
+ // TimestampFormat to use for display when a full timestamp is printed
+ TimestampFormat string
+
+ // The fields are sorted by default for a consistent output. For applications
+ // that log extremely frequently and don't use the JSON formatter this may not
+ // be desired.
+ DisableSorting bool
+}
+
+func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
+ var keys []string = make([]string, 0, len(entry.Data))
+ for k := range entry.Data {
+ keys = append(keys, k)
+ }
+
+ if !f.DisableSorting {
+ sort.Strings(keys)
+ }
+
+ b := &bytes.Buffer{}
+
+ prefixFieldClashes(entry.Data)
+
+ isColorTerminal := isTerminal && (runtime.GOOS != "windows")
+ isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
+
+ timestampFormat := f.TimestampFormat
+ if timestampFormat == "" {
+ timestampFormat = DefaultTimestampFormat
+ }
+ if isColored {
+ f.printColored(b, entry, keys, timestampFormat)
+ } else {
+ if !f.DisableTimestamp {
+ f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
+ }
+ f.appendKeyValue(b, "level", entry.Level.String())
+ if entry.Message != "" {
+ f.appendKeyValue(b, "msg", entry.Message)
+ }
+ for _, key := range keys {
+ f.appendKeyValue(b, key, entry.Data[key])
+ }
+ }
+
+ b.WriteByte('\n')
+ return b.Bytes(), nil
+}
+
+func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
+ var levelColor int
+ switch entry.Level {
+ case DebugLevel:
+ levelColor = gray
+ case WarnLevel:
+ levelColor = yellow
+ case ErrorLevel, FatalLevel, PanicLevel:
+ levelColor = red
+ default:
+ levelColor = blue
+ }
+
+ levelText := strings.ToUpper(entry.Level.String())[0:4]
+
+ if !f.FullTimestamp {
+ fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
+ } else {
+ fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
+ }
+ for _, k := range keys {
+ v := entry.Data[k]
+ fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
+ }
+}
+
+func needsQuoting(text string) bool {
+ for _, ch := range text {
+ if !((ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z') ||
+ (ch >= '0' && ch <= '9') ||
+ ch == '-' || ch == '.') {
+ return false
+ }
+ }
+ return true
+}
+
+func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
+
+ b.WriteString(key)
+ b.WriteByte('=')
+
+ switch value := value.(type) {
+ case string:
+ if needsQuoting(value) {
+ b.WriteString(value)
+ } else {
+ fmt.Fprintf(b, "%q", value)
+ }
+ case error:
+ errmsg := value.Error()
+ if needsQuoting(errmsg) {
+ b.WriteString(errmsg)
+ } else {
+ fmt.Fprintf(b, "%q", value)
+ }
+ default:
+ fmt.Fprint(b, value)
+ }
+
+ b.WriteByte(' ')
+}
diff --git a/vendor/github.com/Sirupsen/logrus/writer.go b/vendor/github.com/Sirupsen/logrus/writer.go
new file mode 100644
index 00000000..1e30b1c7
--- /dev/null
+++ b/vendor/github.com/Sirupsen/logrus/writer.go
@@ -0,0 +1,31 @@
+package logrus
+
+import (
+ "bufio"
+ "io"
+ "runtime"
+)
+
+func (logger *Logger) Writer() *io.PipeWriter {
+ reader, writer := io.Pipe()
+
+ go logger.writerScanner(reader)
+ runtime.SetFinalizer(writer, writerFinalizer)
+
+ return writer
+}
+
+func (logger *Logger) writerScanner(reader *io.PipeReader) {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ logger.Print(scanner.Text())
+ }
+ if err := scanner.Err(); err != nil {
+ logger.Errorf("Error while reading from Writer: %s", err)
+ }
+ reader.Close()
+}
+
+func writerFinalizer(writer *io.PipeWriter) {
+ writer.Close()
+}
diff --git a/vendor/github.com/alecthomas/log4go/LICENSE b/vendor/github.com/alecthomas/log4go/LICENSE
new file mode 100644
index 00000000..7093402b
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/alecthomas/log4go/config.go b/vendor/github.com/alecthomas/log4go/config.go
new file mode 100644
index 00000000..577c3eb2
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/config.go
@@ -0,0 +1,288 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go b/vendor/github.com/alecthomas/log4go/examples/ConsoleLogWriter_Manual.go
new file mode 100644
index 00000000..698dd332
--- /dev/null
+++ b/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/alecthomas/log4go/examples/FileLogWriter_Manual.go b/vendor/github.com/alecthomas/log4go/examples/FileLogWriter_Manual.go
new file mode 100644
index 00000000..efd596aa
--- /dev/null
+++ b/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/alecthomas/log4go/examples/SimpleNetLogServer.go b/vendor/github.com/alecthomas/log4go/examples/SimpleNetLogServer.go
new file mode 100644
index 00000000..83c80ad1
--- /dev/null
+++ b/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/alecthomas/log4go/examples/SocketLogWriter_Manual.go b/vendor/github.com/alecthomas/log4go/examples/SocketLogWriter_Manual.go
new file mode 100644
index 00000000..400b698c
--- /dev/null
+++ b/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/alecthomas/log4go/examples/XMLConfigurationExample.go b/vendor/github.com/alecthomas/log4go/examples/XMLConfigurationExample.go
new file mode 100644
index 00000000..164c2add
--- /dev/null
+++ b/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/alecthomas/log4go/filelog.go b/vendor/github.com/alecthomas/log4go/filelog.go
new file mode 100644
index 00000000..ee0ab0c0
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/filelog.go
@@ -0,0 +1,264 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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(
+ ` <record level="%L">
+ <timestamp>%D %T</timestamp>
+ <source>%S</source>
+ <message>%M</message>
+ </record>`).SetHeadFoot("<log created=\"%D %T\">", "</log>")
+}
diff --git a/vendor/github.com/alecthomas/log4go/log4go.go b/vendor/github.com/alecthomas/log4go/log4go.go
new file mode 100644
index 00000000..822e890c
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/log4go.go
@@ -0,0 +1,484 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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/alecthomas/log4go/pattlog.go b/vendor/github.com/alecthomas/log4go/pattlog.go
new file mode 100644
index 00000000..82b4e36b
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/pattlog.go
@@ -0,0 +1,126 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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 "<nil>"
+ }
+ 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/alecthomas/log4go/socklog.go b/vendor/github.com/alecthomas/log4go/socklog.go
new file mode 100644
index 00000000..1d224a99
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/socklog.go
@@ -0,0 +1,57 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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/alecthomas/log4go/termlog.go b/vendor/github.com/alecthomas/log4go/termlog.go
new file mode 100644
index 00000000..8a941e26
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/termlog.go
@@ -0,0 +1,49 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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/alecthomas/log4go/wrapper.go b/vendor/github.com/alecthomas/log4go/wrapper.go
new file mode 100644
index 00000000..2ae222b0
--- /dev/null
+++ b/vendor/github.com/alecthomas/log4go/wrapper.go
@@ -0,0 +1,278 @@
+// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. 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/gorilla/schema/LICENSE b/vendor/github.com/gorilla/schema/LICENSE
new file mode 100644
index 00000000..0e5fb872
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/gorilla/schema/cache.go b/vendor/github.com/gorilla/schema/cache.go
new file mode 100644
index 00000000..1613b1e6
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/cache.go
@@ -0,0 +1,245 @@
+// Copyright 2012 The Gorilla 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 schema
+
+import (
+ "errors"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+var invalidPath = errors.New("schema: invalid path")
+
+// newCache returns a new cache.
+func newCache() *cache {
+ c := cache{
+ m: make(map[reflect.Type]*structInfo),
+ conv: make(map[reflect.Kind]Converter),
+ regconv: make(map[reflect.Type]Converter),
+ tag: "schema",
+ }
+ for k, v := range converters {
+ c.conv[k] = v
+ }
+ return &c
+}
+
+// cache caches meta-data about a struct.
+type cache struct {
+ l sync.RWMutex
+ m map[reflect.Type]*structInfo
+ conv map[reflect.Kind]Converter
+ regconv map[reflect.Type]Converter
+ tag string
+}
+
+// parsePath parses a path in dotted notation verifying that it is a valid
+// path to a struct field.
+//
+// It returns "path parts" which contain indices to fields to be used by
+// reflect.Value.FieldByString(). Multiple parts are required for slices of
+// structs.
+func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
+ var struc *structInfo
+ var field *fieldInfo
+ var index64 int64
+ var err error
+ parts := make([]pathPart, 0)
+ path := make([]string, 0)
+ keys := strings.Split(p, ".")
+ for i := 0; i < len(keys); i++ {
+ if t.Kind() != reflect.Struct {
+ return nil, invalidPath
+ }
+ if struc = c.get(t); struc == nil {
+ return nil, invalidPath
+ }
+ if field = struc.get(keys[i]); field == nil {
+ return nil, invalidPath
+ }
+ // Valid field. Append index.
+ path = append(path, field.name)
+ if field.ss {
+ // Parse a special case: slices of structs.
+ // i+1 must be the slice index.
+ //
+ // Now that struct can implements TextUnmarshaler interface,
+ // we don't need to force the struct's fields to appear in the path.
+ // So checking i+2 is not necessary anymore.
+ i++
+ if i+1 > len(keys) {
+ return nil, invalidPath
+ }
+ if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
+ return nil, invalidPath
+ }
+ parts = append(parts, pathPart{
+ path: path,
+ field: field,
+ index: int(index64),
+ })
+ path = make([]string, 0)
+
+ // Get the next struct type, dropping ptrs.
+ if field.typ.Kind() == reflect.Ptr {
+ t = field.typ.Elem()
+ } else {
+ t = field.typ
+ }
+ if t.Kind() == reflect.Slice {
+ t = t.Elem()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ }
+ } else if field.typ.Kind() == reflect.Ptr {
+ t = field.typ.Elem()
+ } else {
+ t = field.typ
+ }
+ }
+ // Add the remaining.
+ parts = append(parts, pathPart{
+ path: path,
+ field: field,
+ index: -1,
+ })
+ return parts, nil
+}
+
+// get returns a cached structInfo, creating it if necessary.
+func (c *cache) get(t reflect.Type) *structInfo {
+ c.l.RLock()
+ info := c.m[t]
+ c.l.RUnlock()
+ if info == nil {
+ info = c.create(t, nil)
+ c.l.Lock()
+ c.m[t] = info
+ c.l.Unlock()
+ }
+ return info
+}
+
+// create creates a structInfo with meta-data about a struct.
+func (c *cache) create(t reflect.Type, info *structInfo) *structInfo {
+ if info == nil {
+ info = &structInfo{fields: []*fieldInfo{}}
+ }
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ if field.Anonymous {
+ ft := field.Type
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ if ft.Kind() == reflect.Struct {
+ c.create(ft, info)
+ }
+ }
+ c.createField(field, info)
+ }
+ return info
+}
+
+// createField creates a fieldInfo for the given field.
+func (c *cache) createField(field reflect.StructField, info *structInfo) {
+ alias := fieldAlias(field, c.tag)
+ if alias == "-" {
+ // Ignore this field.
+ return
+ }
+ // Check if the type is supported and don't cache it if not.
+ // First let's get the basic type.
+ isSlice, isStruct := false, false
+ ft := field.Type
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ if isSlice = ft.Kind() == reflect.Slice; isSlice {
+ ft = ft.Elem()
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ }
+ if ft.Kind() == reflect.Array {
+ ft = ft.Elem()
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ }
+ if isStruct = ft.Kind() == reflect.Struct; !isStruct {
+ if conv := c.conv[ft.Kind()]; conv == nil {
+ // Type is not supported.
+ return
+ }
+ }
+
+ info.fields = append(info.fields, &fieldInfo{
+ typ: field.Type,
+ name: field.Name,
+ ss: isSlice && isStruct,
+ alias: alias,
+ })
+}
+
+// converter returns the converter for a type.
+func (c *cache) converter(t reflect.Type) Converter {
+ conv := c.regconv[t]
+ if conv == nil {
+ conv = c.conv[t.Kind()]
+ }
+ return conv
+}
+
+// ----------------------------------------------------------------------------
+
+type structInfo struct {
+ fields []*fieldInfo
+}
+
+func (i *structInfo) get(alias string) *fieldInfo {
+ for _, field := range i.fields {
+ if strings.EqualFold(field.alias, alias) {
+ return field
+ }
+ }
+ return nil
+}
+
+type fieldInfo struct {
+ typ reflect.Type
+ name string // field name in the struct.
+ ss bool // true if this is a slice of structs.
+ alias string
+}
+
+type pathPart struct {
+ field *fieldInfo
+ path []string // path to the field: walks structs using field names.
+ index int // struct index in slices of structs.
+}
+
+// ----------------------------------------------------------------------------
+
+// fieldAlias parses a field tag to get a field alias.
+func fieldAlias(field reflect.StructField, tagName string) string {
+ var alias string
+ if tag := field.Tag.Get(tagName); tag != "" {
+ // For now tags only support the name but let's follow the
+ // comma convention from encoding/json and others.
+ if idx := strings.Index(tag, ","); idx == -1 {
+ alias = tag
+ } else {
+ alias = tag[:idx]
+ }
+ }
+ if alias == "" {
+ alias = field.Name
+ }
+ return alias
+}
diff --git a/vendor/github.com/gorilla/schema/converter.go b/vendor/github.com/gorilla/schema/converter.go
new file mode 100644
index 00000000..b33e9423
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/converter.go
@@ -0,0 +1,145 @@
+// Copyright 2012 The Gorilla 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 schema
+
+import (
+ "reflect"
+ "strconv"
+)
+
+type Converter func(string) reflect.Value
+
+var (
+ invalidValue = reflect.Value{}
+ boolType = reflect.Bool
+ float32Type = reflect.Float32
+ float64Type = reflect.Float64
+ intType = reflect.Int
+ int8Type = reflect.Int8
+ int16Type = reflect.Int16
+ int32Type = reflect.Int32
+ int64Type = reflect.Int64
+ stringType = reflect.String
+ uintType = reflect.Uint
+ uint8Type = reflect.Uint8
+ uint16Type = reflect.Uint16
+ uint32Type = reflect.Uint32
+ uint64Type = reflect.Uint64
+)
+
+// Default converters for basic types.
+var converters = map[reflect.Kind]Converter{
+ boolType: convertBool,
+ float32Type: convertFloat32,
+ float64Type: convertFloat64,
+ intType: convertInt,
+ int8Type: convertInt8,
+ int16Type: convertInt16,
+ int32Type: convertInt32,
+ int64Type: convertInt64,
+ stringType: convertString,
+ uintType: convertUint,
+ uint8Type: convertUint8,
+ uint16Type: convertUint16,
+ uint32Type: convertUint32,
+ uint64Type: convertUint64,
+}
+
+func convertBool(value string) reflect.Value {
+ if value == "on" {
+ return reflect.ValueOf(true)
+ } else if v, err := strconv.ParseBool(value); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertFloat32(value string) reflect.Value {
+ if v, err := strconv.ParseFloat(value, 32); err == nil {
+ return reflect.ValueOf(float32(v))
+ }
+ return invalidValue
+}
+
+func convertFloat64(value string) reflect.Value {
+ if v, err := strconv.ParseFloat(value, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertInt(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 0); err == nil {
+ return reflect.ValueOf(int(v))
+ }
+ return invalidValue
+}
+
+func convertInt8(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 8); err == nil {
+ return reflect.ValueOf(int8(v))
+ }
+ return invalidValue
+}
+
+func convertInt16(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 16); err == nil {
+ return reflect.ValueOf(int16(v))
+ }
+ return invalidValue
+}
+
+func convertInt32(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 32); err == nil {
+ return reflect.ValueOf(int32(v))
+ }
+ return invalidValue
+}
+
+func convertInt64(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertString(value string) reflect.Value {
+ return reflect.ValueOf(value)
+}
+
+func convertUint(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 0); err == nil {
+ return reflect.ValueOf(uint(v))
+ }
+ return invalidValue
+}
+
+func convertUint8(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 8); err == nil {
+ return reflect.ValueOf(uint8(v))
+ }
+ return invalidValue
+}
+
+func convertUint16(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 16); err == nil {
+ return reflect.ValueOf(uint16(v))
+ }
+ return invalidValue
+}
+
+func convertUint32(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 32); err == nil {
+ return reflect.ValueOf(uint32(v))
+ }
+ return invalidValue
+}
+
+func convertUint64(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
diff --git a/vendor/github.com/gorilla/schema/decoder.go b/vendor/github.com/gorilla/schema/decoder.go
new file mode 100644
index 00000000..53b0337f
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/decoder.go
@@ -0,0 +1,299 @@
+// Copyright 2012 The Gorilla 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 schema
+
+import (
+ "encoding"
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+// NewDecoder returns a new Decoder.
+func NewDecoder() *Decoder {
+ return &Decoder{cache: newCache()}
+}
+
+// Decoder decodes values from a map[string][]string to a struct.
+type Decoder struct {
+ cache *cache
+ zeroEmpty bool
+ ignoreUnknownKeys bool
+}
+
+// SetAliasTag changes the tag used to locate custom field aliases.
+// The default tag is "schema".
+func (d *Decoder) SetAliasTag(tag string) {
+ d.cache.tag = tag
+}
+
+// ZeroEmpty controls the behaviour when the decoder encounters empty values
+// in a map.
+// If z is true and a key in the map has the empty string as a value
+// then the corresponding struct field is set to the zero value.
+// If z is false then empty strings are ignored.
+//
+// The default value is false, that is empty values do not change
+// the value of the struct field.
+func (d *Decoder) ZeroEmpty(z bool) {
+ d.zeroEmpty = z
+}
+
+// IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown
+// keys in the map.
+// If i is true and an unknown field is encountered, it is ignored. This is
+// similar to how unknown keys are handled by encoding/json.
+// If i is false then Decode will return an error. Note that any valid keys
+// will still be decoded in to the target struct.
+//
+// To preserve backwards compatibility, the default value is false.
+func (d *Decoder) IgnoreUnknownKeys(i bool) {
+ d.ignoreUnknownKeys = i
+}
+
+// RegisterConverter registers a converter function for a custom type.
+func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
+ d.cache.regconv[reflect.TypeOf(value)] = converterFunc
+}
+
+// Decode decodes a map[string][]string to a struct.
+//
+// The first parameter must be a pointer to a struct.
+//
+// The second parameter is a map, typically url.Values from an HTTP request.
+// Keys are "paths" in dotted notation to the struct fields and nested structs.
+//
+// See the package documentation for a full explanation of the mechanics.
+func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
+ v := reflect.ValueOf(dst)
+ if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
+ return errors.New("schema: interface must be a pointer to struct")
+ }
+ v = v.Elem()
+ t := v.Type()
+ errors := MultiError{}
+ for path, values := range src {
+ if parts, err := d.cache.parsePath(path, t); err == nil {
+ if err = d.decode(v, path, parts, values); err != nil {
+ errors[path] = err
+ }
+ } else if !d.ignoreUnknownKeys {
+ errors[path] = fmt.Errorf("schema: invalid path %q", path)
+ }
+ }
+ if len(errors) > 0 {
+ return errors
+ }
+ return nil
+}
+
+// decode fills a struct field using a parsed path.
+func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values []string) error {
+ // Get the field walking the struct fields by index.
+ for _, name := range parts[0].path {
+ if v.Type().Kind() == reflect.Ptr {
+ if v.IsNil() {
+ v.Set(reflect.New(v.Type().Elem()))
+ }
+ v = v.Elem()
+ }
+ v = v.FieldByName(name)
+ }
+
+ // Don't even bother for unexported fields.
+ if !v.CanSet() {
+ return nil
+ }
+
+ // Dereference if needed.
+ t := v.Type()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ if v.IsNil() {
+ v.Set(reflect.New(t))
+ }
+ v = v.Elem()
+ }
+
+ // Slice of structs. Let's go recursive.
+ if len(parts) > 1 {
+ idx := parts[0].index
+ if v.IsNil() || v.Len() < idx+1 {
+ value := reflect.MakeSlice(t, idx+1, idx+1)
+ if v.Len() < idx+1 {
+ // Resize it.
+ reflect.Copy(value, v)
+ }
+ v.Set(value)
+ }
+ return d.decode(v.Index(idx), path, parts[1:], values)
+ }
+
+ // Get the converter early in case there is one for a slice type.
+ conv := d.cache.converter(t)
+ if conv == nil && t.Kind() == reflect.Slice {
+ var items []reflect.Value
+ elemT := t.Elem()
+ isPtrElem := elemT.Kind() == reflect.Ptr
+ if isPtrElem {
+ elemT = elemT.Elem()
+ }
+
+ // Try to get a converter for the element type.
+ conv := d.cache.converter(elemT)
+ if conv == nil {
+ // As we are not dealing with slice of structs here, we don't need to check if the type
+ // implements TextUnmarshaler interface
+ return fmt.Errorf("schema: converter not found for %v", elemT)
+ }
+
+ for key, value := range values {
+ if value == "" {
+ if d.zeroEmpty {
+ items = append(items, reflect.Zero(elemT))
+ }
+ } else if item := conv(value); item.IsValid() {
+ if isPtrElem {
+ ptr := reflect.New(elemT)
+ ptr.Elem().Set(item)
+ item = ptr
+ }
+ if item.Type() != elemT && !isPtrElem {
+ item = item.Convert(elemT)
+ }
+ items = append(items, item)
+ } else {
+ if strings.Contains(value, ",") {
+ values := strings.Split(value, ",")
+ for _, value := range values {
+ if value == "" {
+ if d.zeroEmpty {
+ items = append(items, reflect.Zero(elemT))
+ }
+ } else if item := conv(value); item.IsValid() {
+ if isPtrElem {
+ ptr := reflect.New(elemT)
+ ptr.Elem().Set(item)
+ item = ptr
+ }
+ if item.Type() != elemT && !isPtrElem {
+ item = item.Convert(elemT)
+ }
+ items = append(items, item)
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: elemT,
+ Index: key,
+ }
+ }
+ }
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: elemT,
+ Index: key,
+ }
+ }
+ }
+ }
+ value := reflect.Append(reflect.MakeSlice(t, 0, 0), items...)
+ v.Set(value)
+ } else {
+ val := ""
+ // Use the last value provided if any values were provided
+ if len(values) > 0 {
+ val = values[len(values)-1]
+ }
+
+ if val == "" {
+ if d.zeroEmpty {
+ v.Set(reflect.Zero(t))
+ }
+ } else if conv != nil {
+ if value := conv(val); value.IsValid() {
+ v.Set(value.Convert(t))
+ } else {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: -1,
+ }
+ }
+ } else {
+ // When there's no registered conversion for the custom type, we will check if the type
+ // implements the TextUnmarshaler interface. As the UnmarshalText function should be applied
+ // to the pointer of the type, we convert the value to pointer.
+ if v.CanAddr() {
+ v = v.Addr()
+ }
+
+ if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
+ if err := u.UnmarshalText([]byte(val)); err != nil {
+ return ConversionError{
+ Key: path,
+ Type: t,
+ Index: -1,
+ Err: err,
+ }
+ }
+
+ } else {
+ return fmt.Errorf("schema: converter not found for %v", t)
+ }
+ }
+ }
+ return nil
+}
+
+// Errors ---------------------------------------------------------------------
+
+// ConversionError stores information about a failed conversion.
+type ConversionError struct {
+ Key string // key from the source map.
+ Type reflect.Type // expected type of elem
+ Index int // index for multi-value fields; -1 for single-value fields.
+ Err error // low-level error (when it exists)
+}
+
+func (e ConversionError) Error() string {
+ var output string
+
+ if e.Index < 0 {
+ output = fmt.Sprintf("schema: error converting value for %q", e.Key)
+ } else {
+ output = fmt.Sprintf("schema: error converting value for index %d of %q",
+ e.Index, e.Key)
+ }
+
+ if e.Err != nil {
+ output = fmt.Sprintf("%s. Details: %s", output, e.Err)
+ }
+
+ return output
+}
+
+// MultiError stores multiple decoding errors.
+//
+// Borrowed from the App Engine SDK.
+type MultiError map[string]error
+
+func (e MultiError) Error() string {
+ s := ""
+ for _, err := range e {
+ s = err.Error()
+ break
+ }
+ switch len(e) {
+ case 0:
+ return "(0 errors)"
+ case 1:
+ return s
+ case 2:
+ return s + " (and 1 other error)"
+ }
+ return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1)
+}
diff --git a/vendor/github.com/gorilla/schema/doc.go b/vendor/github.com/gorilla/schema/doc.go
new file mode 100644
index 00000000..22c0ff4b
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/doc.go
@@ -0,0 +1,148 @@
+// Copyright 2012 The Gorilla 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 gorilla/schema fills a struct with form values.
+
+The basic usage is really simple. Given this struct:
+
+ type Person struct {
+ Name string
+ Phone string
+ }
+
+...we can fill it passing a map to the Load() function:
+
+ values := map[string][]string{
+ "Name": {"John"},
+ "Phone": {"999-999-999"},
+ }
+ person := new(Person)
+ decoder := schema.NewDecoder()
+ decoder.Decode(person, values)
+
+This is just a simple example and it doesn't make a lot of sense to create
+the map manually. Typically it will come from a http.Request object and
+will be of type url.Values: http.Request.Form or http.Request.MultipartForm:
+
+ func MyHandler(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+
+ if err != nil {
+ // Handle error
+ }
+
+ decoder := schema.NewDecoder()
+ // r.PostForm is a map of our POST form values
+ err := decoder.Decode(person, r.PostForm)
+
+ if err != nil {
+ // Handle error
+ }
+
+ // Do something with person.Name or person.Phone
+ }
+
+Note: it is a good idea to set a Decoder instance as a package global,
+because it caches meta-data about structs, and a instance can be shared safely:
+
+ var decoder = schema.NewDecoder()
+
+To define custom names for fields, use a struct tag "schema". To not populate
+certain fields, use a dash for the name and it will be ignored:
+
+ type Person struct {
+ Name string `schema:"name"` // custom name
+ Phone string `schema:"phone"` // custom name
+ Admin bool `schema:"-"` // this field is never set
+ }
+
+The supported field types in the destination struct are:
+
+ * bool
+ * float variants (float32, float64)
+ * int variants (int, int8, int16, int32, int64)
+ * string
+ * uint variants (uint, uint8, uint16, uint32, uint64)
+ * struct
+ * a pointer to one of the above types
+ * a slice or a pointer to a slice of one of the above types
+
+Non-supported types are simply ignored, however custom types can be registered
+to be converted.
+
+To fill nested structs, keys must use a dotted notation as the "path" for the
+field. So for example, to fill the struct Person below:
+
+ type Phone struct {
+ Label string
+ Number string
+ }
+
+ type Person struct {
+ Name string
+ Phone Phone
+ }
+
+...the source map must have the keys "Name", "Phone.Label" and "Phone.Number".
+This means that an HTML form to fill a Person struct must look like this:
+
+ <form>
+ <input type="text" name="Name">
+ <input type="text" name="Phone.Label">
+ <input type="text" name="Phone.Number">
+ </form>
+
+Single values are filled using the first value for a key from the source map.
+Slices are filled using all values for a key from the source map. So to fill
+a Person with multiple Phone values, like:
+
+ type Person struct {
+ Name string
+ Phones []Phone
+ }
+
+...an HTML form that accepts three Phone values would look like this:
+
+ <form>
+ <input type="text" name="Name">
+ <input type="text" name="Phones.0.Label">
+ <input type="text" name="Phones.0.Number">
+ <input type="text" name="Phones.1.Label">
+ <input type="text" name="Phones.1.Number">
+ <input type="text" name="Phones.2.Label">
+ <input type="text" name="Phones.2.Number">
+ </form>
+
+Notice that only for slices of structs the slice index is required.
+This is needed for disambiguation: if the nested struct also had a slice
+field, we could not translate multiple values to it if we did not use an
+index for the parent struct.
+
+There's also the possibility to create a custom type that implements the
+TextUnmarshaler interface, and in this case there's no need to registry
+a converter, like:
+
+ type Person struct {
+ Emails []Email
+ }
+
+ type Email struct {
+ *mail.Address
+ }
+
+ func (e *Email) UnmarshalText(text []byte) (err error) {
+ e.Address, err = mail.ParseAddress(string(text))
+ return
+ }
+
+...an HTML form that accepts three Email values would look like this:
+
+ <form>
+ <input type="email" name="Emails.0">
+ <input type="email" name="Emails.1">
+ <input type="email" name="Emails.2">
+ </form>
+*/
+package schema
diff --git a/vendor/github.com/gorilla/websocket/LICENSE b/vendor/github.com/gorilla/websocket/LICENSE
new file mode 100644
index 00000000..9171c972
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go
new file mode 100644
index 00000000..a353e185
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/client.go
@@ -0,0 +1,350 @@
+// 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")
+
+// 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
+}
+
+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 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-Protocol" && len(d.Subprotocols) > 0):
+ return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
+ default:
+ req.Header[k] = vs
+ }
+ }
+
+ 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 := d.TLSClientConfig
+ if cfg == nil {
+ cfg = &tls.Config{ServerName: hostNoPort}
+ } else if cfg.ServerName == "" {
+ shallowCopy := *cfg
+ cfg = &shallowCopy
+ 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 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
+ }
+
+ 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
+}
diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go
new file mode 100644
index 00000000..eff26c63
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn.go
@@ -0,0 +1,915 @@
+// 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"
+ "math/rand"
+ "net"
+ "strconv"
+ "time"
+)
+
+const (
+ maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
+ maxControlFramePayloadSize = 125
+ finalBit = 1 << 7
+ maskBit = 1 << 7
+ 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
+ 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
+}
+
+func maskBytes(key [4]byte, pos int, b []byte) int {
+ for i := range b {
+ b[i] ^= key[pos&3]
+ pos++
+ }
+ return pos & 3
+}
+
+func newMaskKey() [4]byte {
+ n := rand.Uint32()
+ return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
+}
+
+// Conn 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 and closeSent
+ closeSent bool // true if close message was sent
+
+ // Message writer fields.
+ writeErr error
+ writeBuf []byte // frame is constructed in this buffer.
+ writePos int // end of data in writeBuf.
+ writeFrameType int // type of the current frame.
+ writeSeq int // incremented to invalidate message writers.
+ writeDeadline time.Time
+ isWriting bool // for best-effort concurrent write detection
+
+ // Read fields
+ readErr error
+ br *bufio.Reader
+ readRemaining int64 // bytes remaining in current frame.
+ readFinal bool // true the current message has more frames.
+ readSeq int // incremented to invalidate message readers.
+ readLength int64 // Message size.
+ readLimit int64 // Maximum message size.
+ readMaskPos int
+ readMaskKey [4]byte
+ handlePong func(string) error
+ handlePing func(string) error
+ readErrCount int
+}
+
+func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
+ mu := make(chan bool, 1)
+ mu <- true
+
+ if readBufferSize == 0 {
+ readBufferSize = defaultReadBufferSize
+ }
+ 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),
+ writeFrameType: noFrame,
+ writePos: maxFrameHeaderSize,
+ }
+ 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) write(frameType int, deadline time.Time, bufs ...[]byte) error {
+ <-c.mu
+ defer func() { c.mu <- true }()
+
+ if c.closeSent {
+ return ErrCloseSent
+ } else if frameType == CloseMessage {
+ c.closeSent = true
+ }
+
+ c.conn.SetWriteDeadline(deadline)
+ for _, buf := range bufs {
+ if len(buf) > 0 {
+ n, err := c.conn.Write(buf)
+ if n != len(buf) {
+ // Close on partial write.
+ c.conn.Close()
+ }
+ if err != nil {
+ return err
+ }
+ }
+ }
+ 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 }()
+
+ if c.closeSent {
+ return ErrCloseSent
+ } else if messageType == CloseMessage {
+ c.closeSent = true
+ }
+
+ c.conn.SetWriteDeadline(deadline)
+ n, err := c.conn.Write(buf)
+ if n != 0 && n != len(buf) {
+ c.conn.Close()
+ }
+ return hideTempErr(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 c.writeErr != nil {
+ return nil, c.writeErr
+ }
+
+ if c.writeFrameType != noFrame {
+ if err := c.flushFrame(true, nil); err != nil {
+ return nil, err
+ }
+ }
+
+ if !isControl(messageType) && !isData(messageType) {
+ return nil, errBadWriteOpCode
+ }
+
+ c.writeFrameType = messageType
+ return messageWriter{c, c.writeSeq}, nil
+}
+
+func (c *Conn) flushFrame(final bool, extra []byte) error {
+ length := c.writePos - maxFrameHeaderSize + len(extra)
+
+ // Check for invalid control frames.
+ if isControl(c.writeFrameType) &&
+ (!final || length > maxControlFramePayloadSize) {
+ c.writeSeq++
+ c.writeFrameType = noFrame
+ c.writePos = maxFrameHeaderSize
+ return errInvalidControlFrame
+ }
+
+ b0 := byte(c.writeFrameType)
+ if final {
+ b0 |= finalBit
+ }
+ 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:c.writePos])
+ if len(extra) > 0 {
+ c.writeErr = errors.New("websocket: internal error, extra used in client mode")
+ return c.writeErr
+ }
+ }
+
+ // 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
+
+ c.writeErr = c.write(c.writeFrameType, c.writeDeadline, c.writeBuf[framePos:c.writePos], extra)
+
+ if !c.isWriting {
+ panic("concurrent write to websocket connection")
+ }
+ c.isWriting = false
+
+ // Setup for next frame.
+ c.writePos = maxFrameHeaderSize
+ c.writeFrameType = continuationFrame
+ if final {
+ c.writeSeq++
+ c.writeFrameType = noFrame
+ }
+ return c.writeErr
+}
+
+type messageWriter struct {
+ c *Conn
+ seq int
+}
+
+func (w messageWriter) err() error {
+ c := w.c
+ if c.writeSeq != w.seq {
+ return errWriteClosed
+ }
+ if c.writeErr != nil {
+ return c.writeErr
+ }
+ return nil
+}
+
+func (w messageWriter) ncopy(max int) (int, error) {
+ n := len(w.c.writeBuf) - w.c.writePos
+ if n <= 0 {
+ if err := w.c.flushFrame(false, nil); err != nil {
+ return 0, err
+ }
+ n = len(w.c.writeBuf) - w.c.writePos
+ }
+ if n > max {
+ n = max
+ }
+ return n, nil
+}
+
+func (w messageWriter) write(final bool, p []byte) (int, error) {
+ if err := w.err(); err != nil {
+ return 0, err
+ }
+
+ if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
+ // Don't buffer large messages.
+ err := w.c.flushFrame(final, 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.c.writePos:], p[:n])
+ w.c.writePos += n
+ p = p[n:]
+ }
+ return nn, nil
+}
+
+func (w messageWriter) Write(p []byte) (int, error) {
+ return w.write(false, p)
+}
+
+func (w messageWriter) WriteString(p string) (int, error) {
+ if err := w.err(); err != nil {
+ return 0, 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.c.writePos:], p[:n])
+ w.c.writePos += n
+ p = p[n:]
+ }
+ return nn, nil
+}
+
+func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
+ if err := w.err(); err != nil {
+ return 0, err
+ }
+ for {
+ if w.c.writePos == len(w.c.writeBuf) {
+ err = w.c.flushFrame(false, nil)
+ if err != nil {
+ break
+ }
+ }
+ var n int
+ n, err = r.Read(w.c.writeBuf[w.c.writePos:])
+ w.c.writePos += n
+ nn += int64(n)
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ break
+ }
+ }
+ return nn, err
+}
+
+func (w messageWriter) Close() error {
+ if err := w.err(); err != nil {
+ return err
+ }
+ return w.c.flushFrame(true, 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 {
+ wr, err := c.NextWriter(messageType)
+ if err != nil {
+ return err
+ }
+ w := wr.(messageWriter)
+ if _, err := w.write(true, data); err != nil {
+ return err
+ }
+ if c.writeSeq == w.seq {
+ if err := c.flushFrame(true, nil); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// 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
+
+// readFull is like io.ReadFull except that io.EOF is never returned.
+func (c *Conn) readFull(p []byte) (err error) {
+ var n int
+ for n < len(p) && err == nil {
+ var nn int
+ nn, err = c.br.Read(p[n:])
+ n += nn
+ }
+ if n == len(p) {
+ err = nil
+ } else if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ return
+}
+
+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.
+
+ var b [8]byte
+ if err := c.readFull(b[:2]); err != nil {
+ return noFrame, err
+ }
+
+ final := b[0]&finalBit != 0
+ frameType := int(b[0] & 0xf)
+ reserved := int((b[0] >> 4) & 0x7)
+ mask := b[1]&maskBit != 0
+ c.readRemaining = int64(b[1] & 0x7f)
+
+ if reserved != 0 {
+ return noFrame, c.handleProtocolError("unexpected reserved bits " + strconv.Itoa(reserved))
+ }
+
+ 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:
+ if err := c.readFull(b[:2]); err != nil {
+ return noFrame, err
+ }
+ c.readRemaining = int64(binary.BigEndian.Uint16(b[:2]))
+ case 127:
+ if err := c.readFull(b[:8]); err != nil {
+ return noFrame, err
+ }
+ c.readRemaining = int64(binary.BigEndian.Uint64(b[:8]))
+ }
+
+ // 4. Handle frame masking.
+
+ if mask != c.isServer {
+ return noFrame, c.handleProtocolError("incorrect mask flag")
+ }
+
+ if mask {
+ c.readMaskPos = 0
+ if err := c.readFull(c.readMaskKey[:]); err != nil {
+ return noFrame, err
+ }
+ }
+
+ // 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 = make([]byte, c.readRemaining)
+ c.readRemaining = 0
+ if err := c.readFull(payload); 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:
+ echoMessage := []byte{}
+ closeCode := CloseNoStatusReceived
+ closeText := ""
+ if len(payload) >= 2 {
+ echoMessage = payload[:2]
+ closeCode = int(binary.BigEndian.Uint16(payload))
+ closeText = string(payload[2:])
+ }
+ c.WriteControl(CloseMessage, echoMessage, time.Now().Add(writeWait))
+ 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.readSeq++
+ c.readLength = 0
+
+ for c.readErr == nil {
+ frameType, err := c.advanceFrame()
+ if err != nil {
+ c.readErr = hideTempErr(err)
+ break
+ }
+ if frameType == TextMessage || frameType == BinaryMessage {
+ return frameType, messageReader{c, c.readSeq}, 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
+ seq int
+}
+
+func (r messageReader) Read(b []byte) (int, error) {
+
+ if r.seq != r.c.readSeq {
+ return 0, io.EOF
+ }
+
+ for r.c.readErr == nil {
+
+ if r.c.readRemaining > 0 {
+ if int64(len(b)) > r.c.readRemaining {
+ b = b[:r.c.readRemaining]
+ }
+ n, err := r.c.br.Read(b)
+ r.c.readErr = hideTempErr(err)
+ if r.c.isServer {
+ r.c.readMaskPos = maskBytes(r.c.readMaskKey, r.c.readMaskPos, b[:n])
+ }
+ r.c.readRemaining -= int64(n)
+ return n, r.c.readErr
+ }
+
+ if r.c.readFinal {
+ r.c.readSeq++
+ return 0, io.EOF
+ }
+
+ frameType, err := r.c.advanceFrame()
+ switch {
+ case err != nil:
+ r.c.readErr = hideTempErr(err)
+ case frameType == TextMessage || frameType == BinaryMessage:
+ r.c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
+ }
+ }
+
+ err := r.c.readErr
+ if err == io.EOF && r.seq == r.c.readSeq {
+ 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
+}
+
+// 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
+}
+
+// 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
+}
+
+// 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/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go
new file mode 100644
index 00000000..c901a7a9
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/doc.go
@@ -0,0 +1,152 @@
+// 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.
+package websocket
diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/server.go b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go
new file mode 100644
index 00000000..d96ac84d
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go
@@ -0,0 +1,246 @@
+// 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"
+ "github.com/gorilla/websocket"
+ "io"
+ "log"
+ "net/http"
+ "time"
+ "unicode/utf8"
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 4096,
+ WriteBufferSize: 4096,
+ 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, "<html><body>Echo Server</body></html>")
+}
+
+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 <bjoern@hoehrmann.de>
+//
+// 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/gorilla/websocket/examples/chat/conn.go b/vendor/github.com/gorilla/websocket/examples/chat/conn.go
new file mode 100644
index 00000000..40fd38c2
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/conn.go
@@ -0,0 +1,105 @@
+// 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 (
+ "github.com/gorilla/websocket"
+ "log"
+ "net/http"
+ "time"
+)
+
+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 upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+}
+
+// connection is an middleman between the websocket connection and the hub.
+type connection struct {
+ // The websocket connection.
+ ws *websocket.Conn
+
+ // Buffered channel of outbound messages.
+ send chan []byte
+}
+
+// readPump pumps messages from the websocket connection to the hub.
+func (c *connection) readPump() {
+ defer func() {
+ h.unregister <- c
+ c.ws.Close()
+ }()
+ c.ws.SetReadLimit(maxMessageSize)
+ c.ws.SetReadDeadline(time.Now().Add(pongWait))
+ c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, message, err := c.ws.ReadMessage()
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
+ log.Printf("error: %v", err)
+ }
+ break
+ }
+ h.broadcast <- message
+ }
+}
+
+// write writes a message with the given message type and payload.
+func (c *connection) write(mt int, payload []byte) error {
+ c.ws.SetWriteDeadline(time.Now().Add(writeWait))
+ return c.ws.WriteMessage(mt, payload)
+}
+
+// writePump pumps messages from the hub to the websocket connection.
+func (c *connection) writePump() {
+ ticker := time.NewTicker(pingPeriod)
+ defer func() {
+ ticker.Stop()
+ c.ws.Close()
+ }()
+ for {
+ select {
+ case message, ok := <-c.send:
+ if !ok {
+ c.write(websocket.CloseMessage, []byte{})
+ return
+ }
+ if err := c.write(websocket.TextMessage, message); err != nil {
+ return
+ }
+ case <-ticker.C:
+ if err := c.write(websocket.PingMessage, []byte{}); err != nil {
+ return
+ }
+ }
+ }
+}
+
+// serveWs handles websocket requests from the peer.
+func serveWs(w http.ResponseWriter, r *http.Request) {
+ ws, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ c := &connection{send: make(chan []byte, 256), ws: ws}
+ h.register <- c
+ go c.writePump()
+ c.readPump()
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/hub.go b/vendor/github.com/gorilla/websocket/examples/chat/hub.go
new file mode 100644
index 00000000..449ba753
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/hub.go
@@ -0,0 +1,51 @@
+// 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 connections and broadcasts messages to the
+// connections.
+type hub struct {
+ // Registered connections.
+ connections map[*connection]bool
+
+ // Inbound messages from the connections.
+ broadcast chan []byte
+
+ // Register requests from the connections.
+ register chan *connection
+
+ // Unregister requests from connections.
+ unregister chan *connection
+}
+
+var h = hub{
+ broadcast: make(chan []byte),
+ register: make(chan *connection),
+ unregister: make(chan *connection),
+ connections: make(map[*connection]bool),
+}
+
+func (h *hub) run() {
+ for {
+ select {
+ case c := <-h.register:
+ h.connections[c] = true
+ case c := <-h.unregister:
+ if _, ok := h.connections[c]; ok {
+ delete(h.connections, c)
+ close(c.send)
+ }
+ case m := <-h.broadcast:
+ for c := range h.connections {
+ select {
+ case c.send <- m:
+ default:
+ close(c.send)
+ delete(h.connections, c)
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/main.go b/vendor/github.com/gorilla/websocket/examples/chat/main.go
new file mode 100644
index 00000000..3c4448d7
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/main.go
@@ -0,0 +1,39 @@
+// 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 homeTempl = template.Must(template.ParseFiles("home.html"))
+
+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()
+ go h.run()
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", serveWs)
+ err := http.ListenAndServe(*addr, nil)
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/command/main.go b/vendor/github.com/gorilla/websocket/examples/command/main.go
new file mode 100644
index 00000000..f3f022ed
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/command/main.go
@@ -0,0 +1,188 @@
+// 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
+)
+
+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() {
+ ws.Close()
+ close(done)
+ }()
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
+ break
+ }
+ }
+ if s.Err() != nil {
+ log.Println("scan:", s.Err())
+ }
+}
+
+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/gorilla/websocket/examples/echo/client.go b/vendor/github.com/gorilla/websocket/examples/echo/client.go
new file mode 100644
index 00000000..6578094e
--- /dev/null
+++ b/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/gorilla/websocket/examples/echo/server.go b/vendor/github.com/gorilla/websocket/examples/echo/server.go
new file mode 100644
index 00000000..a685b097
--- /dev/null
+++ b/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(`
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<script>
+window.addEventListener("load", function(evt) {
+
+ var output = document.getElementById("output");
+ var input = document.getElementById("input");
+ var ws;
+
+ var print = function(message) {
+ var d = document.createElement("div");
+ d.innerHTML = message;
+ output.appendChild(d);
+ };
+
+ document.getElementById("open").onclick = function(evt) {
+ if (ws) {
+ return false;
+ }
+ ws = new WebSocket("{{.}}");
+ ws.onopen = function(evt) {
+ print("OPEN");
+ }
+ ws.onclose = function(evt) {
+ print("CLOSE");
+ ws = null;
+ }
+ ws.onmessage = function(evt) {
+ print("RESPONSE: " + evt.data);
+ }
+ ws.onerror = function(evt) {
+ print("ERROR: " + evt.data);
+ }
+ return false;
+ };
+
+ document.getElementById("send").onclick = function(evt) {
+ if (!ws) {
+ return false;
+ }
+ print("SEND: " + input.value);
+ ws.send(input.value);
+ return false;
+ };
+
+ document.getElementById("close").onclick = function(evt) {
+ if (!ws) {
+ return false;
+ }
+ ws.close();
+ return false;
+ };
+
+});
+</script>
+</head>
+<body>
+<table>
+<tr><td valign="top" width="50%">
+<p>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.
+<p>
+<form>
+<button id="open">Open</button>
+<button id="close">Close</button>
+<p><input id="input" type="text" value="Hello world!">
+<button id="send">Send</button>
+</form>
+</td><td valign="top" width="50%">
+<div id="output"></div>
+</td></tr></table>
+</body>
+</html>
+`))
diff --git a/vendor/github.com/gorilla/websocket/examples/filewatch/main.go b/vendor/github.com/gorilla/websocket/examples/filewatch/main.go
new file mode 100644
index 00000000..a2c7b85f
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/filewatch/main.go
@@ -0,0 +1,193 @@
+// 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"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "text/template"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+const (
+ // Time allowed to write the file to the client.
+ writeWait = 10 * time.Second
+
+ // Time allowed to read the next pong message from the client.
+ pongWait = 60 * time.Second
+
+ // Send pings to client with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+
+ // Poll file for changes with this period.
+ filePeriod = 10 * time.Second
+)
+
+var (
+ addr = flag.String("addr", ":8080", "http service address")
+ homeTempl = template.Must(template.New("").Parse(homeHTML))
+ filename string
+ upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ }
+)
+
+func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
+ fi, err := os.Stat(filename)
+ if err != nil {
+ return nil, lastMod, err
+ }
+ if !fi.ModTime().After(lastMod) {
+ return nil, lastMod, nil
+ }
+ p, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, fi.ModTime(), err
+ }
+ return p, fi.ModTime(), nil
+}
+
+func reader(ws *websocket.Conn) {
+ defer ws.Close()
+ ws.SetReadLimit(512)
+ ws.SetReadDeadline(time.Now().Add(pongWait))
+ ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, _, err := ws.ReadMessage()
+ if err != nil {
+ break
+ }
+ }
+}
+
+func writer(ws *websocket.Conn, lastMod time.Time) {
+ lastError := ""
+ pingTicker := time.NewTicker(pingPeriod)
+ fileTicker := time.NewTicker(filePeriod)
+ defer func() {
+ pingTicker.Stop()
+ fileTicker.Stop()
+ ws.Close()
+ }()
+ for {
+ select {
+ case <-fileTicker.C:
+ var p []byte
+ var err error
+
+ p, lastMod, err = readFileIfModified(lastMod)
+
+ if err != nil {
+ if s := err.Error(); s != lastError {
+ lastError = s
+ p = []byte(lastError)
+ }
+ } else {
+ lastError = ""
+ }
+
+ if p != nil {
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
+ return
+ }
+ }
+ case <-pingTicker.C:
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
+ return
+ }
+ }
+ }
+}
+
+func serveWs(w http.ResponseWriter, r *http.Request) {
+ ws, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ if _, ok := err.(websocket.HandshakeError); !ok {
+ log.Println(err)
+ }
+ return
+ }
+
+ var lastMod time.Time
+ if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err != nil {
+ lastMod = time.Unix(0, n)
+ }
+
+ go writer(ws, lastMod)
+ reader(ws)
+}
+
+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")
+ p, lastMod, err := readFileIfModified(time.Time{})
+ if err != nil {
+ p = []byte(err.Error())
+ lastMod = time.Unix(0, 0)
+ }
+ var v = struct {
+ Host string
+ Data string
+ LastMod string
+ }{
+ r.Host,
+ string(p),
+ strconv.FormatInt(lastMod.UnixNano(), 16),
+ }
+ homeTempl.Execute(w, &v)
+}
+
+func main() {
+ flag.Parse()
+ if flag.NArg() != 1 {
+ log.Fatal("filename not specified")
+ }
+ filename = flag.Args()[0]
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", serveWs)
+ if err := http.ListenAndServe(*addr, nil); err != nil {
+ log.Fatal(err)
+ }
+}
+
+const homeHTML = `<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>WebSocket Example</title>
+ </head>
+ <body>
+ <pre id="fileData">{{.Data}}</pre>
+ <script type="text/javascript">
+ (function() {
+ var data = document.getElementById("fileData");
+ var conn = new WebSocket("ws://{{.Host}}/ws?lastMod={{.LastMod}}");
+ conn.onclose = function(evt) {
+ data.textContent = 'Connection closed';
+ }
+ conn.onmessage = function(evt) {
+ console.log('file updated');
+ data.textContent = evt.data;
+ }
+ })();
+ </script>
+ </body>
+</html>
+`
diff --git a/vendor/github.com/gorilla/websocket/json.go b/vendor/github.com/gorilla/websocket/json.go
new file mode 100644
index 00000000..4f0e3687
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/json.go
@@ -0,0 +1,55 @@
+// 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 (
+ "encoding/json"
+ "io"
+)
+
+// WriteJSON is deprecated, use c.WriteJSON instead.
+func WriteJSON(c *Conn, v interface{}) error {
+ return c.WriteJSON(v)
+}
+
+// WriteJSON writes the JSON encoding of v to the connection.
+//
+// See the documentation for encoding/json Marshal for details about the
+// conversion of Go values to JSON.
+func (c *Conn) WriteJSON(v interface{}) error {
+ w, err := c.NextWriter(TextMessage)
+ if err != nil {
+ return err
+ }
+ err1 := json.NewEncoder(w).Encode(v)
+ err2 := w.Close()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
+
+// ReadJSON is deprecated, use c.ReadJSON instead.
+func ReadJSON(c *Conn, v interface{}) error {
+ return c.ReadJSON(v)
+}
+
+// ReadJSON reads the next JSON-encoded message from the connection and stores
+// it in the value pointed to by v.
+//
+// See the documentation for the encoding/json Unmarshal function for details
+// about the conversion of JSON to a Go value.
+func (c *Conn) ReadJSON(v interface{}) error {
+ _, r, err := c.NextReader()
+ if err != nil {
+ return err
+ }
+ err = json.NewDecoder(r).Decode(v)
+ if err == io.EOF {
+ // One value is expected in the message.
+ err = io.ErrUnexpectedEOF
+ }
+ return err
+}
diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go
new file mode 100644
index 00000000..8d7137de
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/server.go
@@ -0,0 +1,260 @@
+// 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"
+ "errors"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// HandshakeError describes an error with the handshake from the peer.
+type HandshakeError struct {
+ message string
+}
+
+func (e HandshakeError) Error() string { return e.message }
+
+// Upgrader specifies parameters for upgrading an HTTP connection to a
+// WebSocket connection.
+type Upgrader struct {
+ // HandshakeTimeout specifies the duration for the handshake to complete.
+ HandshakeTimeout time.Duration
+
+ // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
+ // size is zero, then a default value of 4096 is used. The I/O buffer sizes
+ // do not limit the size of the messages that can be sent or received.
+ ReadBufferSize, WriteBufferSize int
+
+ // Subprotocols specifies the server's supported protocols in order of
+ // preference. If this field is set, then the Upgrade method negotiates a
+ // subprotocol by selecting the first match in this list with a protocol
+ // requested by the client.
+ Subprotocols []string
+
+ // Error specifies the function for generating HTTP error responses. If Error
+ // is nil, then http.Error is used to generate the HTTP response.
+ Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
+
+ // CheckOrigin returns true if the request Origin header is acceptable. If
+ // CheckOrigin is nil, the host in the Origin header must not be set or
+ // must match the host of the request.
+ CheckOrigin func(r *http.Request) bool
+}
+
+func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
+ err := HandshakeError{reason}
+ if u.Error != nil {
+ u.Error(w, r, status, err)
+ } else {
+ http.Error(w, http.StatusText(status), status)
+ }
+ return nil, err
+}
+
+// checkSameOrigin returns true if the origin is not set or is equal to the request host.
+func checkSameOrigin(r *http.Request) bool {
+ origin := r.Header["Origin"]
+ if len(origin) == 0 {
+ return true
+ }
+ u, err := url.Parse(origin[0])
+ if err != nil {
+ return false
+ }
+ return u.Host == r.Host
+}
+
+func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
+ if u.Subprotocols != nil {
+ clientProtocols := Subprotocols(r)
+ for _, serverProtocol := range u.Subprotocols {
+ for _, clientProtocol := range clientProtocols {
+ if clientProtocol == serverProtocol {
+ return clientProtocol
+ }
+ }
+ }
+ } else if responseHeader != nil {
+ return responseHeader.Get("Sec-Websocket-Protocol")
+ }
+ return ""
+}
+
+// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
+//
+// The responseHeader is included in the response to the client's upgrade
+// request. Use the responseHeader to specify cookies (Set-Cookie) and the
+// application negotiated subprotocol (Sec-Websocket-Protocol).
+//
+// If the upgrade fails, then Upgrade replies to the client with an HTTP error
+// response.
+func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
+ if r.Method != "GET" {
+ return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET")
+ }
+ if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13")
+ }
+
+ if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find connection header with token 'upgrade'")
+ }
+
+ if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find upgrade header with token 'websocket'")
+ }
+
+ checkOrigin := u.CheckOrigin
+ if checkOrigin == nil {
+ checkOrigin = checkSameOrigin
+ }
+ if !checkOrigin(r) {
+ return u.returnError(w, r, http.StatusForbidden, "websocket: origin not allowed")
+ }
+
+ challengeKey := r.Header.Get("Sec-Websocket-Key")
+ if challengeKey == "" {
+ return u.returnError(w, r, http.StatusBadRequest, "websocket: key missing or blank")
+ }
+
+ subprotocol := u.selectSubprotocol(r, responseHeader)
+
+ var (
+ netConn net.Conn
+ br *bufio.Reader
+ err error
+ )
+
+ h, ok := w.(http.Hijacker)
+ if !ok {
+ return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
+ }
+ var rw *bufio.ReadWriter
+ netConn, rw, err = h.Hijack()
+ if err != nil {
+ return u.returnError(w, r, http.StatusInternalServerError, err.Error())
+ }
+ br = rw.Reader
+
+ if br.Buffered() > 0 {
+ netConn.Close()
+ return nil, errors.New("websocket: client sent data before handshake is complete")
+ }
+
+ c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize)
+ c.subprotocol = subprotocol
+
+ p := c.writeBuf[:0]
+ p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
+ p = append(p, computeAcceptKey(challengeKey)...)
+ p = append(p, "\r\n"...)
+ if c.subprotocol != "" {
+ p = append(p, "Sec-Websocket-Protocol: "...)
+ p = append(p, c.subprotocol...)
+ p = append(p, "\r\n"...)
+ }
+ for k, vs := range responseHeader {
+ if k == "Sec-Websocket-Protocol" {
+ continue
+ }
+ for _, v := range vs {
+ p = append(p, k...)
+ p = append(p, ": "...)
+ for i := 0; i < len(v); i++ {
+ b := v[i]
+ if b <= 31 {
+ // prevent response splitting.
+ b = ' '
+ }
+ p = append(p, b)
+ }
+ p = append(p, "\r\n"...)
+ }
+ }
+ p = append(p, "\r\n"...)
+
+ // Clear deadlines set by HTTP server.
+ netConn.SetDeadline(time.Time{})
+
+ if u.HandshakeTimeout > 0 {
+ netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
+ }
+ if _, err = netConn.Write(p); err != nil {
+ netConn.Close()
+ return nil, err
+ }
+ if u.HandshakeTimeout > 0 {
+ netConn.SetWriteDeadline(time.Time{})
+ }
+
+ return c, nil
+}
+
+// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
+//
+// This function is deprecated, use websocket.Upgrader instead.
+//
+// The application is responsible for checking the request origin before
+// calling Upgrade. An example implementation of the same origin policy is:
+//
+// if req.Header.Get("Origin") != "http://"+req.Host {
+// http.Error(w, "Origin not allowed", 403)
+// return
+// }
+//
+// If the endpoint supports subprotocols, then the application is responsible
+// for negotiating the protocol used on the connection. Use the Subprotocols()
+// function to get the subprotocols requested by the client. Use the
+// Sec-Websocket-Protocol response header to specify the subprotocol selected
+// by the application.
+//
+// The responseHeader is included in the response to the client's upgrade
+// request. Use the responseHeader to specify cookies (Set-Cookie) and the
+// negotiated subprotocol (Sec-Websocket-Protocol).
+//
+// The connection buffers IO to the underlying network connection. The
+// readBufSize and writeBufSize parameters specify the size of the buffers to
+// use. Messages can be larger than the buffers.
+//
+// If the request is not a valid WebSocket handshake, then Upgrade returns an
+// error of type HandshakeError. Applications should handle this error by
+// replying to the client with an HTTP error response.
+func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
+ u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
+ u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
+ // don't return errors to maintain backwards compatibility
+ }
+ u.CheckOrigin = func(r *http.Request) bool {
+ // allow all connections by default
+ return true
+ }
+ return u.Upgrade(w, r, responseHeader)
+}
+
+// Subprotocols returns the subprotocols requested by the client in the
+// Sec-Websocket-Protocol header.
+func Subprotocols(r *http.Request) []string {
+ h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
+ if h == "" {
+ return nil
+ }
+ protocols := strings.Split(h, ",")
+ for i := range protocols {
+ protocols[i] = strings.TrimSpace(protocols[i])
+ }
+ return protocols
+}
+
+// IsWebSocketUpgrade returns true if the client requested upgrade to the
+// WebSocket protocol.
+func IsWebSocketUpgrade(r *http.Request) bool {
+ return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
+ tokenListContainsValue(r.Header, "Upgrade", "websocket")
+}
diff --git a/vendor/github.com/gorilla/websocket/util.go b/vendor/github.com/gorilla/websocket/util.go
new file mode 100644
index 00000000..ffdc265e
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/util.go
@@ -0,0 +1,44 @@
+// 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 (
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/base64"
+ "io"
+ "net/http"
+ "strings"
+)
+
+// tokenListContainsValue returns true if the 1#token header with the given
+// name contains token.
+func tokenListContainsValue(header http.Header, name string, value string) bool {
+ for _, v := range header[name] {
+ for _, s := range strings.Split(v, ",") {
+ if strings.EqualFold(value, strings.TrimSpace(s)) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
+
+func computeAcceptKey(challengeKey string) string {
+ h := sha1.New()
+ h.Write([]byte(challengeKey))
+ h.Write(keyGUID)
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
+
+func generateChallengeKey() (string, error) {
+ p := make([]byte, 16)
+ if _, err := io.ReadFull(rand.Reader, p); err != nil {
+ return "", err
+ }
+ return base64.StdEncoding.EncodeToString(p), nil
+}
diff --git a/vendor/github.com/jpillora/backoff/backoff.go b/vendor/github.com/jpillora/backoff/backoff.go
new file mode 100644
index 00000000..c8ea03a6
--- /dev/null
+++ b/vendor/github.com/jpillora/backoff/backoff.go
@@ -0,0 +1,69 @@
+package backoff
+
+import (
+ "math"
+ "math/rand"
+ "time"
+)
+
+//Backoff is a time.Duration counter. It starts at Min.
+//After every call to Duration() it is multiplied by Factor.
+//It is capped at Max. It returns to Min on every call to Reset().
+//Used in conjunction with the time package.
+//
+// Backoff is not threadsafe, but the ForAttempt method can be
+// used concurrently if non-zero values for Factor, Max, and Min
+// are set on the Backoff shared among threads.
+type Backoff struct {
+ //Factor is the multiplying factor for each increment step
+ attempts, Factor float64
+ //Jitter eases contention by randomizing backoff steps
+ Jitter bool
+ //Min and Max are the minimum and maximum values of the counter
+ Min, Max time.Duration
+}
+
+//Returns the current value of the counter and then
+//multiplies it Factor
+func (b *Backoff) Duration() time.Duration {
+ d := b.ForAttempt(b.attempts)
+ b.attempts++
+ return d
+}
+
+// ForAttempt returns the duration for a specific attempt. This is useful if
+// you have a large number of independent Backoffs, but don't want use
+// unnecessary memory storing the Backoff parameters per Backoff. The first
+// attempt should be 0.
+//
+// ForAttempt is threadsafe iff non-zero values for Factor, Max, and Min
+// are set before any calls to ForAttempt are made.
+func (b *Backoff) ForAttempt(attempt float64) time.Duration {
+ //Zero-values are nonsensical, so we use
+ //them to apply defaults
+ if b.Min == 0 {
+ b.Min = 100 * time.Millisecond
+ }
+ if b.Max == 0 {
+ b.Max = 10 * time.Second
+ }
+ if b.Factor == 0 {
+ b.Factor = 2
+ }
+ //calculate this duration
+ dur := float64(b.Min) * math.Pow(b.Factor, attempt)
+ if b.Jitter == true {
+ dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min)
+ }
+ //cap!
+ if dur > float64(b.Max) {
+ return b.Max
+ }
+ //return as a time.Duration
+ return time.Duration(dur)
+}
+
+//Resets the current value of the counter back to Min
+func (b *Backoff) Reset() {
+ b.attempts = 0
+}
diff --git a/vendor/github.com/mattermost/platform/model/LICENSE.txt b/vendor/github.com/mattermost/platform/model/LICENSE.txt
new file mode 100644
index 00000000..b05ccb40
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/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 (api/templates/, config/, model/,
+web/react/utils/, web/static/, web/templates/ 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. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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
+<http://www.gnu.org/licenses/>.
diff --git a/vendor/github.com/mattermost/platform/model/access.go b/vendor/github.com/mattermost/platform/model/access.go
new file mode 100644
index 00000000..877b3c4f
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/access.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ ACCESS_TOKEN_GRANT_TYPE = "authorization_code"
+ ACCESS_TOKEN_TYPE = "bearer"
+ REFRESH_TOKEN_GRANT_TYPE = "refresh_token"
+)
+
+type AccessData struct {
+ AuthCode string `json:"auth_code"`
+ Token string `json:"token"`
+ RefreshToken string `json:"refresh_token"`
+ RedirectUri string `json:"redirect_uri"`
+}
+
+type AccessResponse struct {
+ AccessToken string `json:"access_token"`
+ TokenType string `json:"token_type"`
+ ExpiresIn int32 `json:"expires_in"`
+ Scope string `json:"scope"`
+ RefreshToken string `json:"refresh_token"`
+}
+
+// IsValid validates the AccessData and returns an error if it isn't configured
+// correctly.
+func (ad *AccessData) IsValid() *AppError {
+
+ if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 {
+ return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "")
+ }
+
+ if len(ad.Token) != 26 {
+ return NewLocAppError("AccessData.IsValid", "model.access.is_valid.access_token.app_error", nil, "")
+ }
+
+ if len(ad.RefreshToken) > 26 {
+ return NewLocAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "")
+ }
+
+ if len(ad.RedirectUri) > 256 {
+ return NewLocAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (ad *AccessData) ToJson() string {
+ b, err := json.Marshal(ad)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func AccessDataFromJson(data io.Reader) *AccessData {
+ decoder := json.NewDecoder(data)
+ var ad AccessData
+ err := decoder.Decode(&ad)
+ if err == nil {
+ return &ad
+ } else {
+ return nil
+ }
+}
+
+func (ar *AccessResponse) ToJson() string {
+ b, err := json.Marshal(ar)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func AccessResponseFromJson(data io.Reader) *AccessResponse {
+ decoder := json.NewDecoder(data)
+ var ar AccessResponse
+ err := decoder.Decode(&ar)
+ if err == nil {
+ return &ar
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/analytics_row.go b/vendor/github.com/mattermost/platform/model/analytics_row.go
new file mode 100644
index 00000000..ed1d69dd
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/analytics_row.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type AnalyticsRow struct {
+ Name string `json:"name"`
+ Value float64 `json:"value"`
+}
+
+type AnalyticsRows []*AnalyticsRow
+
+func (me *AnalyticsRow) ToJson() string {
+ b, err := json.Marshal(me)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow {
+ decoder := json.NewDecoder(data)
+ var me AnalyticsRow
+ err := decoder.Decode(&me)
+ if err == nil {
+ return &me
+ } else {
+ return nil
+ }
+}
+
+func (me AnalyticsRows) ToJson() string {
+ if b, err := json.Marshal(me); err != nil {
+ return "[]"
+ } else {
+ return string(b)
+ }
+}
+
+func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows {
+ decoder := json.NewDecoder(data)
+ var me AnalyticsRows
+ err := decoder.Decode(&me)
+ if err == nil {
+ return me
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/audit.go b/vendor/github.com/mattermost/platform/model/audit.go
new file mode 100644
index 00000000..8fa1d558
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/audit.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type Audit struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UserId string `json:"user_id"`
+ Action string `json:"action"`
+ ExtraInfo string `json:"extra_info"`
+ IpAddress string `json:"ip_address"`
+ SessionId string `json:"session_id"`
+}
+
+func (o *Audit) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func AuditFromJson(data io.Reader) *Audit {
+ decoder := json.NewDecoder(data)
+ var o Audit
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/audits.go b/vendor/github.com/mattermost/platform/model/audits.go
new file mode 100644
index 00000000..36c80629
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/audits.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type Audits []Audit
+
+func (o Audits) Etag() string {
+ if len(o) > 0 {
+ // the first in the list is always the most current
+ return Etag(o[0].CreateAt)
+ } else {
+ return ""
+ }
+}
+
+func (o Audits) ToJson() string {
+ if b, err := json.Marshal(o); err != nil {
+ return "[]"
+ } else {
+ return string(b)
+ }
+}
+
+func AuditsFromJson(data io.Reader) Audits {
+ decoder := json.NewDecoder(data)
+ var o Audits
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/authorize.go b/vendor/github.com/mattermost/platform/model/authorize.go
new file mode 100644
index 00000000..e0d665ba
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/authorize.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes
+ AUTHCODE_RESPONSE_TYPE = "code"
+)
+
+type AuthData struct {
+ ClientId string `json:"client_id"`
+ UserId string `json:"user_id"`
+ Code string `json:"code"`
+ ExpiresIn int32 `json:"expires_in"`
+ CreateAt int64 `json:"create_at"`
+ RedirectUri string `json:"redirect_uri"`
+ State string `json:"state"`
+ Scope string `json:"scope"`
+}
+
+// IsValid validates the AuthData and returns an error if it isn't configured
+// correctly.
+func (ad *AuthData) IsValid() *AppError {
+
+ if len(ad.ClientId) != 26 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "")
+ }
+
+ if len(ad.UserId) != 26 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "")
+ }
+
+ if len(ad.Code) == 0 || len(ad.Code) > 128 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.auth_code.app_error", nil, "client_id="+ad.ClientId)
+ }
+
+ if ad.ExpiresIn == 0 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.expires.app_error", nil, "")
+ }
+
+ if ad.CreateAt <= 0 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId)
+ }
+
+ if len(ad.RedirectUri) > 256 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId)
+ }
+
+ if len(ad.State) > 128 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ad.ClientId)
+ }
+
+ if len(ad.Scope) > 128 {
+ return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ad.ClientId)
+ }
+
+ return nil
+}
+
+func (ad *AuthData) PreSave() {
+ if ad.ExpiresIn == 0 {
+ ad.ExpiresIn = AUTHCODE_EXPIRE_TIME
+ }
+
+ if ad.CreateAt == 0 {
+ ad.CreateAt = GetMillis()
+ }
+}
+
+func (ad *AuthData) ToJson() string {
+ b, err := json.Marshal(ad)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func AuthDataFromJson(data io.Reader) *AuthData {
+ decoder := json.NewDecoder(data)
+ var ad AuthData
+ err := decoder.Decode(&ad)
+ if err == nil {
+ return &ad
+ } else {
+ return nil
+ }
+}
+
+func (ad *AuthData) IsExpired() bool {
+
+ if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) {
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel.go b/vendor/github.com/mattermost/platform/model/channel.go
new file mode 100644
index 00000000..e7002e3c
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/channel.go
@@ -0,0 +1,136 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "unicode/utf8"
+)
+
+const (
+ CHANNEL_OPEN = "O"
+ CHANNEL_PRIVATE = "P"
+ CHANNEL_DIRECT = "D"
+ DEFAULT_CHANNEL = "town-square"
+)
+
+type Channel struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ TeamId string `json:"team_id"`
+ Type string `json:"type"`
+ DisplayName string `json:"display_name"`
+ Name string `json:"name"`
+ Header string `json:"header"`
+ Purpose string `json:"purpose"`
+ LastPostAt int64 `json:"last_post_at"`
+ TotalMsgCount int64 `json:"total_msg_count"`
+ ExtraUpdateAt int64 `json:"extra_update_at"`
+ CreatorId string `json:"creator_id"`
+}
+
+func (o *Channel) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelFromJson(data io.Reader) *Channel {
+ decoder := json.NewDecoder(data)
+ var o Channel
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *Channel) Etag() string {
+ return Etag(o.Id, o.UpdateAt)
+}
+
+func (o *Channel) ExtraEtag(memberLimit int) string {
+ return Etag(o.Id, o.ExtraUpdateAt, memberLimit)
+}
+
+func (o *Channel) IsValid() *AppError {
+
+ if len(o.Id) != 26 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "")
+ }
+
+ if o.CreateAt == 0 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id)
+ }
+
+ if o.UpdateAt == 0 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id)
+ }
+
+ if utf8.RuneCountInString(o.DisplayName) > 64 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.Name) > 64 {
+ 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) {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id)
+ }
+
+ if utf8.RuneCountInString(o.Header) > 1024 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id)
+ }
+
+ if utf8.RuneCountInString(o.Purpose) > 128 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.CreatorId) > 26 {
+ return NewLocAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (o *Channel) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ o.CreateAt = GetMillis()
+ o.UpdateAt = o.CreateAt
+ o.ExtraUpdateAt = o.CreateAt
+}
+
+func (o *Channel) PreUpdate() {
+ o.UpdateAt = GetMillis()
+}
+
+func (o *Channel) ExtraUpdated() {
+ o.ExtraUpdateAt = GetMillis()
+}
+
+func (o *Channel) PreExport() {
+}
+
+func GetDMNameFromIds(userId1, userId2 string) string {
+ if userId1 > userId2 {
+ return userId2 + "__" + userId1
+ } else {
+ return userId1 + "__" + userId2
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel_count.go b/vendor/github.com/mattermost/platform/model/channel_count.go
new file mode 100644
index 00000000..6cc1b2f2
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/channel_count.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "crypto/md5"
+ "encoding/json"
+ "fmt"
+ "io"
+ "sort"
+ "strconv"
+)
+
+type ChannelCounts struct {
+ Counts map[string]int64 `json:"counts"`
+ UpdateTimes map[string]int64 `json:"update_times"`
+}
+
+func (o *ChannelCounts) Etag() string {
+
+ ids := []string{}
+ for id := range o.Counts {
+ ids = append(ids, id)
+ }
+ sort.Strings(ids)
+
+ str := ""
+ for _, id := range ids {
+ str += id + strconv.FormatInt(o.Counts[id], 10)
+ }
+
+ md5Counts := fmt.Sprintf("%x", md5.Sum([]byte(str)))
+
+ var update int64 = 0
+ for _, u := range o.UpdateTimes {
+ if u > update {
+ update = u
+ }
+ }
+
+ return Etag(md5Counts, update)
+}
+
+func (o *ChannelCounts) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelCountsFromJson(data io.Reader) *ChannelCounts {
+ decoder := json.NewDecoder(data)
+ var o ChannelCounts
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel_data.go b/vendor/github.com/mattermost/platform/model/channel_data.go
new file mode 100644
index 00000000..731d50e7
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/channel_data.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type ChannelData struct {
+ Channel *Channel `json:"channel"`
+ Member *ChannelMember `json:"member"`
+}
+
+func (o *ChannelData) Etag() string {
+ var mt int64 = 0
+ if o.Member != nil {
+ mt = o.Member.LastUpdateAt
+ }
+
+ return Etag(o.Channel.Id, o.Channel.UpdateAt, o.Channel.LastPostAt, mt)
+}
+
+func (o *ChannelData) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelDataFromJson(data io.Reader) *ChannelData {
+ decoder := json.NewDecoder(data)
+ var o ChannelData
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel_extra.go b/vendor/github.com/mattermost/platform/model/channel_extra.go
new file mode 100644
index 00000000..55da588a
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/channel_extra.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type ExtraMember struct {
+ Id string `json:"id"`
+ Nickname string `json:"nickname"`
+ Email string `json:"email"`
+ Roles string `json:"roles"`
+ Username string `json:"username"`
+}
+
+func (o *ExtraMember) Sanitize(options map[string]bool) {
+ if len(options) == 0 || !options["email"] {
+ o.Email = ""
+ }
+}
+
+type ChannelExtra struct {
+ Id string `json:"id"`
+ Members []ExtraMember `json:"members"`
+ MemberCount int64 `json:"member_count"`
+}
+
+func (o *ChannelExtra) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelExtraFromJson(data io.Reader) *ChannelExtra {
+ decoder := json.NewDecoder(data)
+ var o ChannelExtra
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel_list.go b/vendor/github.com/mattermost/platform/model/channel_list.go
new file mode 100644
index 00000000..49ba384a
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/channel_list.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type ChannelList struct {
+ Channels []*Channel `json:"channels"`
+ Members map[string]*ChannelMember `json:"members"`
+}
+
+func (o *ChannelList) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func (o *ChannelList) Etag() string {
+
+ id := "0"
+ var t int64 = 0
+ var delta int64 = 0
+
+ for _, v := range o.Channels {
+ if v.LastPostAt > t {
+ t = v.LastPostAt
+ id = v.Id
+ }
+
+ if v.UpdateAt > t {
+ t = v.UpdateAt
+ id = v.Id
+ }
+
+ member := o.Members[v.Id]
+
+ if member != nil {
+ max := v.LastPostAt
+ if v.UpdateAt > max {
+ max = v.UpdateAt
+ }
+
+ delta += max - member.LastViewedAt
+
+ if member.LastViewedAt > t {
+ t = member.LastViewedAt
+ id = v.Id
+ }
+
+ if member.LastUpdateAt > t {
+ t = member.LastUpdateAt
+ id = v.Id
+ }
+
+ }
+ }
+
+ return Etag(id, t, delta, len(o.Channels))
+}
+
+func ChannelListFromJson(data io.Reader) *ChannelList {
+ decoder := json.NewDecoder(data)
+ var o ChannelList
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/channel_member.go b/vendor/github.com/mattermost/platform/model/channel_member.go
new file mode 100644
index 00000000..66e20da6
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/channel_member.go
@@ -0,0 +1,108 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "strings"
+)
+
+const (
+ CHANNEL_ROLE_ADMIN = "admin"
+ CHANNEL_NOTIFY_DEFAULT = "default"
+ CHANNEL_NOTIFY_ALL = "all"
+ CHANNEL_NOTIFY_MENTION = "mention"
+ CHANNEL_NOTIFY_NONE = "none"
+ CHANNEL_MARK_UNREAD_ALL = "all"
+ CHANNEL_MARK_UNREAD_MENTION = "mention"
+)
+
+type ChannelMember struct {
+ ChannelId string `json:"channel_id"`
+ UserId string `json:"user_id"`
+ Roles string `json:"roles"`
+ LastViewedAt int64 `json:"last_viewed_at"`
+ MsgCount int64 `json:"msg_count"`
+ MentionCount int64 `json:"mention_count"`
+ NotifyProps StringMap `json:"notify_props"`
+ LastUpdateAt int64 `json:"last_update_at"`
+}
+
+func (o *ChannelMember) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelMemberFromJson(data io.Reader) *ChannelMember {
+ decoder := json.NewDecoder(data)
+ var o ChannelMember
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *ChannelMember) IsValid() *AppError {
+
+ if len(o.ChannelId) != 26 {
+ return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "")
+ }
+
+ if len(o.UserId) != 26 {
+ return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "")
+ }
+
+ for _, role := range strings.Split(o.Roles, " ") {
+ if !(role == "" || role == CHANNEL_ROLE_ADMIN) {
+ return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.role.app_error", nil, "role="+role)
+ }
+ }
+
+ notifyLevel := o.NotifyProps["desktop"]
+ 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"]
+ if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) {
+ return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error",
+ nil, "mark_unread_level="+markUnreadLevel)
+ }
+
+ return nil
+}
+
+func (o *ChannelMember) PreSave() {
+ o.LastUpdateAt = GetMillis()
+}
+
+func (o *ChannelMember) PreUpdate() {
+ o.LastUpdateAt = GetMillis()
+}
+
+func IsChannelNotifyLevelValid(notifyLevel string) bool {
+ return notifyLevel == CHANNEL_NOTIFY_DEFAULT ||
+ notifyLevel == CHANNEL_NOTIFY_ALL ||
+ notifyLevel == CHANNEL_NOTIFY_MENTION ||
+ notifyLevel == CHANNEL_NOTIFY_NONE
+}
+
+func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool {
+ return markUnreadLevel == CHANNEL_MARK_UNREAD_ALL || markUnreadLevel == CHANNEL_MARK_UNREAD_MENTION
+}
+
+func GetDefaultChannelNotifyProps() StringMap {
+ return StringMap{
+ "desktop": CHANNEL_NOTIFY_DEFAULT,
+ "mark_unread": CHANNEL_MARK_UNREAD_ALL,
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/client.go b/vendor/github.com/mattermost/platform/model/client.go
new file mode 100644
index 00000000..fba4246e
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/client.go
@@ -0,0 +1,1144 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "fmt"
+ l4g "github.com/alecthomas/log4go"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const (
+ HEADER_REQUEST_ID = "X-Request-ID"
+ HEADER_VERSION_ID = "X-Version-ID"
+ HEADER_ETAG_SERVER = "ETag"
+ HEADER_ETAG_CLIENT = "If-None-Match"
+ HEADER_FORWARDED = "X-Forwarded-For"
+ HEADER_REAL_IP = "X-Real-IP"
+ HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
+ HEADER_TOKEN = "token"
+ HEADER_BEARER = "BEARER"
+ HEADER_AUTH = "Authorization"
+ HEADER_REQUESTED_WITH = "X-Requested-With"
+ HEADER_REQUESTED_WITH_XML = "XMLHttpRequest"
+ API_URL_SUFFIX = "/api/v1"
+)
+
+type Result struct {
+ RequestId string
+ Etag string
+ Data interface{}
+}
+
+type Client struct {
+ Url string // The location of the server like "http://localhost:8065"
+ ApiUrl string // The api location of the server like "http://localhost:8065/api/v1"
+ HttpClient *http.Client // The http client
+ AuthToken string
+ AuthType string
+}
+
+// NewClient constructs a new client with convienence methods for talking to
+// the server.
+func NewClient(url string) *Client {
+ return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", ""}
+}
+
+func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
+ rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
+ rq.Header.Set("Content-Type", contentType)
+
+ if rp, err := c.HttpClient.Do(rq); err != nil {
+ return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
+ } else if rp.StatusCode >= 300 {
+ return nil, AppErrorFromJson(rp.Body)
+ } else {
+ return rp, nil
+ }
+}
+
+func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) {
+ rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data))
+
+ 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 >= 300 {
+ return nil, AppErrorFromJson(rp.Body)
+ } else {
+ return rp, nil
+ }
+}
+
+func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
+ rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
+
+ 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 {
+ return rp, AppErrorFromJson(rp.Body)
+ } else {
+ return rp, nil
+ }
+}
+
+func getCookie(name string, resp *http.Response) *http.Cookie {
+ for _, cookie := range resp.Cookies() {
+ if cookie.Name == name {
+ return cookie
+ }
+ }
+
+ return nil
+}
+
+func (c *Client) Must(result *Result, err *AppError) *Result {
+ if err != nil {
+ l4g.Close()
+ time.Sleep(time.Second)
+ panic(err)
+ }
+
+ return result
+}
+
+func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["email"] = email
+ m["display_name"] = displayName
+ if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateTeam(team *Team) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetAllTeams() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/teams/all", "", ""); err != nil {
+ return nil, err
+ } else {
+
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["name"] = name
+ m["all"] = fmt.Sprintf("%v", allServers)
+ if r, err := c.DoApiPost("/teams/find_team_by_name", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ val := false
+ if body, _ := ioutil.ReadAll(r.Body); string(body) == "true" {
+ val = true
+ }
+
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), val}, nil
+ }
+}
+
+func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/teams/invite_members", invites.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), InvitesFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/teams/update", team.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/create?d="+url.QueryEscape(data)+"&h="+hash, user.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/"+id, "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetMe(etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/me", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["id"] = id
+ m["password"] = password
+ return c.login(m)
+}
+
+func (c *Client) LoginByEmail(name string, email string, password string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["name"] = name
+ m["email"] = email
+ m["password"] = password
+ return c.login(m)
+}
+
+func (c *Client) LoginByUsername(name string, username string, password string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["name"] = name
+ m["username"] = username
+ m["password"] = password
+ return c.login(m)
+}
+
+func (c *Client) LoginByEmailWithDevice(name string, email string, password string, deviceId string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["name"] = name
+ m["email"] = email
+ m["password"] = password
+ m["device_id"] = deviceId
+ return c.login(m)
+}
+
+func (c *Client) login(m map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ c.AuthToken = r.Header.Get(HEADER_TOKEN)
+ c.AuthType = HEADER_BEARER
+ sessionToken := getCookie(SESSION_COOKIE_TOKEN, r)
+
+ if c.AuthToken != sessionToken.Value {
+ NewLocAppError("/users/login", "model.client.login.app_error", nil, "")
+ }
+
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) Logout() (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/logout", ""); err != nil {
+ return nil, err
+ } else {
+ c.AuthToken = ""
+ c.AuthType = HEADER_BEARER
+
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CheckMfa(method, teamName, loginId string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["method"] = method
+ m["team_name"] = teamName
+ m["login_id"] = loginId
+
+ if r, err := c.DoApiPost("/users/mfa", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GenerateMfaQrCode() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), r.Body}, nil
+ }
+}
+
+func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) {
+ m := make(map[string]interface{})
+ m["activate"] = activate
+ m["token"] = token
+
+ if r, err := c.DoApiPost("/users/update_mfa", StringInterfaceToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) SetOAuthToken(token string) {
+ c.AuthToken = token
+ c.AuthType = HEADER_TOKEN
+}
+
+func (c *Client) ClearOAuthToken() {
+ c.AuthToken = ""
+ c.AuthType = HEADER_BEARER
+}
+
+func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["id"] = sessionAltId
+
+ if r, err := c.DoApiPost("/users/revoke_session", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetSessions(id string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/"+id+"/sessions", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), SessionsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) EmailToOAuth(m map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/claim/email_to_sso", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) OAuthToEmail(m map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/claim/oauth_to_email", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) LDAPToEmail(m map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) {
+ m := make(map[string]string)
+ m["command"] = command
+ m["channelId"] = channelId
+ m["suggest"] = strconv.FormatBool(suggest)
+ if r, err := c.DoApiPost("/commands/execute", MapToJson(m)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) ListCommands() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/commands/list", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) ListTeamCommands() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/commands/list_team_commands", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/commands/create", cmd.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/commands/regen_token", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/commands/delete", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetLogs() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetAllAudits() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetClientProperties() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/client_props", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetConfig() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/config", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ConfigFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) SaveConfig(config *Config) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ConfigFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) TestEmail(config *Config) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/admin/test_email", config.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetComplianceReports() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), CompliancesFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/admin/save_compliance_report", job.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ComplianceFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) {
+ var rq *http.Request
+ rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil)
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
+ }
+
+ if rp, err := c.HttpClient.Do(rq); err != nil {
+ return nil, NewLocAppError("/admin/download_compliance_report", "model.client.connecting.app_error", nil, err.Error())
+ } else if rp.StatusCode >= 300 {
+ return nil, AppErrorFromJson(rp.Body)
+ } else {
+ return &Result{rp.Header.Get(HEADER_REQUEST_ID),
+ rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil
+ }
+}
+
+func (c *Client) GetTeamAnalytics(teamId, name string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/admin/analytics/"+name, "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/create_direct", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ 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("/channels/update", channel.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/update_header", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/update_purpose", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/update_notify_props", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetChannels(etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/channels/", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/channels/"+id+"/", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelDataFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/channels/more", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/channels/counts", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelCountsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) JoinChannel(id string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/"+id+"/join", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) LeaveChannel(id string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/"+id+"/leave", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) DeleteChannel(id string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/"+id+"/delete", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["user_id"] = user_id
+ if r, err := c.DoApiPost("/channels/"+id+"/add", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["user_id"] = user_id
+ if r, err := c.DoApiPost("/channels/"+id+"/remove", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/"+channelId+"/update_last_viewed_at", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/channels/"+id+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/create", post.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdatePost(post *Post) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/update", post.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/posts/%v/%v", channelId, offset, limit), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/posts/%v", channelId, time), "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPostsBefore(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/before/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/after/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) {
+ if r, err := c.DoApiPost(fmt.Sprintf("/channels/%v/post/%v/delete", channelId, postId), ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) SearchPosts(terms string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/posts/search?terms="+url.QueryEscape(terms), "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
+ rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data))
+ rq.Header.Set("Content-Type", contentType)
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, "BEARER "+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 >= 300 {
+ return nil, AppErrorFromJson(rp.Body)
+ } else {
+ return &Result{rp.Header.Get(HEADER_REQUEST_ID),
+ rp.Header.Get(HEADER_ETAG_SERVER), FileUploadResponseFromJson(rp.Body)}, nil
+ }
+}
+
+func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
+ var rq *http.Request
+ if isFullUrl {
+ rq, _ = http.NewRequest("GET", url, nil)
+ } else {
+ rq, _ = http.NewRequest("GET", c.ApiUrl+"/files/get"+url, nil)
+ }
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, "BEARER "+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 >= 300 {
+ return nil, AppErrorFromJson(rp.Body)
+ } else {
+ return &Result{rp.Header.Get(HEADER_REQUEST_ID),
+ rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil
+ }
+}
+
+func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
+ var rq *http.Request
+ rq, _ = http.NewRequest("GET", c.ApiUrl+"/files/get_info"+url, nil)
+
+ if len(c.AuthToken) > 0 {
+ rq.Header.Set(HEADER_AUTH, "BEARER "+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 >= 300 {
+ return nil, AppErrorFromJson(rp.Body)
+ } else {
+ return &Result{rp.Header.Get(HEADER_REQUEST_ID),
+ rp.Header.Get(HEADER_ETAG_SERVER), FileInfoFromJson(rp.Body)}, nil
+ }
+}
+
+func (c *Client) GetPublicLink(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/files/get_public_link", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateUser(user *User) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/update", user.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/update_roles", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["device_id"] = deviceId
+ if r, err := c.DoApiPost("/users/attach_device", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["user_id"] = userId
+ data["active"] = strconv.FormatBool(active)
+ if r, err := c.DoApiPost("/users/update_active", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/update_notify", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string) (*Result, *AppError) {
+ data := make(map[string]string)
+ data["current_password"] = currentPassword
+ data["new_password"] = newPassword
+ data["user_id"] = userId
+
+ if r, err := c.DoApiPost("/users/newpassword", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/send_password_reset", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/reset_password", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetStatuses(data []string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/teams/me", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), AccessResponseFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/hooks/incoming/create", hook.ToJson()); err != nil {
+ return nil, err
+ } else {
+ 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
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
+ }
+}
+
+func (c *Client) DeleteIncomingWebhook(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/hooks/incoming/delete", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) ListIncomingWebhooks() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/hooks/incoming/list", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetAllPreferences() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/preferences/", "", ""); err != nil {
+ return nil, err
+ } else {
+ preferences, _ := PreferencesFromJson(r.Body)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil
+ }
+}
+
+func (c *Client) SetPreferences(preferences *Preferences) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/preferences/save", preferences.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil
+ }
+}
+
+func (c *Client) GetPreference(category string, name string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/preferences/"+category+"/"+name, "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PreferenceFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) {
+ if r, err := c.DoApiGet("/preferences/"+category, "", ""); err != nil {
+ return nil, err
+ } else {
+ preferences, _ := PreferencesFromJson(r.Body)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil
+ }
+}
+
+func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/hooks/outgoing/create", hook.ToJson()); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) DeleteOutgoingWebhook(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/hooks/outgoing/delete", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/hooks/outgoing/list", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) RegenOutgoingWebhookToken(data map[string]string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/hooks/outgoing/regen_token", MapToJson(data)); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) MockSession(sessionToken string) {
+ c.AuthToken = sessionToken
+ c.AuthType = HEADER_BEARER
+}
+
+func (c *Client) GetClientLicenceConfig() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/license/client_config", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetMeLoggedIn() (*Result, *AppError) {
+ if r, err := c.DoApiGet("/users/me_logged_in", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/command.go b/vendor/github.com/mattermost/platform/model/command.go
new file mode 100644
index 00000000..b854ae76
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/command.go
@@ -0,0 +1,153 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ COMMAND_METHOD_POST = "P"
+ COMMAND_METHOD_GET = "G"
+)
+
+type Command struct {
+ Id string `json:"id"`
+ Token string `json:"token"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ CreatorId string `json:"creator_id"`
+ TeamId string `json:"team_id"`
+ Trigger string `json:"trigger"`
+ Method string `json:"method"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ AutoComplete bool `json:"auto_complete"`
+ AutoCompleteDesc string `json:"auto_complete_desc"`
+ AutoCompleteHint string `json:"auto_complete_hint"`
+ DisplayName string `json:"display_name"`
+ Description string `json:"description"`
+ URL string `json:"url"`
+}
+
+func (o *Command) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func CommandFromJson(data io.Reader) *Command {
+ decoder := json.NewDecoder(data)
+ var o Command
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func CommandListToJson(l []*Command) string {
+ b, err := json.Marshal(l)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func CommandListFromJson(data io.Reader) []*Command {
+ decoder := json.NewDecoder(data)
+ var o []*Command
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
+
+func (o *Command) IsValid() *AppError {
+
+ if len(o.Id) != 26 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.id.app_error", nil, "")
+ }
+
+ if len(o.Token) != 26 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.token.app_error", nil, "")
+ }
+
+ if o.CreateAt == 0 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.create_at.app_error", nil, "")
+ }
+
+ if o.UpdateAt == 0 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.update_at.app_error", nil, "")
+ }
+
+ if len(o.CreatorId) != 26 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "")
+ }
+
+ if len(o.TeamId) != 26 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "")
+ }
+
+ if len(o.Trigger) > 128 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "")
+ }
+
+ if len(o.URL) == 0 || len(o.URL) > 1024 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.url.app_error", nil, "")
+ }
+
+ if !IsValidHttpUrl(o.URL) {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.url_http.app_error", nil, "")
+ }
+
+ if !(o.Method == COMMAND_METHOD_GET || o.Method == COMMAND_METHOD_POST) {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "")
+ }
+
+ if len(o.DisplayName) > 64 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.display_name.app_error", nil, "")
+ }
+
+ if len(o.Description) > 128 {
+ return NewLocAppError("Command.IsValid", "model.command.is_valid.description.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (o *Command) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ if o.Token == "" {
+ o.Token = NewId()
+ }
+
+ o.CreateAt = GetMillis()
+ o.UpdateAt = o.CreateAt
+}
+
+func (o *Command) PreUpdate() {
+ o.UpdateAt = GetMillis()
+}
+
+func (o *Command) Sanitize() {
+ o.Token = ""
+ o.CreatorId = ""
+ o.Method = ""
+ o.URL = ""
+ o.Username = ""
+ o.IconURL = ""
+}
diff --git a/vendor/github.com/mattermost/platform/model/command_response.go b/vendor/github.com/mattermost/platform/model/command_response.go
new file mode 100644
index 00000000..9314f38e
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/command_response.go
@@ -0,0 +1,41 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ COMMAND_RESPONSE_TYPE_IN_CHANNEL = "in_channel"
+ COMMAND_RESPONSE_TYPE_EPHEMERAL = "ephemeral"
+)
+
+type CommandResponse struct {
+ ResponseType string `json:"response_type"`
+ Text string `json:"text"`
+ GotoLocation string `json:"goto_location"`
+ Attachments interface{} `json:"attachments"`
+}
+
+func (o *CommandResponse) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func CommandResponseFromJson(data io.Reader) *CommandResponse {
+ decoder := json.NewDecoder(data)
+ var o CommandResponse
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/compliance.go b/vendor/github.com/mattermost/platform/model/compliance.go
new file mode 100644
index 00000000..4a96a597
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/compliance.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "strings"
+)
+
+const (
+ COMPLIANCE_STATUS_CREATED = "created"
+ COMPLIANCE_STATUS_RUNNING = "running"
+ COMPLIANCE_STATUS_FINISHED = "finished"
+ COMPLIANCE_STATUS_FAILED = "failed"
+ COMPLIANCE_STATUS_REMOVED = "removed"
+
+ COMPLIANCE_TYPE_DAILY = "daily"
+ COMPLIANCE_TYPE_ADHOC = "adhoc"
+)
+
+type Compliance struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UserId string `json:"user_id"`
+ Status string `json:"status"`
+ Count int `json:"count"`
+ Desc string `json:"desc"`
+ Type string `json:"type"`
+ StartAt int64 `json:"start_at"`
+ EndAt int64 `json:"end_at"`
+ Keywords string `json:"keywords"`
+ Emails string `json:"emails"`
+}
+
+type Compliances []Compliance
+
+func (o *Compliance) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func (me *Compliance) PreSave() {
+ if me.Id == "" {
+ me.Id = NewId()
+ }
+
+ if me.Status == "" {
+ me.Status = COMPLIANCE_STATUS_CREATED
+ }
+
+ me.Count = 0
+ me.Emails = strings.ToLower(me.Emails)
+ me.Keywords = strings.ToLower(me.Keywords)
+
+ me.CreateAt = GetMillis()
+}
+
+func (me *Compliance) JobName() string {
+ jobName := me.Type
+ if me.Type == COMPLIANCE_TYPE_DAILY {
+ jobName += "-" + me.Desc
+ }
+
+ jobName += "-" + me.Id
+
+ return jobName
+}
+
+func (me *Compliance) IsValid() *AppError {
+
+ if len(me.Id) != 26 {
+ return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.id.app_error", nil, "")
+ }
+
+ if me.CreateAt == 0 {
+ return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "")
+ }
+
+ if len(me.Desc) > 512 || len(me.Desc) == 0 {
+ return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "")
+ }
+
+ if me.StartAt == 0 {
+ return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.start_at.app_error", nil, "")
+ }
+
+ if me.EndAt == 0 {
+ return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.end_at.app_error", nil, "")
+ }
+
+ if me.EndAt <= me.StartAt {
+ return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.start_end_at.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func ComplianceFromJson(data io.Reader) *Compliance {
+ decoder := json.NewDecoder(data)
+ var o Compliance
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o Compliances) ToJson() string {
+ if b, err := json.Marshal(o); err != nil {
+ return "[]"
+ } else {
+ return string(b)
+ }
+}
+
+func CompliancesFromJson(data io.Reader) Compliances {
+ decoder := json.NewDecoder(data)
+ var o Compliances
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/compliance_post.go b/vendor/github.com/mattermost/platform/model/compliance_post.go
new file mode 100644
index 00000000..ce26a366
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/compliance_post.go
@@ -0,0 +1,104 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "time"
+)
+
+type CompliancePost struct {
+
+ // From Team
+ TeamName string
+ TeamDisplayName string
+
+ // From Channel
+ ChannelName string
+ ChannelDisplayName string
+
+ // From User
+ UserUsername string
+ UserEmail string
+ UserNickname string
+
+ // From Post
+ PostId string
+ PostCreateAt int64
+ PostUpdateAt int64
+ PostDeleteAt int64
+ PostRootId string
+ PostParentId string
+ PostOriginalId string
+ PostMessage string
+ PostType string
+ PostProps string
+ PostHashtags string
+ PostFilenames string
+}
+
+func CompliancePostHeader() []string {
+ return []string{
+ "TeamName",
+ "TeamDisplayName",
+
+ "ChannelName",
+ "ChannelDisplayName",
+
+ "UserUsername",
+ "UserEmail",
+ "UserNickname",
+
+ "PostId",
+ "PostCreateAt",
+ "PostUpdateAt",
+ "PostDeleteAt",
+ "PostRootId",
+ "PostParentId",
+ "PostOriginalId",
+ "PostMessage",
+ "PostType",
+ "PostProps",
+ "PostHashtags",
+ "PostFilenames",
+ }
+}
+
+func (me *CompliancePost) Row() []string {
+
+ postDeleteAt := ""
+ if me.PostDeleteAt > 0 {
+ postDeleteAt = time.Unix(0, me.PostDeleteAt*int64(1000*1000)).Format(time.RFC3339)
+ }
+
+ postUpdateAt := ""
+ if me.PostUpdateAt != me.PostCreateAt {
+ postUpdateAt = time.Unix(0, me.PostUpdateAt*int64(1000*1000)).Format(time.RFC3339)
+ }
+
+ return []string{
+ me.TeamName,
+ me.TeamDisplayName,
+
+ me.ChannelName,
+ me.ChannelDisplayName,
+
+ me.UserUsername,
+ me.UserEmail,
+ me.UserNickname,
+
+ me.PostId,
+ time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339),
+ postUpdateAt,
+ postDeleteAt,
+
+ me.PostRootId,
+ me.PostParentId,
+ me.PostOriginalId,
+ me.PostMessage,
+ me.PostType,
+ me.PostProps,
+ me.PostHashtags,
+ me.PostFilenames,
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/config.go b/vendor/github.com/mattermost/platform/model/config.go
new file mode 100644
index 00000000..a8974359
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/config.go
@@ -0,0 +1,554 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ CONN_SECURITY_NONE = ""
+ CONN_SECURITY_TLS = "TLS"
+ CONN_SECURITY_STARTTLS = "STARTTLS"
+
+ IMAGE_DRIVER_LOCAL = "local"
+ IMAGE_DRIVER_S3 = "amazons3"
+
+ DATABASE_DRIVER_MYSQL = "mysql"
+ DATABASE_DRIVER_POSTGRES = "postgres"
+
+ SERVICE_GITLAB = "gitlab"
+ SERVICE_GOOGLE = "google"
+
+ WEBSERVER_MODE_REGULAR = "regular"
+ WEBSERVER_MODE_GZIP = "gzip"
+ WEBSERVER_MODE_DISABLED = "disabled"
+
+ GENERIC_NOTIFICATION = "generic"
+ FULL_NOTIFICATION = "full"
+)
+
+type ServiceSettings struct {
+ ListenAddress string
+ 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
+ AllowCorsFrom *string
+ SessionLengthWebInDays *int
+ SessionLengthMobileInDays *int
+ SessionLengthSSOInDays *int
+ SessionCacheInMinutes *int
+ WebsocketSecurePort *int
+ WebsocketPort *int
+ WebserverMode *string
+}
+
+type SSOSettings struct {
+ Enable bool
+ Secret string
+ Id string
+ Scope string
+ AuthEndpoint string
+ TokenEndpoint string
+ UserApiEndpoint string
+}
+
+type SqlSettings struct {
+ DriverName string
+ DataSource string
+ DataSourceReplicas []string
+ MaxIdleConns int
+ MaxOpenConns int
+ Trace bool
+ AtRestEncryptKey string
+}
+
+type LogSettings struct {
+ EnableConsole bool
+ ConsoleLevel string
+ EnableFile bool
+ FileLevel string
+ FileFormat string
+ FileLocation string
+}
+
+type FileSettings struct {
+ DriverName string
+ Directory string
+ EnablePublicLink bool
+ PublicLinkSalt string
+ ThumbnailWidth int
+ ThumbnailHeight int
+ PreviewWidth int
+ PreviewHeight int
+ ProfileWidth int
+ ProfileHeight int
+ InitialFont string
+ AmazonS3AccessKeyId string
+ AmazonS3SecretAccessKey string
+ AmazonS3Bucket string
+ AmazonS3Region string
+ AmazonS3Endpoint string
+ AmazonS3BucketEndpoint string
+ AmazonS3LocationConstraint *bool
+ AmazonS3LowercaseBucket *bool
+}
+
+type EmailSettings struct {
+ EnableSignUpWithEmail bool
+ EnableSignInWithEmail *bool
+ EnableSignInWithUsername *bool
+ SendEmailNotifications bool
+ RequireEmailVerification bool
+ FeedbackName string
+ FeedbackEmail string
+ SMTPUsername string
+ SMTPPassword string
+ SMTPServer string
+ SMTPPort string
+ ConnectionSecurity string
+ InviteSalt string
+ PasswordResetSalt string
+ SendPushNotifications *bool
+ PushNotificationServer *string
+ PushNotificationContents *string
+}
+
+type RateLimitSettings struct {
+ EnableRateLimiter bool
+ PerSec int
+ MemoryStoreSize int
+ VaryByRemoteAddr bool
+ VaryByHeader string
+}
+
+type PrivacySettings struct {
+ ShowEmailAddress bool
+ ShowFullName bool
+}
+
+type SupportSettings struct {
+ TermsOfServiceLink *string
+ PrivacyPolicyLink *string
+ AboutLink *string
+ HelpLink *string
+ ReportAProblemLink *string
+ SupportEmail *string
+}
+
+type TeamSettings struct {
+ SiteName string
+ MaxUsersPerTeam int
+ EnableTeamCreation bool
+ EnableUserCreation bool
+ RestrictCreationToDomains string
+ RestrictTeamNames *bool
+ EnableTeamListing *bool
+}
+
+type LdapSettings struct {
+ // Basic
+ Enable *bool
+ LdapServer *string
+ LdapPort *int
+ ConnectionSecurity *string
+ BaseDN *string
+ BindUsername *string
+ BindPassword *string
+
+ // Filtering
+ UserFilter *string
+
+ // User Mapping
+ FirstNameAttribute *string
+ LastNameAttribute *string
+ EmailAttribute *string
+ UsernameAttribute *string
+ IdAttribute *string
+
+ // Advanced
+ SkipCertificateVerification *bool
+ QueryTimeout *int
+}
+
+type ComplianceSettings struct {
+ Enable *bool
+ Directory *string
+ EnableDaily *bool
+}
+
+type Config struct {
+ ServiceSettings ServiceSettings
+ TeamSettings TeamSettings
+ SqlSettings SqlSettings
+ LogSettings LogSettings
+ FileSettings FileSettings
+ EmailSettings EmailSettings
+ RateLimitSettings RateLimitSettings
+ PrivacySettings PrivacySettings
+ SupportSettings SupportSettings
+ GitLabSettings SSOSettings
+ GoogleSettings SSOSettings
+ LdapSettings LdapSettings
+ ComplianceSettings ComplianceSettings
+}
+
+func (o *Config) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func (o *Config) GetSSOService(service string) *SSOSettings {
+ switch service {
+ case SERVICE_GITLAB:
+ return &o.GitLabSettings
+ case SERVICE_GOOGLE:
+ return &o.GoogleSettings
+ }
+
+ return nil
+}
+
+func ConfigFromJson(data io.Reader) *Config {
+ decoder := json.NewDecoder(data)
+ var o Config
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *Config) SetDefaults() {
+
+ if len(o.SqlSettings.AtRestEncryptKey) == 0 {
+ o.SqlSettings.AtRestEncryptKey = NewRandomString(32)
+ }
+
+ if len(o.FileSettings.PublicLinkSalt) == 0 {
+ o.FileSettings.PublicLinkSalt = NewRandomString(32)
+ }
+
+ if o.FileSettings.AmazonS3LocationConstraint == nil {
+ o.FileSettings.AmazonS3LocationConstraint = new(bool)
+ *o.FileSettings.AmazonS3LocationConstraint = false
+ }
+
+ if o.FileSettings.AmazonS3LowercaseBucket == nil {
+ o.FileSettings.AmazonS3LowercaseBucket = new(bool)
+ *o.FileSettings.AmazonS3LowercaseBucket = false
+ }
+
+ if len(o.EmailSettings.InviteSalt) == 0 {
+ o.EmailSettings.InviteSalt = NewRandomString(32)
+ }
+
+ if len(o.EmailSettings.PasswordResetSalt) == 0 {
+ o.EmailSettings.PasswordResetSalt = NewRandomString(32)
+ }
+
+ if o.ServiceSettings.EnableDeveloper == nil {
+ o.ServiceSettings.EnableDeveloper = new(bool)
+ *o.ServiceSettings.EnableDeveloper = false
+ }
+
+ if o.ServiceSettings.EnableSecurityFixAlert == nil {
+ o.ServiceSettings.EnableSecurityFixAlert = new(bool)
+ *o.ServiceSettings.EnableSecurityFixAlert = true
+ }
+
+ if o.ServiceSettings.EnableInsecureOutgoingConnections == nil {
+ o.ServiceSettings.EnableInsecureOutgoingConnections = new(bool)
+ *o.ServiceSettings.EnableInsecureOutgoingConnections = false
+ }
+
+ if o.ServiceSettings.EnableMultifactorAuthentication == nil {
+ o.ServiceSettings.EnableMultifactorAuthentication = new(bool)
+ *o.ServiceSettings.EnableMultifactorAuthentication = false
+ }
+
+ if o.TeamSettings.RestrictTeamNames == nil {
+ o.TeamSettings.RestrictTeamNames = new(bool)
+ *o.TeamSettings.RestrictTeamNames = true
+ }
+
+ if o.TeamSettings.EnableTeamListing == nil {
+ o.TeamSettings.EnableTeamListing = new(bool)
+ *o.TeamSettings.EnableTeamListing = false
+ }
+
+ if o.EmailSettings.EnableSignInWithEmail == nil {
+ o.EmailSettings.EnableSignInWithEmail = new(bool)
+
+ if o.EmailSettings.EnableSignUpWithEmail == true {
+ *o.EmailSettings.EnableSignInWithEmail = true
+ } else {
+ *o.EmailSettings.EnableSignInWithEmail = false
+ }
+ }
+
+ if o.EmailSettings.EnableSignInWithUsername == nil {
+ o.EmailSettings.EnableSignInWithUsername = new(bool)
+ *o.EmailSettings.EnableSignInWithUsername = false
+ }
+
+ if o.EmailSettings.SendPushNotifications == nil {
+ o.EmailSettings.SendPushNotifications = new(bool)
+ *o.EmailSettings.SendPushNotifications = false
+ }
+
+ if o.EmailSettings.PushNotificationServer == nil {
+ o.EmailSettings.PushNotificationServer = new(string)
+ *o.EmailSettings.PushNotificationServer = ""
+ }
+
+ if o.EmailSettings.PushNotificationContents == nil {
+ o.EmailSettings.PushNotificationContents = new(string)
+ *o.EmailSettings.PushNotificationContents = GENERIC_NOTIFICATION
+ }
+
+ if o.SupportSettings.TermsOfServiceLink == nil {
+ o.SupportSettings.TermsOfServiceLink = new(string)
+ *o.SupportSettings.TermsOfServiceLink = "/static/help/terms.html"
+ }
+
+ if o.SupportSettings.PrivacyPolicyLink == nil {
+ o.SupportSettings.PrivacyPolicyLink = new(string)
+ *o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html"
+ }
+
+ if o.SupportSettings.AboutLink == nil {
+ o.SupportSettings.AboutLink = new(string)
+ *o.SupportSettings.AboutLink = "/static/help/about.html"
+ }
+
+ if o.SupportSettings.HelpLink == nil {
+ o.SupportSettings.HelpLink = new(string)
+ *o.SupportSettings.HelpLink = "/static/help/help.html"
+ }
+
+ if o.SupportSettings.ReportAProblemLink == nil {
+ o.SupportSettings.ReportAProblemLink = new(string)
+ *o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html"
+ }
+
+ if o.SupportSettings.SupportEmail == nil {
+ o.SupportSettings.SupportEmail = new(string)
+ *o.SupportSettings.SupportEmail = "feedback@mattermost.com"
+ }
+
+ if o.LdapSettings.LdapPort == nil {
+ o.LdapSettings.LdapPort = new(int)
+ *o.LdapSettings.LdapPort = 389
+ }
+
+ if o.LdapSettings.QueryTimeout == nil {
+ o.LdapSettings.QueryTimeout = new(int)
+ *o.LdapSettings.QueryTimeout = 60
+ }
+
+ if o.LdapSettings.Enable == nil {
+ o.LdapSettings.Enable = new(bool)
+ *o.LdapSettings.Enable = false
+ }
+
+ if o.LdapSettings.UserFilter == nil {
+ o.LdapSettings.UserFilter = new(string)
+ *o.LdapSettings.UserFilter = ""
+ }
+
+ if o.ServiceSettings.SessionLengthWebInDays == nil {
+ o.ServiceSettings.SessionLengthWebInDays = new(int)
+ *o.ServiceSettings.SessionLengthWebInDays = 30
+ }
+
+ if o.ServiceSettings.SessionLengthMobileInDays == nil {
+ o.ServiceSettings.SessionLengthMobileInDays = new(int)
+ *o.ServiceSettings.SessionLengthMobileInDays = 30
+ }
+
+ if o.ServiceSettings.SessionLengthSSOInDays == nil {
+ o.ServiceSettings.SessionLengthSSOInDays = new(int)
+ *o.ServiceSettings.SessionLengthSSOInDays = 30
+ }
+
+ if o.ServiceSettings.SessionCacheInMinutes == nil {
+ o.ServiceSettings.SessionCacheInMinutes = new(int)
+ *o.ServiceSettings.SessionCacheInMinutes = 10
+ }
+
+ if o.ServiceSettings.EnableCommands == nil {
+ o.ServiceSettings.EnableCommands = new(bool)
+ *o.ServiceSettings.EnableCommands = false
+ }
+
+ if o.ServiceSettings.EnableOnlyAdminIntegrations == nil {
+ o.ServiceSettings.EnableOnlyAdminIntegrations = new(bool)
+ *o.ServiceSettings.EnableOnlyAdminIntegrations = true
+ }
+
+ if o.ServiceSettings.WebsocketPort == nil {
+ o.ServiceSettings.WebsocketPort = new(int)
+ *o.ServiceSettings.WebsocketPort = 80
+ }
+
+ if o.ServiceSettings.WebsocketSecurePort == nil {
+ o.ServiceSettings.WebsocketSecurePort = new(int)
+ *o.ServiceSettings.WebsocketSecurePort = 443
+ }
+
+ if o.ServiceSettings.AllowCorsFrom == nil {
+ o.ServiceSettings.AllowCorsFrom = new(string)
+ *o.ServiceSettings.AllowCorsFrom = ""
+ }
+
+ if o.ServiceSettings.WebserverMode == nil {
+ o.ServiceSettings.WebserverMode = new(string)
+ *o.ServiceSettings.WebserverMode = "regular"
+ }
+
+ if o.ComplianceSettings.Enable == nil {
+ o.ComplianceSettings.Enable = new(bool)
+ *o.ComplianceSettings.Enable = false
+ }
+
+ if o.ComplianceSettings.Directory == nil {
+ o.ComplianceSettings.Directory = new(string)
+ *o.ComplianceSettings.Directory = "./data/"
+ }
+
+ if o.ComplianceSettings.EnableDaily == nil {
+ o.ComplianceSettings.EnableDaily = new(bool)
+ *o.ComplianceSettings.EnableDaily = false
+ }
+
+ if o.LdapSettings.ConnectionSecurity == nil {
+ o.LdapSettings.ConnectionSecurity = new(string)
+ *o.LdapSettings.ConnectionSecurity = ""
+ }
+
+ if o.LdapSettings.SkipCertificateVerification == nil {
+ o.LdapSettings.SkipCertificateVerification = new(bool)
+ *o.LdapSettings.SkipCertificateVerification = false
+ }
+}
+
+func (o *Config) IsValid() *AppError {
+
+ if o.ServiceSettings.MaximumLoginAttempts <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "")
+ }
+
+ if len(o.ServiceSettings.ListenAddress) == 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "")
+ }
+
+ if o.TeamSettings.MaxUsersPerTeam <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "")
+ }
+
+ if len(o.SqlSettings.AtRestEncryptKey) < 32 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "")
+ }
+
+ if !(o.SqlSettings.DriverName == DATABASE_DRIVER_MYSQL || o.SqlSettings.DriverName == DATABASE_DRIVER_POSTGRES) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_driver.app_error", nil, "")
+ }
+
+ if o.SqlSettings.MaxIdleConns <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "")
+ }
+
+ if len(o.SqlSettings.DataSource) == 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "")
+ }
+
+ if o.SqlSettings.MaxOpenConns <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "")
+ }
+
+ if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "")
+ }
+
+ if o.FileSettings.PreviewHeight < 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_height.app_error", nil, "")
+ }
+
+ if o.FileSettings.PreviewWidth <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_width.app_error", nil, "")
+ }
+
+ if o.FileSettings.ProfileHeight <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_height.app_error", nil, "")
+ }
+
+ if o.FileSettings.ProfileWidth <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_width.app_error", nil, "")
+ }
+
+ if o.FileSettings.ThumbnailHeight <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_height.app_error", nil, "")
+ }
+
+ if o.FileSettings.ThumbnailWidth <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "")
+ }
+
+ if len(o.FileSettings.PublicLinkSalt) < 32 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "")
+ }
+
+ if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "")
+ }
+
+ if len(o.EmailSettings.InviteSalt) < 32 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "")
+ }
+
+ if len(o.EmailSettings.PasswordResetSalt) < 32 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "")
+ }
+
+ if o.RateLimitSettings.MemoryStoreSize <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "")
+ }
+
+ if o.RateLimitSettings.PerSec <= 0 {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_sec.app_error", nil, "")
+ }
+
+ if !(*o.LdapSettings.ConnectionSecurity == CONN_SECURITY_NONE || *o.LdapSettings.ConnectionSecurity == CONN_SECURITY_TLS || *o.LdapSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) {
+ return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (me *Config) GetSanitizeOptions() map[string]bool {
+ options := map[string]bool{}
+ options["fullname"] = me.PrivacySettings.ShowFullName
+ options["email"] = me.PrivacySettings.ShowEmailAddress
+
+ return options
+}
diff --git a/vendor/github.com/mattermost/platform/model/file.go b/vendor/github.com/mattermost/platform/model/file.go
new file mode 100644
index 00000000..b7806b3b
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/file.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ MAX_FILE_SIZE = 50000000 // 50 MB
+)
+
+var (
+ IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"}
+ IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"}
+)
+
+type FileUploadResponse struct {
+ Filenames []string `json:"filenames"`
+ ClientIds []string `json:"client_ids"`
+}
+
+func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse {
+ decoder := json.NewDecoder(data)
+ var o FileUploadResponse
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *FileUploadResponse) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/file_info.go b/vendor/github.com/mattermost/platform/model/file_info.go
new file mode 100644
index 00000000..f785042b
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/file_info.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "encoding/json"
+ "image/gif"
+ "io"
+ "mime"
+ "path/filepath"
+)
+
+type FileInfo struct {
+ Filename string `json:"filename"`
+ Size int `json:"size"`
+ Extension string `json:"extension"`
+ MimeType string `json:"mime_type"`
+ HasPreviewImage bool `json:"has_preview_image"`
+}
+
+func GetInfoForBytes(filename string, data []byte) (*FileInfo, *AppError) {
+ size := len(data)
+
+ var mimeType string
+ extension := filepath.Ext(filename)
+ isImage := IsFileExtImage(extension)
+ if isImage {
+ mimeType = GetImageMimeType(extension)
+ } else {
+ mimeType = mime.TypeByExtension(extension)
+ }
+
+ if extension != "" && extension[0] == '.' {
+ // the client expects a file extension without the leading period
+ extension = extension[1:]
+ }
+
+ hasPreviewImage := isImage
+ if mimeType == "image/gif" {
+ // just show the gif itself instead of a preview image for animated gifs
+ if gifImage, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
+ return nil, NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "filename="+filename)
+ } else {
+ hasPreviewImage = len(gifImage.Image) == 1
+ }
+ }
+
+ return &FileInfo{
+ Filename: filename,
+ Size: size,
+ Extension: extension,
+ MimeType: mimeType,
+ HasPreviewImage: hasPreviewImage,
+ }, nil
+}
+
+func (info *FileInfo) ToJson() string {
+ b, err := json.Marshal(info)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func FileInfoFromJson(data io.Reader) *FileInfo {
+ decoder := json.NewDecoder(data)
+
+ var info FileInfo
+ if err := decoder.Decode(&info); err != nil {
+ return nil
+ } else {
+ return &info
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go b/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
new file mode 100644
index 00000000..3ca49997
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
@@ -0,0 +1,107 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package oauthgitlab
+
+import (
+ "encoding/json"
+ "github.com/mattermost/platform/einterfaces"
+ "github.com/mattermost/platform/model"
+ "io"
+ "strconv"
+ "strings"
+)
+
+const (
+ USER_AUTH_SERVICE_GITLAB = "gitlab"
+)
+
+type GitLabProvider struct {
+}
+
+type GitLabUser struct {
+ Id int64 `json:"id"`
+ Username string `json:"username"`
+ Login string `json:"login"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+}
+
+func init() {
+ provider := &GitLabProvider{}
+ einterfaces.RegisterOauthProvider(USER_AUTH_SERVICE_GITLAB, provider)
+}
+
+func userFromGitLabUser(glu *GitLabUser) *model.User {
+ user := &model.User{}
+ username := glu.Username
+ if username == "" {
+ username = glu.Login
+ }
+ user.Username = model.CleanUsername(username)
+ splitName := strings.Split(glu.Name, " ")
+ if len(splitName) == 2 {
+ user.FirstName = splitName[0]
+ user.LastName = splitName[1]
+ } else if len(splitName) >= 2 {
+ user.FirstName = splitName[0]
+ user.LastName = strings.Join(splitName[1:], " ")
+ } else {
+ user.FirstName = glu.Name
+ }
+ user.Email = glu.Email
+ user.AuthData = strconv.FormatInt(glu.Id, 10)
+ user.AuthService = USER_AUTH_SERVICE_GITLAB
+
+ return user
+}
+
+func gitLabUserFromJson(data io.Reader) *GitLabUser {
+ decoder := json.NewDecoder(data)
+ var glu GitLabUser
+ err := decoder.Decode(&glu)
+ if err == nil {
+ return &glu
+ } else {
+ return nil
+ }
+}
+
+func (glu *GitLabUser) IsValid() bool {
+ if glu.Id == 0 {
+ return false
+ }
+
+ if len(glu.Email) == 0 {
+ return false
+ }
+
+ return true
+}
+
+func (glu *GitLabUser) getAuthData() string {
+ return strconv.FormatInt(glu.Id, 10)
+}
+
+func (m *GitLabProvider) GetIdentifier() string {
+ return USER_AUTH_SERVICE_GITLAB
+}
+
+func (m *GitLabProvider) GetUserFromJson(data io.Reader) *model.User {
+ glu := gitLabUserFromJson(data)
+ if glu.IsValid() {
+ return userFromGitLabUser(glu)
+ }
+
+ return &model.User{}
+}
+
+func (m *GitLabProvider) GetAuthDataFromJson(data io.Reader) string {
+ glu := gitLabUserFromJson(data)
+
+ if glu.IsValid() {
+ return glu.getAuthData()
+ }
+
+ return ""
+}
diff --git a/vendor/github.com/mattermost/platform/model/incoming_webhook.go b/vendor/github.com/mattermost/platform/model/incoming_webhook.go
new file mode 100644
index 00000000..0763b443
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/incoming_webhook.go
@@ -0,0 +1,137 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ DEFAULT_WEBHOOK_USERNAME = "webhook"
+ DEFAULT_WEBHOOK_ICON = "/static/images/webhook_icon.jpg"
+)
+
+type IncomingWebhook struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ TeamId string `json:"team_id"`
+ DisplayName string `json:"display_name"`
+ Description string `json:"description"`
+}
+
+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"`
+}
+
+func (o *IncomingWebhook) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func IncomingWebhookFromJson(data io.Reader) *IncomingWebhook {
+ decoder := json.NewDecoder(data)
+ var o IncomingWebhook
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func IncomingWebhookListToJson(l []*IncomingWebhook) string {
+ b, err := json.Marshal(l)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook {
+ decoder := json.NewDecoder(data)
+ var o []*IncomingWebhook
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
+
+func (o *IncomingWebhook) IsValid() *AppError {
+
+ if len(o.Id) != 26 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "")
+ }
+
+ if o.CreateAt == 0 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id)
+ }
+
+ if o.UpdateAt == 0 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.UserId) != 26 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "")
+ }
+
+ if len(o.ChannelId) != 26 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "")
+ }
+
+ if len(o.TeamId) != 26 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "")
+ }
+
+ if len(o.DisplayName) > 64 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "")
+ }
+
+ if len(o.Description) > 128 {
+ return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (o *IncomingWebhook) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ o.CreateAt = GetMillis()
+ o.UpdateAt = o.CreateAt
+}
+
+func (o *IncomingWebhook) PreUpdate() {
+ o.UpdateAt = GetMillis()
+}
+
+func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
+ decoder := json.NewDecoder(data)
+ var o IncomingWebhookRequest
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/ldap.go b/vendor/github.com/mattermost/platform/model/ldap.go
new file mode 100644
index 00000000..5fde06a6
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/ldap.go
@@ -0,0 +1,8 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+const (
+ USER_AUTH_SERVICE_LDAP = "ldap"
+)
diff --git a/vendor/github.com/mattermost/platform/model/license.go b/vendor/github.com/mattermost/platform/model/license.go
new file mode 100644
index 00000000..cab22a68
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/license.go
@@ -0,0 +1,123 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type LicenseRecord struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ Bytes string `json:"-"`
+}
+
+type License struct {
+ Id string `json:"id"`
+ IssuedAt int64 `json:"issued_at"`
+ StartsAt int64 `json:"starts_at"`
+ ExpiresAt int64 `json:"expires_at"`
+ Customer *Customer `json:"customer"`
+ Features *Features `json:"features"`
+}
+
+type Customer struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Company string `json:"company"`
+ PhoneNumber string `json:"phone_number"`
+}
+
+type Features struct {
+ Users *int `json:"users"`
+ LDAP *bool `json:"ldap"`
+ MFA *bool `json:"mfa"`
+ GoogleSSO *bool `json:"google_sso"`
+ Compliance *bool `json:"compliance"`
+}
+
+func (f *Features) SetDefaults() {
+ if f.Users == nil {
+ f.Users = new(int)
+ *f.Users = 0
+ }
+
+ if f.LDAP == nil {
+ f.LDAP = new(bool)
+ *f.LDAP = true
+ }
+
+ if f.MFA == nil {
+ f.MFA = new(bool)
+ *f.MFA = true
+ }
+
+ if f.GoogleSSO == nil {
+ f.GoogleSSO = new(bool)
+ *f.GoogleSSO = true
+ }
+
+ if f.Compliance == nil {
+ f.Compliance = new(bool)
+ *f.Compliance = true
+ }
+}
+
+func (l *License) IsExpired() bool {
+ now := GetMillis()
+ if l.ExpiresAt < now {
+ return true
+ }
+ return false
+}
+
+func (l *License) IsStarted() bool {
+ now := GetMillis()
+ if l.StartsAt < now {
+ return true
+ }
+ return false
+}
+
+func (l *License) ToJson() string {
+ b, err := json.Marshal(l)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func LicenseFromJson(data io.Reader) *License {
+ decoder := json.NewDecoder(data)
+ var o License
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (lr *LicenseRecord) IsValid() *AppError {
+ if len(lr.Id) != 26 {
+ return NewLocAppError("LicenseRecord.IsValid", "model.license_record.is_valid.id.app_error", nil, "")
+ }
+
+ if lr.CreateAt == 0 {
+ return NewLocAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "")
+ }
+
+ if len(lr.Bytes) == 0 || len(lr.Bytes) > 10000 {
+ return NewLocAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (lr *LicenseRecord) PreSave() {
+ lr.CreateAt = GetMillis()
+}
diff --git a/vendor/github.com/mattermost/platform/model/message.go b/vendor/github.com/mattermost/platform/model/message.go
new file mode 100644
index 00000000..cce0ec09
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/message.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ ACTION_TYPING = "typing"
+ ACTION_POSTED = "posted"
+ ACTION_POST_EDITED = "post_edited"
+ ACTION_POST_DELETED = "post_deleted"
+ ACTION_CHANNEL_VIEWED = "channel_viewed"
+ ACTION_NEW_USER = "new_user"
+ ACTION_USER_ADDED = "user_added"
+ ACTION_USER_REMOVED = "user_removed"
+ ACTION_PREFERENCE_CHANGED = "preference_changed"
+ ACTION_EPHEMERAL_MESSAGE = "ephemeral_message"
+)
+
+type Message struct {
+ TeamId string `json:"team_id"`
+ ChannelId string `json:"channel_id"`
+ UserId string `json:"user_id"`
+ Action string `json:"action"`
+ Props map[string]string `json:"props"`
+}
+
+func (m *Message) Add(key string, value string) {
+ m.Props[key] = value
+}
+
+func NewMessage(teamId string, channelId string, userId string, action string) *Message {
+ return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)}
+}
+
+func (o *Message) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func MessageFromJson(data io.Reader) *Message {
+ decoder := json.NewDecoder(data)
+ var o Message
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/oauth.go b/vendor/github.com/mattermost/platform/model/oauth.go
new file mode 100644
index 00000000..c54df107
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/oauth.go
@@ -0,0 +1,159 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "unicode/utf8"
+)
+
+const (
+ OAUTH_ACTION_SIGNUP = "signup"
+ OAUTH_ACTION_LOGIN = "login"
+ OAUTH_ACTION_EMAIL_TO_SSO = "email_to_sso"
+ OAUTH_ACTION_SSO_TO_EMAIL = "sso_to_email"
+)
+
+type OAuthApp struct {
+ Id string `json:"id"`
+ CreatorId string `json:"creator_id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ ClientSecret string `json:"client_secret"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ CallbackUrls StringArray `json:"callback_urls"`
+ Homepage string `json:"homepage"`
+}
+
+// IsValid validates the app and returns an error if it isn't configured
+// correctly.
+func (a *OAuthApp) IsValid() *AppError {
+
+ if len(a.Id) != 26 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "")
+ }
+
+ if a.CreateAt == 0 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id)
+ }
+
+ if a.UpdateAt == 0 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id)
+ }
+
+ if len(a.CreatorId) != 26 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id)
+ }
+
+ if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id)
+ }
+
+ if len(a.Name) == 0 || len(a.Name) > 64 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id)
+ }
+
+ if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id)
+ }
+
+ if len(a.Homepage) == 0 || len(a.Homepage) > 256 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id)
+ }
+
+ if utf8.RuneCountInString(a.Description) > 512 {
+ return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id)
+ }
+
+ return nil
+}
+
+// PreSave will set the Id and ClientSecret if missing. It will also fill
+// in the CreateAt, UpdateAt times. It should be run before saving the app to the db.
+func (a *OAuthApp) PreSave() {
+ if a.Id == "" {
+ a.Id = NewId()
+ }
+
+ if a.ClientSecret == "" {
+ a.ClientSecret = NewId()
+ }
+
+ a.CreateAt = GetMillis()
+ a.UpdateAt = a.CreateAt
+
+ if len(a.ClientSecret) > 0 {
+ a.ClientSecret = HashPassword(a.ClientSecret)
+ }
+}
+
+// PreUpdate should be run before updating the app in the db.
+func (a *OAuthApp) PreUpdate() {
+ a.UpdateAt = GetMillis()
+}
+
+// ToJson convert a User to a json string
+func (a *OAuthApp) ToJson() string {
+ b, err := json.Marshal(a)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+// Generate a valid strong etag so the browser can cache the results
+func (a *OAuthApp) Etag() string {
+ return Etag(a.Id, a.UpdateAt)
+}
+
+// Remove any private data from the app object
+func (a *OAuthApp) Sanitize() {
+ a.ClientSecret = ""
+}
+
+func (a *OAuthApp) IsValidRedirectURL(url string) bool {
+ for _, u := range a.CallbackUrls {
+ if u == url {
+ return true
+ }
+ }
+
+ return false
+}
+
+// OAuthAppFromJson will decode the input and return a User
+func OAuthAppFromJson(data io.Reader) *OAuthApp {
+ decoder := json.NewDecoder(data)
+ var app OAuthApp
+ err := decoder.Decode(&app)
+ if err == nil {
+ return &app
+ } else {
+ return nil
+ }
+}
+
+func OAuthAppMapToJson(a map[string]*OAuthApp) string {
+ b, err := json.Marshal(a)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp {
+ decoder := json.NewDecoder(data)
+ var apps map[string]*OAuthApp
+ err := decoder.Decode(&apps)
+ if err == nil {
+ return apps
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/outgoing_webhook.go b/vendor/github.com/mattermost/platform/model/outgoing_webhook.go
new file mode 100644
index 00000000..e13de908
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/outgoing_webhook.go
@@ -0,0 +1,151 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+)
+
+type OutgoingWebhook struct {
+ Id string `json:"id"`
+ Token string `json:"token"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ CreatorId string `json:"creator_id"`
+ ChannelId string `json:"channel_id"`
+ TeamId string `json:"team_id"`
+ TriggerWords StringArray `json:"trigger_words"`
+ CallbackURLs StringArray `json:"callback_urls"`
+ DisplayName string `json:"display_name"`
+ Description string `json:"description"`
+}
+
+func (o *OutgoingWebhook) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func OutgoingWebhookFromJson(data io.Reader) *OutgoingWebhook {
+ decoder := json.NewDecoder(data)
+ var o OutgoingWebhook
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func OutgoingWebhookListToJson(l []*OutgoingWebhook) string {
+ b, err := json.Marshal(l)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook {
+ decoder := json.NewDecoder(data)
+ var o []*OutgoingWebhook
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
+
+func (o *OutgoingWebhook) IsValid() *AppError {
+
+ if len(o.Id) != 26 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "")
+ }
+
+ if len(o.Token) != 26 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "")
+ }
+
+ if o.CreateAt == 0 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id)
+ }
+
+ if o.UpdateAt == 0 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.CreatorId) != 26 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "")
+ }
+
+ if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "")
+ }
+
+ if len(o.TeamId) != 26 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "")
+ }
+
+ if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "")
+ }
+
+ if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "")
+ }
+
+ for _, callback := range o.CallbackURLs {
+ if !IsValidHttpUrl(callback) {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "")
+ }
+ }
+
+ if len(o.DisplayName) > 64 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "")
+ }
+
+ if len(o.Description) > 128 {
+ return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func (o *OutgoingWebhook) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ if o.Token == "" {
+ o.Token = NewId()
+ }
+
+ o.CreateAt = GetMillis()
+ o.UpdateAt = o.CreateAt
+}
+
+func (o *OutgoingWebhook) PreUpdate() {
+ o.UpdateAt = GetMillis()
+}
+
+func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
+ if len(o.TriggerWords) == 0 || len(word) == 0 {
+ return false
+ }
+
+ for _, trigger := range o.TriggerWords {
+ if trigger == word {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/mattermost/platform/model/post.go b/vendor/github.com/mattermost/platform/model/post.go
new file mode 100644
index 00000000..8a451831
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/post.go
@@ -0,0 +1,169 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "unicode/utf8"
+)
+
+const (
+ POST_SYSTEM_MESSAGE_PREFIX = "system_"
+ POST_DEFAULT = ""
+ POST_SLACK_ATTACHMENT = "slack_attachment"
+ POST_SYSTEM_GENERIC = "system_generic"
+ POST_JOIN_LEAVE = "system_join_leave"
+ POST_HEADER_CHANGE = "system_header_change"
+ POST_EPHEMERAL = "system_ephemeral"
+)
+
+type Post struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ RootId string `json:"root_id"`
+ ParentId string `json:"parent_id"`
+ OriginalId string `json:"original_id"`
+ Message string `json:"message"`
+ Type string `json:"type"`
+ Props StringInterface `json:"props"`
+ Hashtags string `json:"hashtags"`
+ Filenames StringArray `json:"filenames"`
+ PendingPostId string `json:"pending_post_id" db:"-"`
+}
+
+func (o *Post) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func PostFromJson(data io.Reader) *Post {
+ decoder := json.NewDecoder(data)
+ var o Post
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *Post) Etag() string {
+ return Etag(o.Id, o.UpdateAt)
+}
+
+func (o *Post) IsValid() *AppError {
+
+ if len(o.Id) != 26 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.id.app_error", nil, "")
+ }
+
+ if o.CreateAt == 0 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.create_at.app_error", nil, "id="+o.Id)
+ }
+
+ if o.UpdateAt == 0 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.update_at.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.UserId) != 26 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.user_id.app_error", nil, "")
+ }
+
+ if len(o.ChannelId) != 26 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "")
+ }
+
+ if !(len(o.RootId) == 26 || len(o.RootId) == 0) {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "")
+ }
+
+ if !(len(o.ParentId) == 26 || len(o.ParentId) == 0) {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.parent_id.app_error", nil, "")
+ }
+
+ if len(o.ParentId) == 26 && len(o.RootId) == 0 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.root_parent.app_error", nil, "")
+ }
+
+ if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "")
+ }
+
+ if utf8.RuneCountInString(o.Message) > 4000 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id)
+ }
+
+ if utf8.RuneCountInString(o.Hashtags) > 1000 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id)
+ }
+
+ // should be removed once more message types are supported
+ if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type)
+ }
+
+ if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > 4000 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id)
+ }
+
+ if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > 8000 {
+ return NewLocAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id)
+ }
+
+ return nil
+}
+
+func (o *Post) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ o.OriginalId = ""
+
+ if o.CreateAt == 0 {
+ o.CreateAt = GetMillis()
+ }
+
+ o.UpdateAt = o.CreateAt
+
+ if o.Props == nil {
+ o.Props = make(map[string]interface{})
+ }
+
+ if o.Filenames == nil {
+ o.Filenames = []string{}
+ }
+}
+
+func (o *Post) MakeNonNil() {
+ if o.Props == nil {
+ o.Props = make(map[string]interface{})
+ }
+ if o.Filenames == nil {
+ o.Filenames = []string{}
+ }
+}
+
+func (o *Post) AddProp(key string, value interface{}) {
+
+ o.MakeNonNil()
+
+ o.Props[key] = value
+}
+
+func (o *Post) PreExport() {
+}
+
+func (o *Post) IsSystemMessage() bool {
+ return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX
+}
diff --git a/vendor/github.com/mattermost/platform/model/post_list.go b/vendor/github.com/mattermost/platform/model/post_list.go
new file mode 100644
index 00000000..4c0f5408
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/post_list.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type PostList struct {
+ Order []string `json:"order"`
+ Posts map[string]*Post `json:"posts"`
+}
+
+func (o *PostList) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func (o *PostList) MakeNonNil() {
+ if o.Order == nil {
+ o.Order = make([]string, 0)
+ }
+
+ if o.Posts == nil {
+ o.Posts = make(map[string]*Post)
+ }
+
+ for _, v := range o.Posts {
+ v.MakeNonNil()
+ }
+}
+
+func (o *PostList) AddOrder(id string) {
+
+ if o.Order == nil {
+ o.Order = make([]string, 0, 128)
+ }
+
+ o.Order = append(o.Order, id)
+}
+
+func (o *PostList) AddPost(post *Post) {
+
+ if o.Posts == nil {
+ o.Posts = make(map[string]*Post)
+ }
+
+ o.Posts[post.Id] = post
+}
+
+func (o *PostList) Extend(other *PostList) {
+ for _, postId := range other.Order {
+ if _, ok := o.Posts[postId]; !ok {
+ o.AddPost(other.Posts[postId])
+ o.AddOrder(postId)
+ }
+ }
+}
+
+func (o *PostList) Etag() string {
+
+ id := "0"
+ var t int64 = 0
+
+ for _, v := range o.Posts {
+ if v.UpdateAt > t {
+ t = v.UpdateAt
+ id = v.Id
+ }
+ }
+
+ return Etag(id, t)
+}
+
+func (o *PostList) IsChannelId(channelId string) bool {
+ for _, v := range o.Posts {
+ if v.ChannelId != channelId {
+ return false
+ }
+ }
+
+ return true
+}
+
+func PostListFromJson(data io.Reader) *PostList {
+ decoder := json.NewDecoder(data)
+ var o PostList
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/preference.go b/vendor/github.com/mattermost/platform/model/preference.go
new file mode 100644
index 00000000..b2ec9310
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/preference.go
@@ -0,0 +1,66 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+ "unicode/utf8"
+)
+
+const (
+ PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show"
+ PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
+ PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
+
+ PREFERENCE_CATEGORY_LAST = "last"
+ PREFERENCE_NAME_LAST_CHANNEL = "channel"
+)
+
+type Preference struct {
+ UserId string `json:"user_id"`
+ Category string `json:"category"`
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
+
+func (o *Preference) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func PreferenceFromJson(data io.Reader) *Preference {
+ decoder := json.NewDecoder(data)
+ var o Preference
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *Preference) IsValid() *AppError {
+ if len(o.UserId) != 26 {
+ return NewLocAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId)
+ }
+
+ if len(o.Category) == 0 || len(o.Category) > 32 {
+ return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category)
+ }
+
+ if len(o.Name) == 0 || len(o.Name) > 32 {
+ return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name)
+ }
+
+ if utf8.RuneCountInString(o.Value) > 128 {
+ return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mattermost/platform/model/preferences.go b/vendor/github.com/mattermost/platform/model/preferences.go
new file mode 100644
index 00000000..f11b5fd8
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/preferences.go
@@ -0,0 +1,31 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type Preferences []Preference
+
+func (o *Preferences) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func PreferencesFromJson(data io.Reader) (Preferences, error) {
+ decoder := json.NewDecoder(data)
+ var o Preferences
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o, nil
+ } else {
+ return nil, err
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/push_notification.go b/vendor/github.com/mattermost/platform/model/push_notification.go
new file mode 100644
index 00000000..9196a44d
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/push_notification.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ PUSH_NOTIFY_APPLE = "apple"
+ PUSH_NOTIFY_ANDROID = "android"
+
+ CATEGORY_DM = "DIRECT_MESSAGE"
+)
+
+type PushNotification struct {
+ Platform string `json:"platform"`
+ ServerId string `json:"server_id"`
+ DeviceId string `json:"device_id"`
+ Category string `json:"category"`
+ Sound string `json:"sound"`
+ Message string `json:"message"`
+ Badge int `json:"badge"`
+ ContentAvailable int `json:"cont_ava"`
+ ChannelId string `json:"channel_id"`
+ ChannelName string `json:"channel_name"`
+}
+
+func (me *PushNotification) ToJson() string {
+ b, err := json.Marshal(me)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func PushNotificationFromJson(data io.Reader) *PushNotification {
+ decoder := json.NewDecoder(data)
+ var me PushNotification
+ err := decoder.Decode(&me)
+ if err == nil {
+ return &me
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/search_params.go b/vendor/github.com/mattermost/platform/model/search_params.go
new file mode 100644
index 00000000..d3178269
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/search_params.go
@@ -0,0 +1,170 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "strings"
+)
+
+type SearchParams struct {
+ Terms string
+ IsHashtag bool
+ InChannels []string
+ FromUsers []string
+}
+
+var searchFlags = [...]string{"from", "channel", "in"}
+
+func splitWordsNoQuotes(text string) []string {
+ words := []string{}
+
+ for _, word := range strings.Fields(text) {
+ words = append(words, word)
+ }
+
+ return words
+}
+
+func splitWords(text string) []string {
+ words := []string{}
+
+ foundQuote := false
+ location := 0
+ for i, char := range text {
+ if char == '"' {
+ if foundQuote {
+ // Grab the quoted section
+ word := text[location : i+1]
+ words = append(words, word)
+ foundQuote = false
+ location = i + 1
+ } else {
+ words = append(words, splitWordsNoQuotes(text[location:i])...)
+ foundQuote = true
+ location = i
+ }
+ }
+ }
+
+ words = append(words, splitWordsNoQuotes(text[location:])...)
+
+ return words
+}
+
+func parseSearchFlags(input []string) ([]string, [][2]string) {
+ words := []string{}
+ flags := [][2]string{}
+
+ skipNextWord := false
+ for i, word := range input {
+ if skipNextWord {
+ skipNextWord = false
+ continue
+ }
+
+ isFlag := false
+
+ if colon := strings.Index(word, ":"); colon != -1 {
+ flag := word[:colon]
+ value := word[colon+1:]
+
+ for _, searchFlag := range searchFlags {
+ // check for case insensitive equality
+ if strings.EqualFold(flag, searchFlag) {
+ if value != "" {
+ flags = append(flags, [2]string{searchFlag, value})
+ isFlag = true
+ } else if i < len(input)-1 {
+ flags = append(flags, [2]string{searchFlag, input[i+1]})
+ skipNextWord = true
+ isFlag = true
+ }
+
+ if isFlag {
+ break
+ }
+ }
+ }
+ }
+
+ if !isFlag {
+ // trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards)
+ word = puncStart.ReplaceAllString(word, "")
+ word = puncEndWildcard.ReplaceAllString(word, "")
+
+ // and remove extra pound #s
+ word = hashtagStart.ReplaceAllString(word, "#")
+
+ if len(word) != 0 {
+ words = append(words, word)
+ }
+ }
+ }
+
+ return words, flags
+}
+
+func ParseSearchParams(text string) []*SearchParams {
+ words, flags := parseSearchFlags(splitWords(text))
+
+ hashtagTermList := []string{}
+ plainTermList := []string{}
+
+ for _, word := range words {
+ if validHashtag.MatchString(word) {
+ hashtagTermList = append(hashtagTermList, word)
+ } else {
+ plainTermList = append(plainTermList, word)
+ }
+ }
+
+ hashtagTerms := strings.Join(hashtagTermList, " ")
+ plainTerms := strings.Join(plainTermList, " ")
+
+ inChannels := []string{}
+ fromUsers := []string{}
+
+ for _, flagPair := range flags {
+ flag := flagPair[0]
+ value := flagPair[1]
+
+ if flag == "in" || flag == "channel" {
+ inChannels = append(inChannels, value)
+ } else if flag == "from" {
+ fromUsers = append(fromUsers, value)
+ }
+ }
+
+ paramsList := []*SearchParams{}
+
+ if len(plainTerms) > 0 {
+ paramsList = append(paramsList, &SearchParams{
+ Terms: plainTerms,
+ IsHashtag: false,
+ InChannels: inChannels,
+ FromUsers: fromUsers,
+ })
+ }
+
+ if len(hashtagTerms) > 0 {
+ paramsList = append(paramsList, &SearchParams{
+ Terms: hashtagTerms,
+ IsHashtag: true,
+ InChannels: inChannels,
+ FromUsers: fromUsers,
+ })
+ }
+
+ // special case for when no terms are specified but we still have a filter
+ if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0) {
+ paramsList = append(paramsList, &SearchParams{
+ Terms: "",
+ IsHashtag: true,
+ InChannels: inChannels,
+ FromUsers: fromUsers,
+ })
+ }
+
+ return paramsList
+}
diff --git a/vendor/github.com/mattermost/platform/model/security_bulletin.go b/vendor/github.com/mattermost/platform/model/security_bulletin.go
new file mode 100644
index 00000000..8d9be6d3
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/security_bulletin.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type SecurityBulletin struct {
+ Id string `json:"id"`
+ AppliesToVersion string `json:"applies_to_version"`
+}
+
+type SecurityBulletins []SecurityBulletin
+
+func (me *SecurityBulletin) ToJson() string {
+ b, err := json.Marshal(me)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func SecurityBulletinFromJson(data io.Reader) *SecurityBulletin {
+ decoder := json.NewDecoder(data)
+ var o SecurityBulletin
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (me SecurityBulletins) ToJson() string {
+ if b, err := json.Marshal(me); err != nil {
+ return "[]"
+ } else {
+ return string(b)
+ }
+}
+
+func SecurityBulletinsFromJson(data io.Reader) SecurityBulletins {
+ decoder := json.NewDecoder(data)
+ var o SecurityBulletins
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/session.go b/vendor/github.com/mattermost/platform/model/session.go
new file mode 100644
index 00000000..bf0d9531
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/session.go
@@ -0,0 +1,115 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
+ SESSION_CACHE_SIZE = 10000
+ SESSION_PROP_PLATFORM = "platform"
+ SESSION_PROP_OS = "os"
+ SESSION_PROP_BROWSER = "browser"
+)
+
+type Session struct {
+ Id string `json:"id"`
+ Token string `json:"token"`
+ CreateAt int64 `json:"create_at"`
+ ExpiresAt int64 `json:"expires_at"`
+ LastActivityAt int64 `json:"last_activity_at"`
+ UserId string `json:"user_id"`
+ TeamId string `json:"team_id"`
+ DeviceId string `json:"device_id"`
+ Roles string `json:"roles"`
+ IsOAuth bool `json:"is_oauth"`
+ Props StringMap `json:"props"`
+}
+
+func (me *Session) ToJson() string {
+ b, err := json.Marshal(me)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func SessionFromJson(data io.Reader) *Session {
+ decoder := json.NewDecoder(data)
+ var me Session
+ err := decoder.Decode(&me)
+ if err == nil {
+ return &me
+ } else {
+ return nil
+ }
+}
+
+func (me *Session) PreSave() {
+ if me.Id == "" {
+ me.Id = NewId()
+ }
+
+ me.Token = NewId()
+
+ me.CreateAt = GetMillis()
+ me.LastActivityAt = me.CreateAt
+
+ if me.Props == nil {
+ me.Props = make(map[string]string)
+ }
+}
+
+func (me *Session) Sanitize() {
+ me.Token = ""
+}
+
+func (me *Session) IsExpired() bool {
+
+ if me.ExpiresAt <= 0 {
+ return false
+ }
+
+ if GetMillis() > me.ExpiresAt {
+ return true
+ }
+
+ return false
+}
+
+func (me *Session) SetExpireInDays(days int) {
+ me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
+}
+
+func (me *Session) AddProp(key string, value string) {
+
+ if me.Props == nil {
+ me.Props = make(map[string]string)
+ }
+
+ me.Props[key] = value
+}
+
+func SessionsToJson(o []*Session) string {
+ if b, err := json.Marshal(o); err != nil {
+ return "[]"
+ } else {
+ return string(b)
+ }
+}
+
+func SessionsFromJson(data io.Reader) []*Session {
+ decoder := json.NewDecoder(data)
+ var o []*Session
+ err := decoder.Decode(&o)
+ if err == nil {
+ return o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/suggest_command.go b/vendor/github.com/mattermost/platform/model/suggest_command.go
new file mode 100644
index 00000000..7bc35369
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/suggest_command.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type SuggestCommand struct {
+ Suggestion string `json:"suggestion"`
+ Description string `json:"description"`
+}
+
+func (o *SuggestCommand) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func SuggestCommandFromJson(data io.Reader) *SuggestCommand {
+ decoder := json.NewDecoder(data)
+ var o SuggestCommand
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/system.go b/vendor/github.com/mattermost/platform/model/system.go
new file mode 100644
index 00000000..68d542c1
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/system.go
@@ -0,0 +1,42 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+const (
+ SYSTEM_DIAGNOSTIC_ID = "DiagnosticId"
+ SYSTEM_RAN_UNIT_TESTS = "RanUnitTests"
+ SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime"
+ SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId"
+ SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime"
+)
+
+type System struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
+
+func (o *System) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func SystemFromJson(data io.Reader) *System {
+ decoder := json.NewDecoder(data)
+ var o System
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/team.go b/vendor/github.com/mattermost/platform/model/team.go
new file mode 100644
index 00000000..d95dea11
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/team.go
@@ -0,0 +1,243 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+ "unicode/utf8"
+)
+
+const (
+ TEAM_OPEN = "O"
+ TEAM_INVITE = "I"
+)
+
+type Team struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ DisplayName string `json:"display_name"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Type string `json:"type"`
+ CompanyName string `json:"company_name"`
+ AllowedDomains string `json:"allowed_domains"`
+ InviteId string `json:"invite_id"`
+ AllowOpenInvite bool `json:"allow_open_invite"`
+ AllowTeamListing bool `json:"allow_team_listing"`
+}
+
+type Invites struct {
+ Invites []map[string]string `json:"invites"`
+}
+
+func InvitesFromJson(data io.Reader) *Invites {
+ decoder := json.NewDecoder(data)
+ var o Invites
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *Invites) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func (o *Team) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func TeamFromJson(data io.Reader) *Team {
+ decoder := json.NewDecoder(data)
+ var o Team
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func TeamMapToJson(u map[string]*Team) string {
+ b, err := json.Marshal(u)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func TeamMapFromJson(data io.Reader) map[string]*Team {
+ decoder := json.NewDecoder(data)
+ var teams map[string]*Team
+ err := decoder.Decode(&teams)
+ if err == nil {
+ return teams
+ } else {
+ return nil
+ }
+}
+
+func (o *Team) Etag() string {
+ return Etag(o.Id, o.UpdateAt)
+}
+
+func (o *Team) IsValid(restrictTeamNames bool) *AppError {
+
+ if len(o.Id) != 26 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "")
+ }
+
+ if o.CreateAt == 0 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id)
+ }
+
+ if o.UpdateAt == 0 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.Email) > 128 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.Email) > 0 && !IsValidEmail(o.Email) {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id)
+ }
+
+ 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 len(o.Name) > 64 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id)
+ }
+
+ if restrictTeamNames && IsReservedTeamName(o.Name) {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id)
+ }
+
+ if !IsValidTeamName(o.Name) {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id)
+ }
+
+ if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.CompanyName) > 64 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id)
+ }
+
+ if len(o.AllowedDomains) > 500 {
+ return NewLocAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id)
+ }
+
+ return nil
+}
+
+func (o *Team) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ o.CreateAt = GetMillis()
+ o.UpdateAt = o.CreateAt
+
+ if len(o.InviteId) == 0 {
+ o.InviteId = NewId()
+ }
+}
+
+func (o *Team) PreUpdate() {
+ o.UpdateAt = GetMillis()
+}
+
+func IsReservedTeamName(s string) bool {
+ s = strings.ToLower(s)
+
+ for _, value := range reservedName {
+ if strings.Index(s, value) == 0 {
+ return true
+ }
+ }
+
+ return false
+}
+
+func IsValidTeamName(s string) bool {
+
+ if !IsValidAlphaNum(s, false) {
+ return false
+ }
+
+ if len(s) <= 3 {
+ return false
+ }
+
+ return true
+}
+
+var validTeamNameCharacter = regexp.MustCompile(`^[a-z0-9-]$`)
+
+func CleanTeamName(s string) string {
+ s = strings.ToLower(strings.Replace(s, " ", "-", -1))
+
+ for _, value := range reservedName {
+ if strings.Index(s, value) == 0 {
+ s = strings.Replace(s, value, "", -1)
+ }
+ }
+
+ s = strings.TrimSpace(s)
+
+ for _, c := range s {
+ char := fmt.Sprintf("%c", c)
+ if !validTeamNameCharacter.MatchString(char) {
+ s = strings.Replace(s, char, "", -1)
+ }
+ }
+
+ s = strings.Trim(s, "-")
+
+ if !IsValidTeamName(s) {
+ s = NewId()
+ }
+
+ return s
+}
+
+func (o *Team) PreExport() {
+}
+
+func (o *Team) Sanitize() {
+ o.Email = ""
+ o.AllowedDomains = ""
+}
+
+func (o *Team) SanitizeForNotLoggedIn() {
+ o.Email = ""
+ o.AllowedDomains = ""
+ o.CompanyName = ""
+ if !o.AllowOpenInvite {
+ o.InviteId = ""
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/team_signup.go b/vendor/github.com/mattermost/platform/model/team_signup.go
new file mode 100644
index 00000000..e3642044
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/team_signup.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+)
+
+type TeamSignup struct {
+ Team Team `json:"team"`
+ User User `json:"user"`
+ Invites []string `json:"invites"`
+ Data string `json:"data"`
+ Hash string `json:"hash"`
+}
+
+func TeamSignupFromJson(data io.Reader) *TeamSignup {
+ decoder := json.NewDecoder(data)
+ var o TeamSignup
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ fmt.Println(err)
+
+ return nil
+ }
+}
+
+func (o *TeamSignup) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
diff --git a/vendor/github.com/mattermost/platform/model/user.go b/vendor/github.com/mattermost/platform/model/user.go
new file mode 100644
index 00000000..173fe2b4
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/user.go
@@ -0,0 +1,481 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+ "unicode/utf8"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+const (
+ ROLE_TEAM_ADMIN = "admin"
+ ROLE_SYSTEM_ADMIN = "system_admin"
+ USER_AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutes
+ USER_OFFLINE_TIMEOUT = 1 * 60 * 1000 // 1 minute
+ USER_OFFLINE = "offline"
+ USER_AWAY = "away"
+ USER_ONLINE = "online"
+ USER_NOTIFY_ALL = "all"
+ USER_NOTIFY_MENTION = "mention"
+ USER_NOTIFY_NONE = "none"
+ DEFAULT_LOCALE = "en"
+ USER_AUTH_SERVICE_EMAIL = "email"
+ USER_AUTH_SERVICE_USERNAME = "username"
+)
+
+type User struct {
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at,omitempty"`
+ UpdateAt int64 `json:"update_at,omitempty"`
+ DeleteAt int64 `json:"delete_at"`
+ TeamId string `json:"team_id"`
+ Username string `json:"username"`
+ Password string `json:"password,omitempty"`
+ AuthData string `json:"auth_data,omitempty"`
+ AuthService string `json:"auth_service"`
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified,omitempty"`
+ Nickname string `json:"nickname"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ Roles string `json:"roles"`
+ LastActivityAt int64 `json:"last_activity_at,omitempty"`
+ LastPingAt int64 `json:"last_ping_at,omitempty"`
+ AllowMarketing bool `json:"allow_marketing,omitempty"`
+ Props StringMap `json:"props,omitempty"`
+ NotifyProps StringMap `json:"notify_props,omitempty"`
+ ThemeProps StringMap `json:"theme_props,omitempty"`
+ LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
+ LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
+ FailedAttempts int `json:"failed_attempts,omitempty"`
+ Locale string `json:"locale"`
+ MfaActive bool `json:"mfa_active,omitempty"`
+ MfaSecret string `json:"mfa_secret,omitempty"`
+}
+
+// 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, "")
+ }
+
+ if u.CreateAt == 0 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.create_at.app_error", nil, "user_id="+u.Id)
+ }
+
+ if u.UpdateAt == 0 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.update_at.app_error", nil, "user_id="+u.Id)
+ }
+
+ if len(u.TeamId) != 26 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.team_id.app_error", nil, "")
+ }
+
+ if !IsValidUsername(u.Username) {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.username.app_error", nil, "user_id="+u.Id)
+ }
+
+ 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 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.FirstName) > 64 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id)
+ }
+
+ if utf8.RuneCountInString(u.LastName) > 64 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id)
+ }
+
+ if len(u.Password) > 128 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.pwd.app_error", nil, "user_id="+u.Id)
+ }
+
+ if len(u.AuthData) > 128 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id)
+ }
+
+ if 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)
+ }
+
+ if len(u.Password) > 0 && len(u.AuthData) > 0 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
+ }
+
+ if len(u.ThemeProps) > 2000 {
+ return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id)
+ }
+
+ return nil
+}
+
+// PreSave will set the Id and Username if missing. It will also fill
+// in the CreateAt, UpdateAt times. It will also hash the password. It should
+// be run before saving the user to the db.
+func (u *User) PreSave() {
+ if u.Id == "" {
+ u.Id = NewId()
+ }
+
+ if u.Username == "" {
+ u.Username = NewId()
+ }
+
+ u.Username = strings.ToLower(u.Username)
+ u.Email = strings.ToLower(u.Email)
+ u.Locale = strings.ToLower(u.Locale)
+
+ u.CreateAt = GetMillis()
+ u.UpdateAt = u.CreateAt
+
+ u.LastPasswordUpdate = u.CreateAt
+
+ u.MfaActive = false
+
+ if u.Locale == "" {
+ u.Locale = DEFAULT_LOCALE
+ }
+
+ if u.Props == nil {
+ u.Props = make(map[string]string)
+ }
+
+ if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
+ u.SetDefaultNotifications()
+ }
+
+ if len(u.Password) > 0 {
+ u.Password = HashPassword(u.Password)
+ }
+}
+
+// PreUpdate should be run before updating the user in the db.
+func (u *User) PreUpdate() {
+ u.Username = strings.ToLower(u.Username)
+ u.Email = strings.ToLower(u.Email)
+ u.Locale = strings.ToLower(u.Locale)
+ u.UpdateAt = GetMillis()
+
+ if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
+ u.SetDefaultNotifications()
+ } else if _, ok := u.NotifyProps["mention_keys"]; ok {
+ // Remove any blank mention keys
+ splitKeys := strings.Split(u.NotifyProps["mention_keys"], ",")
+ goodKeys := []string{}
+ for _, key := range splitKeys {
+ if len(key) > 0 {
+ goodKeys = append(goodKeys, strings.ToLower(key))
+ }
+ }
+ u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",")
+ }
+}
+
+func (u *User) SetDefaultNotifications() {
+ u.NotifyProps = make(map[string]string)
+ u.NotifyProps["email"] = "true"
+ u.NotifyProps["desktop"] = USER_NOTIFY_ALL
+ u.NotifyProps["desktop_sound"] = "true"
+ u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username
+ u.NotifyProps["first_name"] = "false"
+ u.NotifyProps["all"] = "true"
+ u.NotifyProps["channel"] = "true"
+ splitName := strings.Split(u.Nickname, " ")
+ if len(splitName) > 0 && splitName[0] != "" {
+ u.NotifyProps["first_name"] = "true"
+ u.NotifyProps["mention_keys"] += "," + splitName[0]
+ }
+}
+
+// ToJson convert a User to a json string
+func (u *User) 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() string {
+ return Etag(u.Id, u.UpdateAt)
+}
+
+func (u *User) IsOffline() bool {
+ return (GetMillis()-u.LastPingAt) > USER_OFFLINE_TIMEOUT && (GetMillis()-u.LastActivityAt) > USER_OFFLINE_TIMEOUT
+}
+
+func (u *User) IsAway() bool {
+ return (GetMillis() - u.LastActivityAt) > USER_AWAY_TIMEOUT
+}
+
+// Remove any private data from the user object
+func (u *User) Sanitize(options map[string]bool) {
+ u.Password = ""
+ u.AuthData = ""
+
+ if len(options) != 0 && !options["email"] {
+ u.Email = ""
+ }
+ if len(options) != 0 && !options["fullname"] {
+ u.FirstName = ""
+ u.LastName = ""
+ }
+ if len(options) != 0 && !options["passwordupdate"] {
+ u.LastPasswordUpdate = 0
+ }
+}
+
+func (u *User) ClearNonProfileFields() {
+ u.UpdateAt = 0
+ u.Password = ""
+ u.AuthData = ""
+ u.AuthService = ""
+ u.EmailVerified = false
+ u.LastPingAt = 0
+ u.AllowMarketing = false
+ u.Props = StringMap{}
+ u.NotifyProps = StringMap{}
+ u.ThemeProps = StringMap{}
+ u.LastPasswordUpdate = 0
+ u.LastPictureUpdate = 0
+ u.FailedAttempts = 0
+}
+
+func (u *User) MakeNonNil() {
+ if u.Props == nil {
+ u.Props = make(map[string]string)
+ }
+
+ if u.NotifyProps == nil {
+ u.NotifyProps = make(map[string]string)
+ }
+}
+
+func (u *User) AddProp(key string, value string) {
+ u.MakeNonNil()
+
+ u.Props[key] = value
+}
+
+func (u *User) AddNotifyProp(key string, value string) {
+ u.MakeNonNil()
+
+ u.NotifyProps[key] = value
+}
+
+func (u *User) GetFullName() string {
+ if u.FirstName != "" && u.LastName != "" {
+ return u.FirstName + " " + u.LastName
+ } else if u.FirstName != "" {
+ return u.FirstName
+ } else if u.LastName != "" {
+ return u.LastName
+ } else {
+ return ""
+ }
+}
+
+func (u *User) GetDisplayName() string {
+ if u.Nickname != "" {
+ return u.Nickname
+ } else if fullName := u.GetFullName(); fullName != "" {
+ return fullName
+ } else {
+ return u.Username
+ }
+}
+
+func IsValidRoles(userRoles string) bool {
+
+ roles := strings.Split(userRoles, " ")
+
+ for _, r := range roles {
+ if !isValidRole(r) {
+ return false
+ }
+ }
+
+ return true
+}
+
+func isValidRole(role string) bool {
+ if role == "" {
+ return true
+ }
+
+ if role == ROLE_TEAM_ADMIN {
+ return true
+ }
+
+ if role == ROLE_SYSTEM_ADMIN {
+ return true
+ }
+
+ return false
+}
+
+// Make sure you acually want to use this function. In context.go there are functions to check permssions
+// This function should not be used to check permissions.
+func (u *User) IsInRole(inRole string) bool {
+ return IsInRole(u.Roles, inRole)
+}
+
+// Make sure you acually want to use this function. In context.go there are functions to check permssions
+// This function should not be used to check permissions.
+func IsInRole(userRoles string, inRole string) bool {
+ roles := strings.Split(userRoles, " ")
+
+ for _, r := range roles {
+ if r == inRole {
+ return true
+ }
+
+ }
+
+ return false
+}
+
+func (u *User) IsSSOUser() bool {
+ if len(u.AuthData) != 0 && len(u.AuthService) != 0 && u.AuthService != USER_AUTH_SERVICE_LDAP {
+ return true
+ }
+ return false
+}
+
+func (u *User) IsLDAPUser() bool {
+ if u.AuthService == USER_AUTH_SERVICE_LDAP {
+ return true
+ }
+ return false
+}
+
+func (u *User) PreExport() {
+ u.Password = ""
+ u.AuthData = ""
+ u.LastActivityAt = 0
+ u.LastPingAt = 0
+ u.LastPasswordUpdate = 0
+ u.LastPictureUpdate = 0
+ u.FailedAttempts = 0
+}
+
+// UserFromJson will decode the input and return a User
+func UserFromJson(data io.Reader) *User {
+ decoder := json.NewDecoder(data)
+ var user User
+ 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 {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func UserMapFromJson(data io.Reader) map[string]*User {
+ decoder := json.NewDecoder(data)
+ var users map[string]*User
+ err := decoder.Decode(&users)
+ if err == nil {
+ return users
+ } else {
+ return nil
+ }
+}
+
+// HashPassword generates a hash using the bcrypt.GenerateFromPassword
+func HashPassword(password string) string {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
+ if err != nil {
+ panic(err)
+ }
+
+ return string(hash)
+}
+
+// ComparePassword compares the hash
+func ComparePassword(hash string, password string) bool {
+
+ if len(password) == 0 {
+ return false
+ }
+
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
+ return err == nil
+}
+
+var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
+
+var restrictedUsernames = []string{
+ "all",
+ "channel",
+}
+
+func IsValidUsername(s string) bool {
+ if len(s) == 0 || len(s) > 64 {
+ return false
+ }
+
+ if !validUsernameChars.MatchString(s) {
+ return false
+ }
+
+ for _, restrictedUsername := range restrictedUsernames {
+ if s == restrictedUsername {
+ return false
+ }
+ }
+
+ return true
+}
+
+func CleanUsername(s string) string {
+ s = strings.ToLower(strings.Replace(s, " ", "-", -1))
+
+ for _, value := range reservedName {
+ if s == value {
+ s = strings.Replace(s, value, "", -1)
+ }
+ }
+
+ s = strings.TrimSpace(s)
+
+ for _, c := range s {
+ char := fmt.Sprintf("%c", c)
+ if !validUsernameChars.MatchString(char) {
+ s = strings.Replace(s, char, "-", -1)
+ }
+ }
+
+ s = strings.Trim(s, "-")
+
+ if !IsValidUsername(s) {
+ s = "a" + NewId()
+ }
+
+ return s
+}
diff --git a/vendor/github.com/mattermost/platform/model/utils.go b/vendor/github.com/mattermost/platform/model/utils.go
new file mode 100644
index 00000000..1ce41bb3
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/utils.go
@@ -0,0 +1,381 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/base32"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/mail"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+ "github.com/pborman/uuid"
+)
+
+type StringInterface map[string]interface{}
+type StringMap map[string]string
+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"` // The RequestId that's also set in the header
+ StatusCode int `json:"status_code"` // The http status code
+ Where string `json:"-"` // The function where it happened in the form of Struct.Func
+ IsOAuth bool `json:"is_oauth"` // Whether the error is OAuth specific
+ params map[string]interface{} `json:"-"`
+}
+
+func (er *AppError) Error() string {
+ return er.Where + ": " + er.Message + ", " + er.DetailedError
+}
+
+func (er *AppError) Translate(T goi18n.TranslateFunc) {
+ if len(er.Message) == 0 {
+ if er.params == nil {
+ er.Message = T(er.Id)
+ } else {
+ er.Message = T(er.Id, er.params)
+ }
+ }
+}
+
+func (er *AppError) ToJson() string {
+ b, err := json.Marshal(er)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+// AppErrorFromJson will decode the input and return an AppError
+func AppErrorFromJson(data io.Reader) *AppError {
+ decoder := json.NewDecoder(data)
+ var er AppError
+ err := decoder.Decode(&er)
+ if err == nil {
+ return &er
+ } else {
+ return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error())
+ }
+}
+
+func NewLocAppError(where string, id string, params map[string]interface{}, details string) *AppError {
+ ap := &AppError{}
+ ap.Id = id
+ ap.params = params
+ ap.Where = where
+ ap.DetailedError = details
+ ap.StatusCode = 500
+ ap.IsOAuth = false
+ return ap
+}
+
+var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769")
+
+// NewId is a globally unique identifier. It is a [A-Z0-9] string 26
+// characters long. It is a UUID version 4 Guid that is zbased32 encoded
+// with the padding stripped off.
+func NewId() string {
+ var b bytes.Buffer
+ encoder := base32.NewEncoder(encoding, &b)
+ encoder.Write(uuid.NewRandom())
+ encoder.Close()
+ b.Truncate(26) // removes the '==' padding
+ return b.String()
+}
+
+func NewRandomString(length int) string {
+ var b bytes.Buffer
+ str := make([]byte, length+8)
+ rand.Read(str)
+ encoder := base32.NewEncoder(encoding, &b)
+ encoder.Write(str)
+ encoder.Close()
+ b.Truncate(length) // removes the '==' padding
+ return b.String()
+}
+
+// GetMillis is a convience method to get milliseconds since epoch.
+func GetMillis() int64 {
+ return time.Now().UnixNano() / int64(time.Millisecond)
+}
+
+// MapToJson converts a map to a json string
+func MapToJson(objmap map[string]string) string {
+ if b, err := json.Marshal(objmap); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+// MapFromJson will decode the key/value pair map
+func MapFromJson(data io.Reader) map[string]string {
+ decoder := json.NewDecoder(data)
+
+ var objmap map[string]string
+ if err := decoder.Decode(&objmap); err != nil {
+ return make(map[string]string)
+ } else {
+ return objmap
+ }
+}
+
+func ArrayToJson(objmap []string) string {
+ if b, err := json.Marshal(objmap); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ArrayFromJson(data io.Reader) []string {
+ decoder := json.NewDecoder(data)
+
+ var objmap []string
+ if err := decoder.Decode(&objmap); err != nil {
+ return make([]string, 0)
+ } else {
+ return objmap
+ }
+}
+
+func StringInterfaceToJson(objmap map[string]interface{}) string {
+ if b, err := json.Marshal(objmap); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
+ decoder := json.NewDecoder(data)
+
+ var objmap map[string]interface{}
+ if err := decoder.Decode(&objmap); err != nil {
+ return make(map[string]interface{})
+ } else {
+ return objmap
+ }
+}
+
+func IsLower(s string) bool {
+ if strings.ToLower(s) == s {
+ return true
+ }
+
+ return false
+}
+
+func IsValidEmail(email string) bool {
+
+ if !IsLower(email) {
+ return false
+ }
+
+ if _, err := mail.ParseAddress(email); err == nil {
+ return true
+ }
+
+ return false
+}
+
+var reservedName = []string{
+ "www",
+ "web",
+ "admin",
+ "support",
+ "notify",
+ "test",
+ "demo",
+ "mail",
+ "team",
+ "channel",
+ "internal",
+ "localhost",
+ "dockerhost",
+ "stag",
+ "post",
+ "cluster",
+ "api",
+ "oauth",
+}
+
+var wwwStart = regexp.MustCompile(`^www`)
+var betaStart = regexp.MustCompile(`^beta`)
+var ciStart = regexp.MustCompile(`^ci`)
+
+func GetSubDomain(s string) (string, string) {
+ s = strings.Replace(s, "http://", "", 1)
+ s = strings.Replace(s, "https://", "", 1)
+
+ match := wwwStart.MatchString(s)
+ if match {
+ return "", ""
+ }
+
+ match = betaStart.MatchString(s)
+ if match {
+ return "", ""
+ }
+
+ match = ciStart.MatchString(s)
+ if match {
+ return "", ""
+ }
+
+ parts := strings.Split(s, ".")
+
+ if len(parts) != 3 {
+ return "", ""
+ }
+
+ return parts[0], parts[1]
+}
+
+func IsValidChannelIdentifier(s string) bool {
+
+ if !IsValidAlphaNum(s, true) {
+ return false
+ }
+
+ if len(s) < 2 {
+ return false
+ }
+
+ return true
+}
+
+var validAlphaNumUnderscore = regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
+var validAlphaNum = regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
+
+func IsValidAlphaNum(s string, allowUnderscores bool) bool {
+ var match bool
+ if allowUnderscores {
+ match = validAlphaNumUnderscore.MatchString(s)
+ } else {
+ match = validAlphaNum.MatchString(s)
+ }
+
+ if !match {
+ return false
+ }
+
+ return true
+}
+
+func Etag(parts ...interface{}) string {
+
+ etag := CurrentVersion
+
+ for _, part := range parts {
+ etag += fmt.Sprintf(".%v", part)
+ }
+
+ return etag
+}
+
+var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`)
+var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\<>\-+=%^*|]+`)
+var hashtagStart = regexp.MustCompile(`^#{2,}`)
+var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^*|]+$`)
+var puncEndWildcard = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^|]+$`)
+
+func ParseHashtags(text string) (string, string) {
+ words := strings.Fields(text)
+
+ hashtagString := ""
+ plainString := ""
+ for _, word := range words {
+ // trim off surrounding punctuation
+ word = puncStart.ReplaceAllString(word, "")
+ word = puncEnd.ReplaceAllString(word, "")
+
+ // and remove extra pound #s
+ word = hashtagStart.ReplaceAllString(word, "#")
+
+ if validHashtag.MatchString(word) {
+ hashtagString += " " + word
+ } else {
+ plainString += " " + word
+ }
+ }
+
+ if len(hashtagString) > 1000 {
+ hashtagString = hashtagString[:999]
+ lastSpace := strings.LastIndex(hashtagString, " ")
+ if lastSpace > -1 {
+ hashtagString = hashtagString[:lastSpace]
+ } else {
+ hashtagString = ""
+ }
+ }
+
+ return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
+}
+
+func IsFileExtImage(ext string) bool {
+ ext = strings.ToLower(ext)
+ for _, imgExt := range IMAGE_EXTENSIONS {
+ if ext == imgExt {
+ return true
+ }
+ }
+ return false
+}
+
+func GetImageMimeType(ext string) string {
+ ext = strings.ToLower(ext)
+ if len(IMAGE_MIME_TYPES[ext]) == 0 {
+ return "image"
+ } else {
+ return IMAGE_MIME_TYPES[ext]
+ }
+}
+
+func ClearMentionTags(post string) string {
+ post = strings.Replace(post, "<mention>", "", -1)
+ post = strings.Replace(post, "</mention>", "", -1)
+ return post
+}
+
+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+_~\-\.%=&amp;]*)?)?(?:#[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}
+
+func IsValidHttpUrl(rawUrl string) bool {
+ if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
+ return false
+ }
+
+ if _, err := url.ParseRequestURI(rawUrl); err != nil {
+ return false
+ }
+
+ return true
+}
+
+func IsValidHttpsUrl(rawUrl string) bool {
+ if strings.Index(rawUrl, "https://") != 0 {
+ return false
+ }
+
+ if _, err := url.ParseRequestURI(rawUrl); err != nil {
+ return false
+ }
+
+ return true
+}
diff --git a/vendor/github.com/mattermost/platform/model/version.go b/vendor/github.com/mattermost/platform/model/version.go
new file mode 100644
index 00000000..b3950dcc
--- /dev/null
+++ b/vendor/github.com/mattermost/platform/model/version.go
@@ -0,0 +1,127 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// This is a list of all the current viersions including any patches.
+// It should be maitained in chronological order with most current
+// release at the front of the list.
+var versions = []string{
+ "2.1.0",
+ "2.0.0",
+ "1.4.0",
+ "1.3.0",
+ "1.2.1",
+ "1.2.0",
+ "1.1.0",
+ "1.0.0",
+ "0.7.1",
+ "0.7.0",
+ "0.6.0",
+ "0.5.0",
+}
+
+var CurrentVersion string = versions[0]
+var BuildNumber string
+var BuildDate string
+var BuildHash string
+var BuildEnterpriseReady string
+var versionsWithoutHotFixes []string
+
+func init() {
+ versionsWithoutHotFixes = make([]string, 0, len(versions))
+ seen := make(map[string]string)
+
+ for _, version := range versions {
+ maj, min, _ := SplitVersion(version)
+ verStr := fmt.Sprintf("%v.%v.0", maj, min)
+
+ if seen[verStr] == "" {
+ versionsWithoutHotFixes = append(versionsWithoutHotFixes, verStr)
+ seen[verStr] = verStr
+ }
+ }
+}
+
+func SplitVersion(version string) (int64, int64, int64) {
+ parts := strings.Split(version, ".")
+
+ major := int64(0)
+ minor := int64(0)
+ patch := int64(0)
+
+ if len(parts) > 0 {
+ major, _ = strconv.ParseInt(parts[0], 10, 64)
+ }
+
+ if len(parts) > 1 {
+ minor, _ = strconv.ParseInt(parts[1], 10, 64)
+ }
+
+ if len(parts) > 2 {
+ patch, _ = strconv.ParseInt(parts[2], 10, 64)
+ }
+
+ return major, minor, patch
+}
+
+func GetPreviousVersion(version string) string {
+ verMajor, verMinor, _ := SplitVersion(version)
+ verStr := fmt.Sprintf("%v.%v.0", verMajor, verMinor)
+
+ for index, v := range versionsWithoutHotFixes {
+ if v == verStr && len(versionsWithoutHotFixes) > index+1 {
+ return versionsWithoutHotFixes[index+1]
+ }
+ }
+
+ return ""
+}
+
+func IsOfficalBuild() bool {
+ return BuildNumber != "_BUILD_NUMBER_"
+}
+
+func IsCurrentVersion(versionToCheck string) bool {
+ currentMajor, currentMinor, _ := SplitVersion(CurrentVersion)
+ toCheckMajor, toCheckMinor, _ := SplitVersion(versionToCheck)
+
+ if toCheckMajor == currentMajor && toCheckMinor == currentMinor {
+ return true
+ } else {
+ return false
+ }
+}
+
+func IsPreviousVersionsSupported(versionToCheck string) bool {
+ toCheckMajor, toCheckMinor, _ := SplitVersion(versionToCheck)
+ versionToCheckStr := fmt.Sprintf("%v.%v.0", toCheckMajor, toCheckMinor)
+
+ // Current Supported
+ if versionsWithoutHotFixes[0] == versionToCheckStr {
+ return true
+ }
+
+ // Current - 1 Supported
+ if versionsWithoutHotFixes[1] == versionToCheckStr {
+ return true
+ }
+
+ // Current - 2 Supported
+ if versionsWithoutHotFixes[2] == versionToCheckStr {
+ return true
+ }
+
+ // Current - 3 Supported
+ if versionsWithoutHotFixes[3] == versionToCheckStr {
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE b/vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE
new file mode 100644
index 00000000..609cce79
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder
+
+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.
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go b/vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go
new file mode 100644
index 00000000..e93db95d
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go
@@ -0,0 +1,315 @@
+// Package bundle manages translations for multiple languages.
+package bundle
+
+import (
+ "encoding/json"
+ "fmt"
+ "gopkg.in/yaml.v2"
+ "io/ioutil"
+ "reflect"
+
+ "path/filepath"
+
+ "github.com/nicksnyder/go-i18n/i18n/language"
+ "github.com/nicksnyder/go-i18n/i18n/translation"
+)
+
+// TranslateFunc is a copy of i18n.TranslateFunc to avoid a circular dependency.
+type TranslateFunc func(translationID string, args ...interface{}) string
+
+// Bundle stores the translations for multiple languages.
+type Bundle struct {
+ // The primary translations for a language tag and translation id.
+ translations map[string]map[string]translation.Translation
+
+ // Translations that can be used when an exact language match is not possible.
+ fallbackTranslations map[string]map[string]translation.Translation
+}
+
+// New returns an empty bundle.
+func New() *Bundle {
+ return &Bundle{
+ translations: make(map[string]map[string]translation.Translation),
+ fallbackTranslations: make(map[string]map[string]translation.Translation),
+ }
+}
+
+// MustLoadTranslationFile is similar to LoadTranslationFile
+// except it panics if an error happens.
+func (b *Bundle) MustLoadTranslationFile(filename string) {
+ if err := b.LoadTranslationFile(filename); err != nil {
+ panic(err)
+ }
+}
+
+// LoadTranslationFile loads the translations from filename into memory.
+//
+// The language that the translations are associated with is parsed from the filename (e.g. en-US.json).
+//
+// Generally you should load translation files once during your program's initialization.
+func (b *Bundle) LoadTranslationFile(filename string) error {
+ buf, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+ return b.ParseTranslationFileBytes(filename, buf)
+}
+
+// ParseTranslationFileBytes is similar to LoadTranslationFile except it parses the bytes in buf.
+//
+// It is useful for parsing translation files embedded with go-bindata.
+func (b *Bundle) ParseTranslationFileBytes(filename string, buf []byte) error {
+ basename := filepath.Base(filename)
+ langs := language.Parse(basename)
+ switch l := len(langs); {
+ case l == 0:
+ return fmt.Errorf("no language found in %q", basename)
+ case l > 1:
+ return fmt.Errorf("multiple languages found in filename %q: %v; expected one", basename, langs)
+ }
+ translations, err := parseTranslations(filename, buf)
+ if err != nil {
+ return err
+ }
+ b.AddTranslation(langs[0], translations...)
+ return nil
+}
+
+func parseTranslations(filename string, buf []byte) ([]translation.Translation, error) {
+ var unmarshalFunc func([]byte, interface{}) error
+ switch format := filepath.Ext(filename); format {
+ case ".json":
+ unmarshalFunc = json.Unmarshal
+ case ".yaml":
+ unmarshalFunc = yaml.Unmarshal
+ default:
+ return nil, fmt.Errorf("unsupported file extension %s", format)
+ }
+
+ var translationsData []map[string]interface{}
+ if len(buf) > 0 {
+ if err := unmarshalFunc(buf, &translationsData); err != nil {
+ return nil, err
+ }
+ }
+
+ translations := make([]translation.Translation, 0, len(translationsData))
+ for i, translationData := range translationsData {
+ t, err := translation.NewTranslation(translationData)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse translation #%d in %s because %s\n%v", i, filename, err, translationData)
+ }
+ translations = append(translations, t)
+ }
+ return translations, nil
+}
+
+// AddTranslation adds translations for a language.
+//
+// It is useful if your translations are in a format not supported by LoadTranslationFile.
+func (b *Bundle) AddTranslation(lang *language.Language, translations ...translation.Translation) {
+ if b.translations[lang.Tag] == nil {
+ b.translations[lang.Tag] = make(map[string]translation.Translation, len(translations))
+ }
+ currentTranslations := b.translations[lang.Tag]
+ for _, newTranslation := range translations {
+ if currentTranslation := currentTranslations[newTranslation.ID()]; currentTranslation != nil {
+ currentTranslations[newTranslation.ID()] = currentTranslation.Merge(newTranslation)
+ } else {
+ currentTranslations[newTranslation.ID()] = newTranslation
+ }
+ }
+
+ // lang can provide translations for less specific language tags.
+ for _, tag := range lang.MatchingTags() {
+ b.fallbackTranslations[tag] = currentTranslations
+ }
+}
+
+// Translations returns all translations in the bundle.
+func (b *Bundle) Translations() map[string]map[string]translation.Translation {
+ return b.translations
+}
+
+// LanguageTags returns the tags of all languages that that have been added.
+func (b *Bundle) LanguageTags() []string {
+ var tags []string
+ for k := range b.translations {
+ tags = append(tags, k)
+ }
+ return tags
+}
+
+// LanguageTranslationIDs returns the ids of all translations that have been added for a given language.
+func (b *Bundle) LanguageTranslationIDs(languageTag string) []string {
+ var ids []string
+ for id := range b.translations[languageTag] {
+ ids = append(ids, id)
+ }
+ return ids
+}
+
+// MustTfunc is similar to Tfunc except it panics if an error happens.
+func (b *Bundle) MustTfunc(pref string, prefs ...string) TranslateFunc {
+ tfunc, err := b.Tfunc(pref, prefs...)
+ if err != nil {
+ panic(err)
+ }
+ return tfunc
+}
+
+// MustTfuncAndLanguage is similar to TfuncAndLanguage except it panics if an error happens.
+func (b *Bundle) MustTfuncAndLanguage(pref string, prefs ...string) (TranslateFunc, *language.Language) {
+ tfunc, language, err := b.TfuncAndLanguage(pref, prefs...)
+ if err != nil {
+ panic(err)
+ }
+ return tfunc, language
+}
+
+// Tfunc is similar to TfuncAndLanguage except is doesn't return the Language.
+func (b *Bundle) Tfunc(pref string, prefs ...string) (TranslateFunc, error) {
+ tfunc, _, err := b.TfuncAndLanguage(pref, prefs...)
+ return tfunc, err
+}
+
+// TfuncAndLanguage returns a TranslateFunc for the first Language that
+// has a non-zero number of translations in the bundle.
+//
+// The returned Language matches the the first language preference that could be satisfied,
+// but this may not strictly match the language of the translations used to satisfy that preference.
+//
+// For example, the user may request "zh". If there are no translations for "zh" but there are translations
+// for "zh-cn", then the translations for "zh-cn" will be used but the returned Language will be "zh".
+//
+// It can parse languages from Accept-Language headers (RFC 2616),
+// but it assumes weights are monotonically decreasing.
+func (b *Bundle) TfuncAndLanguage(pref string, prefs ...string) (TranslateFunc, *language.Language, error) {
+ lang := b.supportedLanguage(pref, prefs...)
+ var err error
+ if lang == nil {
+ err = fmt.Errorf("no supported languages found %#v", append(prefs, pref))
+ }
+ return func(translationID string, args ...interface{}) string {
+ return b.translate(lang, translationID, args...)
+ }, lang, err
+}
+
+// supportedLanguage returns the first language which
+// has a non-zero number of translations in the bundle.
+func (b *Bundle) supportedLanguage(pref string, prefs ...string) *language.Language {
+ lang := b.translatedLanguage(pref)
+ if lang == nil {
+ for _, pref := range prefs {
+ lang = b.translatedLanguage(pref)
+ if lang != nil {
+ break
+ }
+ }
+ }
+ return lang
+}
+
+func (b *Bundle) translatedLanguage(src string) *language.Language {
+ langs := language.Parse(src)
+ for _, lang := range langs {
+ if len(b.translations[lang.Tag]) > 0 ||
+ len(b.fallbackTranslations[lang.Tag]) > 0 {
+ return lang
+ }
+ }
+ return nil
+}
+
+func (b *Bundle) translate(lang *language.Language, translationID string, args ...interface{}) string {
+ if lang == nil {
+ return translationID
+ }
+
+ translations := b.translations[lang.Tag]
+ if translations == nil {
+ translations = b.fallbackTranslations[lang.Tag]
+ if translations == nil {
+ return translationID
+ }
+ }
+
+ translation := translations[translationID]
+ if translation == nil {
+ return translationID
+ }
+
+ var data interface{}
+ var count interface{}
+ if argc := len(args); argc > 0 {
+ if isNumber(args[0]) {
+ count = args[0]
+ if argc > 1 {
+ data = args[1]
+ }
+ } else {
+ data = args[0]
+ }
+ }
+
+ if count != nil {
+ if data == nil {
+ data = map[string]interface{}{"Count": count}
+ } else {
+ dataMap := toMap(data)
+ dataMap["Count"] = count
+ data = dataMap
+ }
+ }
+
+ p, _ := lang.Plural(count)
+ template := translation.Template(p)
+ if template == nil {
+ return translationID
+ }
+
+ s := template.Execute(data)
+ if s == "" {
+ return translationID
+ }
+ return s
+}
+
+func isNumber(n interface{}) bool {
+ switch n.(type) {
+ case int, int8, int16, int32, int64, string:
+ return true
+ }
+ return false
+}
+
+func toMap(input interface{}) map[string]interface{} {
+ if data, ok := input.(map[string]interface{}); ok {
+ return data
+ }
+ v := reflect.ValueOf(input)
+ switch v.Kind() {
+ case reflect.Ptr:
+ return toMap(v.Elem().Interface())
+ case reflect.Struct:
+ return structToMap(v)
+ default:
+ return nil
+ }
+}
+
+// Converts the top level of a struct to a map[string]interface{}.
+// Code inspired by github.com/fatih/structs.
+func structToMap(v reflect.Value) map[string]interface{} {
+ out := make(map[string]interface{})
+ t := v.Type()
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ if field.PkgPath != "" {
+ // unexported field. skip.
+ continue
+ }
+ out[field.Name] = v.FieldByName(field.Name).Interface()
+ }
+ return out
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go b/vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go
new file mode 100644
index 00000000..f9684296
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go
@@ -0,0 +1,152 @@
+// Package i18n supports string translations with variable substitution and CLDR pluralization.
+// It is intended to be used in conjunction with the goi18n command, although that is not strictly required.
+//
+// Initialization
+//
+// Your Go program should load translations during its initialization.
+// i18n.MustLoadTranslationFile("path/to/fr-FR.all.json")
+// If your translations are in a file format not supported by (Must)?LoadTranslationFile,
+// then you can use the AddTranslation function to manually add translations.
+//
+// Fetching a translation
+//
+// Use Tfunc or MustTfunc to fetch a TranslateFunc that will return the translated string for a specific language.
+// func handleRequest(w http.ResponseWriter, r *http.Request) {
+// cookieLang := r.Cookie("lang")
+// acceptLang := r.Header.Get("Accept-Language")
+// defaultLang = "en-US" // known valid language
+// T, err := i18n.Tfunc(cookieLang, acceptLang, defaultLang)
+// fmt.Println(T("Hello world"))
+// }
+//
+// Usually it is a good idea to identify strings by a generic id rather than the English translation,
+// but the rest of this documentation will continue to use the English translation for readability.
+// T("Hello world") // ok
+// T("programGreeting") // better!
+//
+// Variables
+//
+// TranslateFunc supports strings that have variables using the text/template syntax.
+// T("Hello {{.Person}}", map[string]interface{}{
+// "Person": "Bob",
+// })
+//
+// Pluralization
+//
+// TranslateFunc supports the pluralization of strings using the CLDR pluralization rules defined here:
+// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+// T("You have {{.Count}} unread emails.", 2)
+// T("I am {{.Count}} meters tall.", "1.7")
+//
+// Plural strings may also have variables.
+// T("{{.Person}} has {{.Count}} unread emails", 2, map[string]interface{}{
+// "Person": "Bob",
+// })
+//
+// Sentences with multiple plural components can be supported with nesting.
+// T("{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 3, map[string]interface{}{
+// "Person": "Bob",
+// "Timeframe": T("{{.Count}} days", 2),
+// })
+//
+// Templates
+//
+// You can use the .Funcs() method of a text/template or html/template to register a TranslateFunc
+// for usage inside of that template.
+package i18n
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/bundle"
+ "github.com/nicksnyder/go-i18n/i18n/language"
+ "github.com/nicksnyder/go-i18n/i18n/translation"
+)
+
+// TranslateFunc returns the translation of the string identified by translationID.
+//
+// If there is no translation for translationID, then the translationID itself is returned.
+// This makes it easy to identify missing translations in your app.
+//
+// If translationID is a non-plural form, then the first variadic argument may be a map[string]interface{}
+// or struct that contains template data.
+//
+// If translationID is a plural form, then the first variadic argument must be an integer type
+// (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45").
+// The second variadic argument may be a map[string]interface{} or struct that contains template data.
+type TranslateFunc func(translationID string, args ...interface{}) string
+
+// IdentityTfunc returns a TranslateFunc that always returns the translationID passed to it.
+//
+// It is a useful placeholder when parsing a text/template or html/template
+// before the actual Tfunc is available.
+func IdentityTfunc() TranslateFunc {
+ return func(translationID string, args ...interface{}) string {
+ return translationID
+ }
+}
+
+var defaultBundle = bundle.New()
+
+// MustLoadTranslationFile is similar to LoadTranslationFile
+// except it panics if an error happens.
+func MustLoadTranslationFile(filename string) {
+ defaultBundle.MustLoadTranslationFile(filename)
+}
+
+// LoadTranslationFile loads the translations from filename into memory.
+//
+// The language that the translations are associated with is parsed from the filename (e.g. en-US.json).
+//
+// Generally you should load translation files once during your program's initialization.
+func LoadTranslationFile(filename string) error {
+ return defaultBundle.LoadTranslationFile(filename)
+}
+
+// ParseTranslationFileBytes is similar to LoadTranslationFile except it parses the bytes in buf.
+//
+// It is useful for parsing translation files embedded with go-bindata.
+func ParseTranslationFileBytes(filename string, buf []byte) error {
+ return defaultBundle.ParseTranslationFileBytes(filename, buf)
+}
+
+// AddTranslation adds translations for a language.
+//
+// It is useful if your translations are in a format not supported by LoadTranslationFile.
+func AddTranslation(lang *language.Language, translations ...translation.Translation) {
+ defaultBundle.AddTranslation(lang, translations...)
+}
+
+// LanguageTags returns the tags of all languages that have been added.
+func LanguageTags() []string {
+ return defaultBundle.LanguageTags()
+}
+
+// LanguageTranslationIDs returns the ids of all translations that have been added for a given language.
+func LanguageTranslationIDs(languageTag string) []string {
+ return defaultBundle.LanguageTranslationIDs(languageTag)
+}
+
+// MustTfunc is similar to Tfunc except it panics if an error happens.
+func MustTfunc(languageSource string, languageSources ...string) TranslateFunc {
+ return TranslateFunc(defaultBundle.MustTfunc(languageSource, languageSources...))
+}
+
+// Tfunc returns a TranslateFunc that will be bound to the first language which
+// has a non-zero number of translations.
+//
+// It can parse languages from Accept-Language headers (RFC 2616).
+func Tfunc(languageSource string, languageSources ...string) (TranslateFunc, error) {
+ tfunc, err := defaultBundle.Tfunc(languageSource, languageSources...)
+ return TranslateFunc(tfunc), err
+}
+
+// MustTfuncAndLanguage is similar to TfuncAndLanguage except it panics if an error happens.
+func MustTfuncAndLanguage(languageSource string, languageSources ...string) (TranslateFunc, *language.Language) {
+ tfunc, lang := defaultBundle.MustTfuncAndLanguage(languageSource, languageSources...)
+ return TranslateFunc(tfunc), lang
+}
+
+// TfuncAndLanguage is similar to Tfunc except it also returns the language which TranslateFunc is bound to.
+func TfuncAndLanguage(languageSource string, languageSources ...string) (TranslateFunc, *language.Language, error) {
+ tfunc, lang, err := defaultBundle.TfuncAndLanguage(languageSource, languageSources...)
+ return TranslateFunc(tfunc), lang, err
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go
new file mode 100644
index 00000000..5d6b6ad4
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "text/template"
+)
+
+var usage = `%[1]s generates Go code to support CLDR plural rules.
+
+Usage: %[1]s [options]
+
+Options:
+
+`
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, usage, os.Args[0])
+ flag.PrintDefaults()
+ }
+ var in, cout, tout string
+ flag.StringVar(&in, "i", "plurals.xml", "the input XML file containing CLDR plural rules")
+ flag.StringVar(&cout, "cout", "", "the code output file")
+ flag.StringVar(&tout, "tout", "", "the test output file")
+ flag.BoolVar(&verbose, "v", false, "verbose output")
+ flag.Parse()
+
+ buf, err := ioutil.ReadFile(in)
+ if err != nil {
+ fatalf("failed to read file: %s", err)
+ }
+
+ var data SupplementalData
+ if err := xml.Unmarshal(buf, &data); err != nil {
+ fatalf("failed to unmarshal xml: %s", err)
+ }
+
+ count := 0
+ for _, pg := range data.PluralGroups {
+ count += len(pg.SplitLocales())
+ }
+ infof("parsed %d locales", count)
+
+ if cout != "" {
+ file := openWritableFile(cout)
+ if err := codeTemplate.Execute(file, data); err != nil {
+ fatalf("unable to execute code template because %s", err)
+ } else {
+ infof("generated %s", cout)
+ }
+ } else {
+ infof("not generating code file (use -cout)")
+ }
+
+ if tout != "" {
+ file := openWritableFile(tout)
+ if err := testTemplate.Execute(file, data); err != nil {
+ fatalf("unable to execute test template because %s", err)
+ } else {
+ infof("generated %s", tout)
+ }
+ } else {
+ infof("not generating test file (use -tout)")
+ }
+}
+
+func openWritableFile(name string) *os.File {
+ file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ fatalf("failed to write file %s because %s", name, err)
+ }
+ return file
+}
+
+var codeTemplate = template.Must(template.New("spec").Parse(`package language
+// This file is generated by i18n/language/codegen/generate.sh
+
+func init() {
+{{range .PluralGroups}}
+ registerPluralSpec({{printf "%#v" .SplitLocales}}, &PluralSpec{
+ Plurals: newPluralSet({{range $i, $e := .PluralRules}}{{if $i}}, {{end}}{{$e.CountTitle}}{{end}}),
+ PluralFunc: func(ops *operands) Plural { {{range .PluralRules}}{{if .GoCondition}}
+ // {{.Condition}}
+ if {{.GoCondition}} {
+ return {{.CountTitle}}
+ }{{end}}{{end}}
+ return Other
+ },
+ }){{end}}
+}
+`))
+
+var testTemplate = template.Must(template.New("spec").Parse(`package language
+// This file is generated by i18n/language/codegen/generate.sh
+
+import "testing"
+
+{{range .PluralGroups}}
+func Test{{.Name}}(t *testing.T) {
+ var tests []pluralTest
+ {{range .PluralRules}}
+ {{if .IntegerExamples}}tests = appendIntegerTests(tests, {{.CountTitle}}, {{printf "%#v" .IntegerExamples}}){{end}}
+ {{if .DecimalExamples}}tests = appendDecimalTests(tests, {{.CountTitle}}, {{printf "%#v" .DecimalExamples}}){{end}}
+ {{end}}
+ locales := {{printf "%#v" .SplitLocales}}
+ for _, locale := range locales {
+ runTests(t, locale, tests)
+ }
+}
+{{end}}
+`))
+
+func infof(format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, format+"\n", args...)
+}
+
+var verbose bool
+
+func verbosef(format string, args ...interface{}) {
+ if verbose {
+ infof(format, args...)
+ }
+}
+
+func fatalf(format string, args ...interface{}) {
+ infof("fatal: "+format+"\n", args...)
+ os.Exit(1)
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go
new file mode 100644
index 00000000..9d39053c
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+ "encoding/xml"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// SupplementalData is the top level struct of plural.xml
+type SupplementalData struct {
+ XMLName xml.Name `xml:"supplementalData"`
+ PluralGroups []PluralGroup `xml:"plurals>pluralRules"`
+}
+
+// PluralGroup is a group of locales with the same plural rules.
+type PluralGroup struct {
+ Locales string `xml:"locales,attr"`
+ PluralRules []PluralRule `xml:"pluralRule"`
+}
+
+// Name returns a unique name for this plural group.
+func (pg *PluralGroup) Name() string {
+ n := strings.Title(pg.Locales)
+ return strings.Replace(n, " ", "", -1)
+}
+
+// SplitLocales returns all the locales in the PluralGroup as a slice.
+func (pg *PluralGroup) SplitLocales() []string {
+ return strings.Split(pg.Locales, " ")
+}
+
+// PluralRule is the rule for a single plural form.
+type PluralRule struct {
+ Count string `xml:"count,attr"`
+ Rule string `xml:",innerxml"`
+}
+
+// CountTitle returns the title case of the PluralRule's count.
+func (pr *PluralRule) CountTitle() string {
+ return strings.Title(pr.Count)
+}
+
+// Condition returns the condition where the PluralRule applies.
+func (pr *PluralRule) Condition() string {
+ i := strings.Index(pr.Rule, "@")
+ return pr.Rule[:i]
+}
+
+// Examples returns the integer and decimal exmaples for the PLuralRule.
+func (pr *PluralRule) Examples() (integer []string, decimal []string) {
+ ex := strings.Replace(pr.Rule, ", …", "", -1)
+ ddelim := "@decimal"
+ if i := strings.Index(ex, ddelim); i > 0 {
+ dex := strings.TrimSpace(ex[i+len(ddelim):])
+ decimal = strings.Split(dex, ", ")
+ ex = ex[:i]
+ }
+ idelim := "@integer"
+ if i := strings.Index(ex, idelim); i > 0 {
+ iex := strings.TrimSpace(ex[i+len(idelim):])
+ integer = strings.Split(iex, ", ")
+ }
+ return integer, decimal
+}
+
+// IntegerExamples returns the integer exmaples for the PLuralRule.
+func (pr *PluralRule) IntegerExamples() []string {
+ integer, _ := pr.Examples()
+ return integer
+}
+
+// DecimalExamples returns the decimal exmaples for the PLuralRule.
+func (pr *PluralRule) DecimalExamples() []string {
+ _, decimal := pr.Examples()
+ return decimal
+}
+
+var relationRegexp = regexp.MustCompile("([niftvw])(?: % ([0-9]+))? (!=|=)(.*)")
+
+// GoCondition converts the XML condition to valid Go code.
+func (pr *PluralRule) GoCondition() string {
+ var ors []string
+ for _, and := range strings.Split(pr.Condition(), "or") {
+ var ands []string
+ for _, relation := range strings.Split(and, "and") {
+ parts := relationRegexp.FindStringSubmatch(relation)
+ if parts == nil {
+ continue
+ }
+ lvar, lmod, op, rhs := strings.Title(parts[1]), parts[2], parts[3], strings.TrimSpace(parts[4])
+ if op == "=" {
+ op = "=="
+ }
+ lvar = "ops." + lvar
+ var rhor []string
+ var rany []string
+ for _, rh := range strings.Split(rhs, ",") {
+ if parts := strings.Split(rh, ".."); len(parts) == 2 {
+ from, to := parts[0], parts[1]
+ if lvar == "ops.N" {
+ if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("ops.NmodInRange(%s, %s, %s)", lmod, from, to))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("ops.NinRange(%s, %s)", from, to))
+ }
+ } else if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("intInRange(%s %% %s, %s, %s)", lvar, lmod, from, to))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("intInRange(%s, %s, %s)", lvar, from, to))
+ }
+ } else {
+ rany = append(rany, rh)
+ }
+ }
+
+ if len(rany) > 0 {
+ rh := strings.Join(rany, ",")
+ if lvar == "ops.N" {
+ if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("ops.NmodEqualsAny(%s, %s)", lmod, rh))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("ops.NequalsAny(%s)", rh))
+ }
+ } else if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("intEqualsAny(%s %% %s, %s)", lvar, lmod, rh))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("intEqualsAny(%s, %s)", lvar, rh))
+ }
+ }
+ r := strings.Join(rhor, " || ")
+ if len(rhor) > 1 {
+ r = "(" + r + ")"
+ }
+ if op == "!=" {
+ r = "!" + r
+ }
+ ands = append(ands, r)
+ }
+ ors = append(ors, strings.Join(ands, " && "))
+ }
+ return strings.Join(ors, " ||\n")
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go
new file mode 100644
index 00000000..9a155efc
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go
@@ -0,0 +1,99 @@
+// Package language defines languages that implement CLDR pluralization.
+package language
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Language is a written human language.
+type Language struct {
+ // Tag uniquely identifies the language as defined by RFC 5646.
+ //
+ // Most language tags are a two character language code (ISO 639-1)
+ // optionally followed by a dash and a two character country code (ISO 3166-1).
+ // (e.g. en, pt-br)
+ Tag string
+ *PluralSpec
+}
+
+func (l *Language) String() string {
+ return l.Tag
+}
+
+// MatchingTags returns the set of language tags that map to this Language.
+// e.g. "zh-hans-cn" yields {"zh", "zh-hans", "zh-hans-cn"}
+// BUG: This should be computed once and stored as a field on Language for efficiency,
+// but this would require changing how Languages are constructed.
+func (l *Language) MatchingTags() []string {
+ parts := strings.Split(l.Tag, "-")
+ var prefix, matches []string
+ for _, part := range parts {
+ prefix = append(prefix, part)
+ match := strings.Join(prefix, "-")
+ matches = append(matches, match)
+ }
+ return matches
+}
+
+// Parse returns a slice of supported languages found in src or nil if none are found.
+// It can parse language tags and Accept-Language headers.
+func Parse(src string) []*Language {
+ var langs []*Language
+ start := 0
+ for end, chr := range src {
+ switch chr {
+ case ',', ';', '.':
+ tag := strings.TrimSpace(src[start:end])
+ if spec := getPluralSpec(tag); spec != nil {
+ langs = append(langs, &Language{NormalizeTag(tag), spec})
+ }
+ start = end + 1
+ }
+ }
+ if start > 0 {
+ tag := strings.TrimSpace(src[start:])
+ if spec := getPluralSpec(tag); spec != nil {
+ langs = append(langs, &Language{NormalizeTag(tag), spec})
+ }
+ return dedupe(langs)
+ }
+ if spec := getPluralSpec(src); spec != nil {
+ langs = append(langs, &Language{NormalizeTag(src), spec})
+ }
+ return langs
+}
+
+func dedupe(langs []*Language) []*Language {
+ found := make(map[string]struct{}, len(langs))
+ deduped := make([]*Language, 0, len(langs))
+ for _, lang := range langs {
+ if _, ok := found[lang.Tag]; !ok {
+ found[lang.Tag] = struct{}{}
+ deduped = append(deduped, lang)
+ }
+ }
+ return deduped
+}
+
+// MustParse is similar to Parse except it panics instead of retuning a nil Language.
+func MustParse(src string) []*Language {
+ langs := Parse(src)
+ if len(langs) == 0 {
+ panic(fmt.Errorf("unable to parse language from %q", src))
+ }
+ return langs
+}
+
+// Add adds support for a new language.
+func Add(l *Language) {
+ tag := NormalizeTag(l.Tag)
+ pluralSpecs[tag] = l.PluralSpec
+}
+
+// NormalizeTag returns a language tag with all lower-case characters
+// and dashes "-" instead of underscores "_"
+func NormalizeTag(tag string) string {
+ tag = strings.ToLower(tag)
+ return strings.Replace(tag, "_", "-", -1)
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go
new file mode 100644
index 00000000..877bcc89
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go
@@ -0,0 +1,119 @@
+package language
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// http://unicode.org/reports/tr35/tr35-numbers.html#Operands
+type operands struct {
+ N float64 // absolute value of the source number (integer and decimals)
+ I int64 // integer digits of n
+ V int64 // number of visible fraction digits in n, with trailing zeros
+ W int64 // number of visible fraction digits in n, without trailing zeros
+ F int64 // visible fractional digits in n, with trailing zeros
+ T int64 // visible fractional digits in n, without trailing zeros
+}
+
+// NmodEqualAny returns true if o represents an integer equal to any of the arguments.
+func (o *operands) NequalsAny(any ...int64) bool {
+ for _, i := range any {
+ if o.I == i && o.T == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// NmodEqualAny returns true if o represents an integer equal to any of the arguments modulo mod.
+func (o *operands) NmodEqualsAny(mod int64, any ...int64) bool {
+ modI := o.I % mod
+ for _, i := range any {
+ if modI == i && o.T == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// NmodInRange returns true if o represents an integer in the closed interval [from, to].
+func (o *operands) NinRange(from, to int64) bool {
+ return o.T == 0 && from <= o.I && o.I <= to
+}
+
+// NmodInRange returns true if o represents an integer in the closed interval [from, to] modulo mod.
+func (o *operands) NmodInRange(mod, from, to int64) bool {
+ modI := o.I % mod
+ return o.T == 0 && from <= modI && modI <= to
+}
+
+func newOperands(v interface{}) (*operands, error) {
+ switch v := v.(type) {
+ case int:
+ return newOperandsInt64(int64(v)), nil
+ case int8:
+ return newOperandsInt64(int64(v)), nil
+ case int16:
+ return newOperandsInt64(int64(v)), nil
+ case int32:
+ return newOperandsInt64(int64(v)), nil
+ case int64:
+ return newOperandsInt64(v), nil
+ case string:
+ return newOperandsString(v)
+ case float32, float64:
+ return nil, fmt.Errorf("floats should be formatted into a string")
+ default:
+ return nil, fmt.Errorf("invalid type %T; expected integer or string", v)
+ }
+}
+
+func newOperandsInt64(i int64) *operands {
+ if i < 0 {
+ i = -i
+ }
+ return &operands{float64(i), i, 0, 0, 0, 0}
+}
+
+func newOperandsString(s string) (*operands, error) {
+ if s[0] == '-' {
+ s = s[1:]
+ }
+ n, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return nil, err
+ }
+ ops := &operands{N: n}
+ parts := strings.SplitN(s, ".", 2)
+ ops.I, err = strconv.ParseInt(parts[0], 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ if len(parts) == 1 {
+ return ops, nil
+ }
+ fraction := parts[1]
+ ops.V = int64(len(fraction))
+ for i := ops.V - 1; i >= 0; i-- {
+ if fraction[i] != '0' {
+ ops.W = i + 1
+ break
+ }
+ }
+ if ops.V > 0 {
+ f, err := strconv.ParseInt(fraction, 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ops.F = f
+ }
+ if ops.W > 0 {
+ t, err := strconv.ParseInt(fraction[:ops.W], 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ops.T = t
+ }
+ return ops, nil
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go
new file mode 100644
index 00000000..1f3ea5c6
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go
@@ -0,0 +1,40 @@
+package language
+
+import (
+ "fmt"
+)
+
+// Plural represents a language pluralization form as defined here:
+// http://cldr.unicode.org/index/cldr-spec/plural-rules
+type Plural string
+
+// All defined plural categories.
+const (
+ Invalid Plural = "invalid"
+ Zero = "zero"
+ One = "one"
+ Two = "two"
+ Few = "few"
+ Many = "many"
+ Other = "other"
+)
+
+// NewPlural returns src as a Plural
+// or Invalid and a non-nil error if src is not a valid Plural.
+func NewPlural(src string) (Plural, error) {
+ switch src {
+ case "zero":
+ return Zero, nil
+ case "one":
+ return One, nil
+ case "two":
+ return Two, nil
+ case "few":
+ return Few, nil
+ case "many":
+ return Many, nil
+ case "other":
+ return Other, nil
+ }
+ return Invalid, fmt.Errorf("invalid plural category %s", src)
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go
new file mode 100644
index 00000000..fc352268
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go
@@ -0,0 +1,74 @@
+package language
+
+import "strings"
+
+// PluralSpec defines the CLDR plural rules for a language.
+// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+// http://unicode.org/reports/tr35/tr35-numbers.html#Operands
+type PluralSpec struct {
+ Plurals map[Plural]struct{}
+ PluralFunc func(*operands) Plural
+}
+
+var pluralSpecs = make(map[string]*PluralSpec)
+
+func normalizePluralSpecID(id string) string {
+ id = strings.Replace(id, "_", "-", -1)
+ id = strings.ToLower(id)
+ return id
+}
+
+func registerPluralSpec(ids []string, ps *PluralSpec) {
+ for _, id := range ids {
+ id = normalizePluralSpecID(id)
+ pluralSpecs[id] = ps
+ }
+}
+
+// Plural returns the plural category for number as defined by
+// the language's CLDR plural rules.
+func (ps *PluralSpec) Plural(number interface{}) (Plural, error) {
+ ops, err := newOperands(number)
+ if err != nil {
+ return Invalid, err
+ }
+ return ps.PluralFunc(ops), nil
+}
+
+// getPluralSpec returns the PluralSpec that matches the longest prefix of tag.
+// It returns nil if no PluralSpec matches tag.
+func getPluralSpec(tag string) *PluralSpec {
+ tag = NormalizeTag(tag)
+ subtag := tag
+ for {
+ if spec := pluralSpecs[subtag]; spec != nil {
+ return spec
+ }
+ end := strings.LastIndex(subtag, "-")
+ if end == -1 {
+ return nil
+ }
+ subtag = subtag[:end]
+ }
+}
+
+func newPluralSet(plurals ...Plural) map[Plural]struct{} {
+ set := make(map[Plural]struct{}, len(plurals))
+ for _, plural := range plurals {
+ set[plural] = struct{}{}
+ }
+ return set
+}
+
+func intInRange(i, from, to int64) bool {
+ return from <= i && i <= to
+}
+
+func intEqualsAny(i int64, any ...int64) bool {
+ for _, a := range any {
+ if i == a {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go
new file mode 100644
index 00000000..c9b4f266
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go
@@ -0,0 +1,567 @@
+package language
+
+// This file is generated by i18n/language/codegen/generate.sh
+
+func init() {
+
+ registerPluralSpec([]string{"bm", "bo", "dz", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "root", "sah", "ses", "sg", "th", "to", "vi", "wo", "yo", "zh"}, &PluralSpec{
+ Plurals: newPluralSet(Other),
+ PluralFunc: func(ops *operands) Plural {
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"am", "as", "bn", "fa", "gu", "hi", "kn", "mr", "zu"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 0 or n = 1
+ if intEqualsAny(ops.I, 0) ||
+ ops.NequalsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ff", "fr", "hy", "kab"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 0,1
+ if intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "it", "ji", "nl", "sv", "sw", "ur", "yi"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"si"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0,1 or i = 0 and f = 1
+ if ops.NequalsAny(0, 1) ||
+ intEqualsAny(ops.I, 0) && intEqualsAny(ops.F, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ak", "bh", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0..1
+ if ops.NinRange(0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"tzm"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0..1 or n = 11..99
+ if ops.NinRange(0, 1) ||
+ ops.NinRange(11, 99) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"pt"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0..2 and n != 2
+ if ops.NinRange(0, 2) && !ops.NequalsAny(2) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"af", "asa", "az", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "es", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"pt_PT"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1 and v = 0
+ if ops.NequalsAny(1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"da"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1 or t != 0 and i = 0,1
+ if ops.NequalsAny(1) ||
+ !intEqualsAny(ops.T, 0) && intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"is"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0
+ if intEqualsAny(ops.T, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ !intEqualsAny(ops.T, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"mk"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1 or f % 10 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) ||
+ intEqualsAny(ops.F%10, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"fil", "tl"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I, 1, 2, 3) ||
+ intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I%10, 4, 6, 9) ||
+ !intEqualsAny(ops.V, 0) && !intEqualsAny(ops.F%10, 4, 6, 9) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"lv", "prg"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19
+ if ops.NmodEqualsAny(10, 0) ||
+ ops.NmodInRange(100, 11, 19) ||
+ intEqualsAny(ops.V, 2) && intInRange(ops.F%100, 11, 19) {
+ return Zero
+ }
+ // n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodEqualsAny(100, 11) ||
+ intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) ||
+ !intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"lag"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // i = 0,1 and n != 0
+ if intEqualsAny(ops.I, 0, 1) && !ops.NequalsAny(0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ksh"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"iu", "kw", "naq", "se", "sma", "smi", "smj", "smn", "sms"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"shi"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 0 or n = 1
+ if intEqualsAny(ops.I, 0) ||
+ ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2..10
+ if ops.NinRange(2, 10) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"mo", "ro"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // v != 0 or n = 0 or n != 1 and n % 100 = 1..19
+ if !intEqualsAny(ops.V, 0) ||
+ ops.NequalsAny(0) ||
+ !ops.NequalsAny(1) && ops.NmodInRange(100, 1, 19) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"bs", "hr", "sh", "sr"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) ||
+ intInRange(ops.F%10, 2, 4) && !intInRange(ops.F%100, 12, 14) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"gd"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1,11
+ if ops.NequalsAny(1, 11) {
+ return One
+ }
+ // n = 2,12
+ if ops.NequalsAny(2, 12) {
+ return Two
+ }
+ // n = 3..10,13..19
+ if ops.NinRange(3, 10) || ops.NinRange(13, 19) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"sl"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 100 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) {
+ return One
+ }
+ // v = 0 and i % 100 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 3..4 or v != 0
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) ||
+ !intEqualsAny(ops.V, 0) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"dsb", "hsb"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 100 = 1 or f % 100 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) ||
+ intEqualsAny(ops.F%100, 1) {
+ return One
+ }
+ // v = 0 and i % 100 = 2 or f % 100 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) ||
+ intEqualsAny(ops.F%100, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 3..4 or f % 100 = 3..4
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) ||
+ intInRange(ops.F%100, 3, 4) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"he", "iw"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // i = 2 and v = 0
+ if intEqualsAny(ops.I, 2) && intEqualsAny(ops.V, 0) {
+ return Two
+ }
+ // v = 0 and n != 0..10 and n % 10 = 0
+ if intEqualsAny(ops.V, 0) && !ops.NinRange(0, 10) && ops.NmodEqualsAny(10, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"cs", "sk"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // i = 2..4 and v = 0
+ if intInRange(ops.I, 2, 4) && intEqualsAny(ops.V, 0) {
+ return Few
+ }
+ // v != 0
+ if !intEqualsAny(ops.V, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"pl"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) {
+ return Few
+ }
+ // v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14
+ if intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I, 1) && intInRange(ops.I%10, 0, 1) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 12, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"be"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 1 and n % 100 != 11
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodEqualsAny(100, 11) {
+ return One
+ }
+ // n % 10 = 2..4 and n % 100 != 12..14
+ if ops.NmodInRange(10, 2, 4) && !ops.NmodInRange(100, 12, 14) {
+ return Few
+ }
+ // n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14
+ if ops.NmodEqualsAny(10, 0) ||
+ ops.NmodInRange(10, 5, 9) ||
+ ops.NmodInRange(100, 11, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"lt"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 1 and n % 100 != 11..19
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodInRange(100, 11, 19) {
+ return One
+ }
+ // n % 10 = 2..9 and n % 100 != 11..19
+ if ops.NmodInRange(10, 2, 9) && !ops.NmodInRange(100, 11, 19) {
+ return Few
+ }
+ // f != 0
+ if !intEqualsAny(ops.F, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"mt"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 0 or n % 100 = 2..10
+ if ops.NequalsAny(0) ||
+ ops.NmodInRange(100, 2, 10) {
+ return Few
+ }
+ // n % 100 = 11..19
+ if ops.NmodInRange(100, 11, 19) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ru", "uk"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1 and i % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) {
+ return Few
+ }
+ // v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 0) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 11, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"br"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 1 and n % 100 != 11,71,91
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodEqualsAny(100, 11, 71, 91) {
+ return One
+ }
+ // n % 10 = 2 and n % 100 != 12,72,92
+ if ops.NmodEqualsAny(10, 2) && !ops.NmodEqualsAny(100, 12, 72, 92) {
+ return Two
+ }
+ // n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99
+ if (ops.NmodInRange(10, 3, 4) || ops.NmodEqualsAny(10, 9)) && !(ops.NmodInRange(100, 10, 19) || ops.NmodInRange(100, 70, 79) || ops.NmodInRange(100, 90, 99)) {
+ return Few
+ }
+ // n != 0 and n % 1000000 = 0
+ if !ops.NequalsAny(0) && ops.NmodEqualsAny(1000000, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ga"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ // n = 3..6
+ if ops.NinRange(3, 6) {
+ return Few
+ }
+ // n = 7..10
+ if ops.NinRange(7, 10) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"gv"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) {
+ return One
+ }
+ // v = 0 and i % 10 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 0,20,40,60,80
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 0, 20, 40, 60, 80) {
+ return Few
+ }
+ // v != 0
+ if !intEqualsAny(ops.V, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ar"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ // n % 100 = 3..10
+ if ops.NmodInRange(100, 3, 10) {
+ return Few
+ }
+ // n % 100 = 11..99
+ if ops.NmodInRange(100, 11, 99) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"cy"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ // n = 3
+ if ops.NequalsAny(3) {
+ return Few
+ }
+ // n = 6
+ if ops.NequalsAny(6) {
+ return Many
+ }
+ return Other
+ },
+ })
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go
new file mode 100644
index 00000000..4f579d16
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go
@@ -0,0 +1,78 @@
+package translation
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/language"
+)
+
+type pluralTranslation struct {
+ id string
+ templates map[language.Plural]*template
+}
+
+func (pt *pluralTranslation) MarshalInterface() interface{} {
+ return map[string]interface{}{
+ "id": pt.id,
+ "translation": pt.templates,
+ }
+}
+
+func (pt *pluralTranslation) ID() string {
+ return pt.id
+}
+
+func (pt *pluralTranslation) Template(pc language.Plural) *template {
+ return pt.templates[pc]
+}
+
+func (pt *pluralTranslation) UntranslatedCopy() Translation {
+ return &pluralTranslation{pt.id, make(map[language.Plural]*template)}
+}
+
+func (pt *pluralTranslation) Normalize(l *language.Language) Translation {
+ // Delete plural categories that don't belong to this language.
+ for pc := range pt.templates {
+ if _, ok := l.Plurals[pc]; !ok {
+ delete(pt.templates, pc)
+ }
+ }
+ // Create map entries for missing valid categories.
+ for pc := range l.Plurals {
+ if _, ok := pt.templates[pc]; !ok {
+ pt.templates[pc] = mustNewTemplate("")
+ }
+ }
+ return pt
+}
+
+func (pt *pluralTranslation) Backfill(src Translation) Translation {
+ for pc, t := range pt.templates {
+ if t == nil || t.src == "" {
+ pt.templates[pc] = src.Template(language.Other)
+ }
+ }
+ return pt
+}
+
+func (pt *pluralTranslation) Merge(t Translation) Translation {
+ other, ok := t.(*pluralTranslation)
+ if !ok || pt.ID() != t.ID() {
+ return t
+ }
+ for pluralCategory, template := range other.templates {
+ if template != nil && template.src != "" {
+ pt.templates[pluralCategory] = template
+ }
+ }
+ return pt
+}
+
+func (pt *pluralTranslation) Incomplete(l *language.Language) bool {
+ for pc := range l.Plurals {
+ if t := pt.templates[pc]; t == nil || t.src == "" {
+ return true
+ }
+ }
+ return false
+}
+
+var _ = Translation(&pluralTranslation{})
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go
new file mode 100644
index 00000000..1010e594
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go
@@ -0,0 +1,57 @@
+package translation
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/language"
+)
+
+type singleTranslation struct {
+ id string
+ template *template
+}
+
+func (st *singleTranslation) MarshalInterface() interface{} {
+ return map[string]interface{}{
+ "id": st.id,
+ "translation": st.template,
+ }
+}
+
+func (st *singleTranslation) ID() string {
+ return st.id
+}
+
+func (st *singleTranslation) Template(pc language.Plural) *template {
+ return st.template
+}
+
+func (st *singleTranslation) UntranslatedCopy() Translation {
+ return &singleTranslation{st.id, mustNewTemplate("")}
+}
+
+func (st *singleTranslation) Normalize(language *language.Language) Translation {
+ return st
+}
+
+func (st *singleTranslation) Backfill(src Translation) Translation {
+ if st.template == nil || st.template.src == "" {
+ st.template = src.Template(language.Other)
+ }
+ return st
+}
+
+func (st *singleTranslation) Merge(t Translation) Translation {
+ other, ok := t.(*singleTranslation)
+ if !ok || st.ID() != t.ID() {
+ return t
+ }
+ if other.template != nil && other.template.src != "" {
+ st.template = other.template
+ }
+ return st
+}
+
+func (st *singleTranslation) Incomplete(l *language.Language) bool {
+ return st.template == nil || st.template.src == ""
+}
+
+var _ = Translation(&singleTranslation{})
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go
new file mode 100644
index 00000000..c8756fa4
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go
@@ -0,0 +1,61 @@
+package translation
+
+import (
+ "bytes"
+ "encoding"
+ "strings"
+ gotemplate "text/template"
+)
+
+type template struct {
+ tmpl *gotemplate.Template
+ src string
+}
+
+func newTemplate(src string) (*template, error) {
+ var tmpl template
+ err := tmpl.parseTemplate(src)
+ return &tmpl, err
+}
+
+func mustNewTemplate(src string) *template {
+ t, err := newTemplate(src)
+ if err != nil {
+ panic(err)
+ }
+ return t
+}
+
+func (t *template) String() string {
+ return t.src
+}
+
+func (t *template) Execute(args interface{}) string {
+ if t.tmpl == nil {
+ return t.src
+ }
+ var buf bytes.Buffer
+ if err := t.tmpl.Execute(&buf, args); err != nil {
+ return err.Error()
+ }
+ return buf.String()
+}
+
+func (t *template) MarshalText() ([]byte, error) {
+ return []byte(t.src), nil
+}
+
+func (t *template) UnmarshalText(src []byte) error {
+ return t.parseTemplate(string(src))
+}
+
+func (t *template) parseTemplate(src string) (err error) {
+ t.src = src
+ if strings.Contains(src, "{{") {
+ t.tmpl, err = gotemplate.New(src).Parse(src)
+ }
+ return
+}
+
+var _ = encoding.TextMarshaler(&template{})
+var _ = encoding.TextUnmarshaler(&template{})
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go
new file mode 100644
index 00000000..fa93180b
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go
@@ -0,0 +1,83 @@
+// Package translation defines the interface for a translation.
+package translation
+
+import (
+ "fmt"
+
+ "github.com/nicksnyder/go-i18n/i18n/language"
+)
+
+// Translation is the interface that represents a translated string.
+type Translation interface {
+ // MarshalInterface returns the object that should be used
+ // to serialize the translation.
+ MarshalInterface() interface{}
+ ID() string
+ Template(language.Plural) *template
+ UntranslatedCopy() Translation
+ Normalize(language *language.Language) Translation
+ Backfill(src Translation) Translation
+ Merge(Translation) Translation
+ Incomplete(l *language.Language) bool
+}
+
+// SortableByID implements sort.Interface for a slice of translations.
+type SortableByID []Translation
+
+func (a SortableByID) Len() int { return len(a) }
+func (a SortableByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a SortableByID) Less(i, j int) bool { return a[i].ID() < a[j].ID() }
+
+// NewTranslation reflects on data to create a new Translation.
+//
+// data["id"] must be a string and data["translation"] must be either a string
+// for a non-plural translation or a map[string]interface{} for a plural translation.
+func NewTranslation(data map[string]interface{}) (Translation, error) {
+ id, ok := data["id"].(string)
+ if !ok {
+ return nil, fmt.Errorf(`missing "id" key`)
+ }
+ var pluralObject map[string]interface{}
+ switch translation := data["translation"].(type) {
+ case string:
+ tmpl, err := newTemplate(translation)
+ if err != nil {
+ return nil, err
+ }
+ return &singleTranslation{id, tmpl}, nil
+ case map[interface{}]interface{}:
+ // The YAML parser uses interface{} keys so we first convert them to string keys.
+ pluralObject = make(map[string]interface{})
+ for k, v := range translation {
+ kStr, ok := k.(string)
+ if !ok {
+ return nil, fmt.Errorf(`invalid plural category type %T; expected string`, k)
+ }
+ pluralObject[kStr] = v
+ }
+ case map[string]interface{}:
+ pluralObject = translation
+ case nil:
+ return nil, fmt.Errorf(`missing "translation" key`)
+ default:
+ return nil, fmt.Errorf(`unsupported type for "translation" key %T`, translation)
+ }
+
+ templates := make(map[language.Plural]*template, len(pluralObject))
+ for k, v := range pluralObject {
+ pc, err := language.NewPlural(k)
+ if err != nil {
+ return nil, err
+ }
+ str, ok := v.(string)
+ if !ok {
+ return nil, fmt.Errorf(`plural category "%s" has value of type %T; expected string`, pc, v)
+ }
+ tmpl, err := newTemplate(str)
+ if err != nil {
+ return nil, err
+ }
+ templates[pc] = tmpl
+ }
+ return &pluralTranslation{id, templates}, nil
+}
diff --git a/vendor/github.com/pborman/uuid/LICENSE b/vendor/github.com/pborman/uuid/LICENSE
new file mode 100644
index 00000000..5dc68268
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009,2014 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/pborman/uuid/dce.go b/vendor/github.com/pborman/uuid/dce.go
new file mode 100644
index 00000000..50a0f2d0
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/dce.go
@@ -0,0 +1,84 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+)
+
+// A Domain represents a Version 2 domain
+type Domain byte
+
+// Domain constants for DCE Security (Version 2) UUIDs.
+const (
+ Person = Domain(0)
+ Group = Domain(1)
+ Org = Domain(2)
+)
+
+// NewDCESecurity returns a DCE Security (Version 2) UUID.
+//
+// The domain should be one of Person, Group or Org.
+// On a POSIX system the id should be the users UID for the Person
+// domain and the users GID for the Group. The meaning of id for
+// the domain Org or on non-POSIX systems is site defined.
+//
+// For a given domain/id pair the same token may be returned for up to
+// 7 minutes and 10 seconds.
+func NewDCESecurity(domain Domain, id uint32) UUID {
+ uuid := NewUUID()
+ if uuid != nil {
+ uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
+ uuid[9] = byte(domain)
+ binary.BigEndian.PutUint32(uuid[0:], id)
+ }
+ return uuid
+}
+
+// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
+// domain with the id returned by os.Getuid.
+//
+// NewDCEPerson(Person, uint32(os.Getuid()))
+func NewDCEPerson() UUID {
+ return NewDCESecurity(Person, uint32(os.Getuid()))
+}
+
+// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
+// domain with the id returned by os.Getgid.
+//
+// NewDCEGroup(Group, uint32(os.Getgid()))
+func NewDCEGroup() UUID {
+ return NewDCESecurity(Group, uint32(os.Getgid()))
+}
+
+// Domain returns the domain for a Version 2 UUID or false.
+func (uuid UUID) Domain() (Domain, bool) {
+ if v, _ := uuid.Version(); v != 2 {
+ return 0, false
+ }
+ return Domain(uuid[9]), true
+}
+
+// Id returns the id for a Version 2 UUID or false.
+func (uuid UUID) Id() (uint32, bool) {
+ if v, _ := uuid.Version(); v != 2 {
+ return 0, false
+ }
+ return binary.BigEndian.Uint32(uuid[0:4]), true
+}
+
+func (d Domain) String() string {
+ switch d {
+ case Person:
+ return "Person"
+ case Group:
+ return "Group"
+ case Org:
+ return "Org"
+ }
+ return fmt.Sprintf("Domain%d", int(d))
+}
diff --git a/vendor/github.com/pborman/uuid/doc.go b/vendor/github.com/pborman/uuid/doc.go
new file mode 100644
index 00000000..d8bd013e
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/doc.go
@@ -0,0 +1,8 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The uuid package generates and inspects UUIDs.
+//
+// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
+package uuid
diff --git a/vendor/github.com/pborman/uuid/hash.go b/vendor/github.com/pborman/uuid/hash.go
new file mode 100644
index 00000000..a0420c1e
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/hash.go
@@ -0,0 +1,53 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "crypto/md5"
+ "crypto/sha1"
+ "hash"
+)
+
+// Well known Name Space IDs and UUIDs
+var (
+ NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
+ NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
+ NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
+ NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
+ NIL = Parse("00000000-0000-0000-0000-000000000000")
+)
+
+// NewHash returns a new UUID derived from the hash of space concatenated with
+// data generated by h. The hash should be at least 16 byte in length. The
+// first 16 bytes of the hash are used to form the UUID. The version of the
+// UUID will be the lower 4 bits of version. NewHash is used to implement
+// NewMD5 and NewSHA1.
+func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
+ h.Reset()
+ h.Write(space)
+ h.Write([]byte(data))
+ s := h.Sum(nil)
+ uuid := make([]byte, 16)
+ copy(uuid, s)
+ uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
+ return uuid
+}
+
+// NewMD5 returns a new MD5 (Version 3) UUID based on the
+// supplied name space and data.
+//
+// NewHash(md5.New(), space, data, 3)
+func NewMD5(space UUID, data []byte) UUID {
+ return NewHash(md5.New(), space, data, 3)
+}
+
+// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
+// supplied name space and data.
+//
+// NewHash(sha1.New(), space, data, 5)
+func NewSHA1(space UUID, data []byte) UUID {
+ return NewHash(sha1.New(), space, data, 5)
+}
diff --git a/vendor/github.com/pborman/uuid/json.go b/vendor/github.com/pborman/uuid/json.go
new file mode 100644
index 00000000..9dda1dfb
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/json.go
@@ -0,0 +1,34 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import "errors"
+
+func (u UUID) MarshalJSON() ([]byte, error) {
+ if len(u) != 16 {
+ return []byte(`""`), nil
+ }
+ var js [38]byte
+ js[0] = '"'
+ encodeHex(js[1:], u)
+ js[37] = '"'
+ return js[:], nil
+}
+
+func (u *UUID) UnmarshalJSON(data []byte) error {
+ if string(data) == `""` {
+ return nil
+ }
+ if data[0] != '"' {
+ return errors.New("invalid UUID format")
+ }
+ data = data[1 : len(data)-1]
+ uu := Parse(string(data))
+ if uu == nil {
+ return errors.New("invalid UUID format")
+ }
+ *u = uu
+ return nil
+}
diff --git a/vendor/github.com/pborman/uuid/node.go b/vendor/github.com/pborman/uuid/node.go
new file mode 100644
index 00000000..42d60da8
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/node.go
@@ -0,0 +1,117 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "net"
+ "sync"
+)
+
+var (
+ nodeMu sync.Mutex
+ interfaces []net.Interface // cached list of interfaces
+ ifname string // name of interface being used
+ nodeID []byte // hardware for version 1 UUIDs
+)
+
+// NodeInterface returns the name of the interface from which the NodeID was
+// derived. The interface "user" is returned if the NodeID was set by
+// SetNodeID.
+func NodeInterface() string {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ return ifname
+}
+
+// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
+// If name is "" then the first usable interface found will be used or a random
+// Node ID will be generated. If a named interface cannot be found then false
+// is returned.
+//
+// SetNodeInterface never fails when name is "".
+func SetNodeInterface(name string) bool {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ return setNodeInterface(name)
+}
+
+func setNodeInterface(name string) bool {
+ if interfaces == nil {
+ var err error
+ interfaces, err = net.Interfaces()
+ if err != nil && name != "" {
+ return false
+ }
+ }
+
+ for _, ifs := range interfaces {
+ if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
+ if setNodeID(ifs.HardwareAddr) {
+ ifname = ifs.Name
+ return true
+ }
+ }
+ }
+
+ // We found no interfaces with a valid hardware address. If name
+ // does not specify a specific interface generate a random Node ID
+ // (section 4.1.6)
+ if name == "" {
+ if nodeID == nil {
+ nodeID = make([]byte, 6)
+ }
+ randomBits(nodeID)
+ return true
+ }
+ return false
+}
+
+// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
+// if not already set.
+func NodeID() []byte {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ if nodeID == nil {
+ setNodeInterface("")
+ }
+ nid := make([]byte, 6)
+ copy(nid, nodeID)
+ return nid
+}
+
+// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
+// of id are used. If id is less than 6 bytes then false is returned and the
+// Node ID is not set.
+func SetNodeID(id []byte) bool {
+ defer nodeMu.Unlock()
+ nodeMu.Lock()
+ if setNodeID(id) {
+ ifname = "user"
+ return true
+ }
+ return false
+}
+
+func setNodeID(id []byte) bool {
+ if len(id) < 6 {
+ return false
+ }
+ if nodeID == nil {
+ nodeID = make([]byte, 6)
+ }
+ copy(nodeID, id)
+ return true
+}
+
+// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
+// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
+func (uuid UUID) NodeID() []byte {
+ if len(uuid) != 16 {
+ return nil
+ }
+ node := make([]byte, 6)
+ copy(node, uuid[10:])
+ return node
+}
diff --git a/vendor/github.com/pborman/uuid/sql.go b/vendor/github.com/pborman/uuid/sql.go
new file mode 100644
index 00000000..d015bfd1
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/sql.go
@@ -0,0 +1,66 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "database/sql/driver"
+ "errors"
+ "fmt"
+)
+
+// Scan implements sql.Scanner so UUIDs can be read from databases transparently
+// Currently, database types that map to string and []byte are supported. Please
+// consult database-specific driver documentation for matching types.
+func (uuid *UUID) Scan(src interface{}) error {
+ switch src.(type) {
+ case string:
+ // if an empty UUID comes from a table, we return a null UUID
+ if src.(string) == "" {
+ return nil
+ }
+
+ // see uuid.Parse for required string format
+ parsed := Parse(src.(string))
+
+ if parsed == nil {
+ return errors.New("Scan: invalid UUID format")
+ }
+
+ *uuid = parsed
+ case []byte:
+ b := src.([]byte)
+
+ // if an empty UUID comes from a table, we return a null UUID
+ if len(b) == 0 {
+ return nil
+ }
+
+ // assumes a simple slice of bytes if 16 bytes
+ // otherwise attempts to parse
+ if len(b) == 16 {
+ *uuid = UUID(b)
+ } else {
+ u := Parse(string(b))
+
+ if u == nil {
+ return errors.New("Scan: invalid UUID format")
+ }
+
+ *uuid = u
+ }
+
+ default:
+ return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
+ }
+
+ return nil
+}
+
+// Value implements sql.Valuer so that UUIDs can be written to databases
+// transparently. Currently, UUIDs map to strings. Please consult
+// database-specific driver documentation for matching types.
+func (uuid UUID) Value() (driver.Value, error) {
+ return uuid.String(), nil
+}
diff --git a/vendor/github.com/pborman/uuid/time.go b/vendor/github.com/pborman/uuid/time.go
new file mode 100644
index 00000000..eedf2421
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/time.go
@@ -0,0 +1,132 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+ "sync"
+ "time"
+)
+
+// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
+// 1582.
+type Time int64
+
+const (
+ lillian = 2299160 // Julian day of 15 Oct 1582
+ unix = 2440587 // Julian day of 1 Jan 1970
+ epoch = unix - lillian // Days between epochs
+ g1582 = epoch * 86400 // seconds between epochs
+ g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
+)
+
+var (
+ timeMu sync.Mutex
+ lasttime uint64 // last time we returned
+ clock_seq uint16 // clock sequence for this run
+
+ timeNow = time.Now // for testing
+)
+
+// UnixTime converts t the number of seconds and nanoseconds using the Unix
+// epoch of 1 Jan 1970.
+func (t Time) UnixTime() (sec, nsec int64) {
+ sec = int64(t - g1582ns100)
+ nsec = (sec % 10000000) * 100
+ sec /= 10000000
+ return sec, nsec
+}
+
+// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
+// clock sequence as well as adjusting the clock sequence as needed. An error
+// is returned if the current time cannot be determined.
+func GetTime() (Time, uint16, error) {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ return getTime()
+}
+
+func getTime() (Time, uint16, error) {
+ t := timeNow()
+
+ // If we don't have a clock sequence already, set one.
+ if clock_seq == 0 {
+ setClockSequence(-1)
+ }
+ now := uint64(t.UnixNano()/100) + g1582ns100
+
+ // If time has gone backwards with this clock sequence then we
+ // increment the clock sequence
+ if now <= lasttime {
+ clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
+ }
+ lasttime = now
+ return Time(now), clock_seq, nil
+}
+
+// ClockSequence returns the current clock sequence, generating one if not
+// already set. The clock sequence is only used for Version 1 UUIDs.
+//
+// The uuid package does not use global static storage for the clock sequence or
+// the last time a UUID was generated. Unless SetClockSequence a new random
+// clock sequence is generated the first time a clock sequence is requested by
+// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
+// for
+func ClockSequence() int {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ return clockSequence()
+}
+
+func clockSequence() int {
+ if clock_seq == 0 {
+ setClockSequence(-1)
+ }
+ return int(clock_seq & 0x3fff)
+}
+
+// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
+// -1 causes a new sequence to be generated.
+func SetClockSequence(seq int) {
+ defer timeMu.Unlock()
+ timeMu.Lock()
+ setClockSequence(seq)
+}
+
+func setClockSequence(seq int) {
+ if seq == -1 {
+ var b [2]byte
+ randomBits(b[:]) // clock sequence
+ seq = int(b[0])<<8 | int(b[1])
+ }
+ old_seq := clock_seq
+ clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
+ if old_seq != clock_seq {
+ lasttime = 0
+ }
+}
+
+// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
+// uuid. It returns false if uuid is not valid. The time is only well defined
+// for version 1 and 2 UUIDs.
+func (uuid UUID) Time() (Time, bool) {
+ if len(uuid) != 16 {
+ return 0, false
+ }
+ time := int64(binary.BigEndian.Uint32(uuid[0:4]))
+ time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
+ time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
+ return Time(time), true
+}
+
+// ClockSequence returns the clock sequence encoded in uuid. It returns false
+// if uuid is not valid. The clock sequence is only well defined for version 1
+// and 2 UUIDs.
+func (uuid UUID) ClockSequence() (int, bool) {
+ if len(uuid) != 16 {
+ return 0, false
+ }
+ return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
+}
diff --git a/vendor/github.com/pborman/uuid/util.go b/vendor/github.com/pborman/uuid/util.go
new file mode 100644
index 00000000..fc8e052c
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/util.go
@@ -0,0 +1,43 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "io"
+)
+
+// randomBits completely fills slice b with random data.
+func randomBits(b []byte) {
+ if _, err := io.ReadFull(rander, b); err != nil {
+ panic(err.Error()) // rand should never fail
+ }
+}
+
+// xvalues returns the value of a byte as a hexadecimal digit or 255.
+var xvalues = [256]byte{
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
+ 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+}
+
+// xtob converts the the first two hex bytes of x into a byte.
+func xtob(x string) (byte, bool) {
+ b1 := xvalues[x[0]]
+ b2 := xvalues[x[1]]
+ return (b1 << 4) | b2, b1 != 255 && b2 != 255
+}
diff --git a/vendor/github.com/pborman/uuid/uuid.go b/vendor/github.com/pborman/uuid/uuid.go
new file mode 100644
index 00000000..82c9e7ee
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/uuid.go
@@ -0,0 +1,201 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// Array is a pass-by-value UUID that can be used as an effecient key in a map.
+type Array [16]byte
+
+// UUID converts uuid into a slice.
+func (uuid Array) UUID() UUID {
+ return uuid[:]
+}
+
+// String returns the string representation of uuid,
+// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+func (uuid Array) String() string {
+ return uuid.UUID().String()
+}
+
+// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
+// 4122.
+type UUID []byte
+
+// A Version represents a UUIDs version.
+type Version byte
+
+// A Variant represents a UUIDs variant.
+type Variant byte
+
+// Constants returned by Variant.
+const (
+ Invalid = Variant(iota) // Invalid UUID
+ RFC4122 // The variant specified in RFC4122
+ Reserved // Reserved, NCS backward compatibility.
+ Microsoft // Reserved, Microsoft Corporation backward compatibility.
+ Future // Reserved for future definition.
+)
+
+var rander = rand.Reader // random function
+
+// New returns a new random (version 4) UUID as a string. It is a convenience
+// function for NewRandom().String().
+func New() string {
+ return NewRandom().String()
+}
+
+// Parse decodes s into a UUID or returns nil. Both the UUID form of
+// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
+func Parse(s string) UUID {
+ if len(s) == 36+9 {
+ if strings.ToLower(s[:9]) != "urn:uuid:" {
+ return nil
+ }
+ s = s[9:]
+ } else if len(s) != 36 {
+ return nil
+ }
+ if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
+ return nil
+ }
+ var uuid [16]byte
+ for i, x := range [16]int{
+ 0, 2, 4, 6,
+ 9, 11,
+ 14, 16,
+ 19, 21,
+ 24, 26, 28, 30, 32, 34} {
+ if v, ok := xtob(s[x:]); !ok {
+ return nil
+ } else {
+ uuid[i] = v
+ }
+ }
+ return uuid[:]
+}
+
+// Equal returns true if uuid1 and uuid2 are equal.
+func Equal(uuid1, uuid2 UUID) bool {
+ return bytes.Equal(uuid1, uuid2)
+}
+
+// Array returns an array representation of uuid that can be used as a map key.
+// Array panics if uuid is not valid.
+func (uuid UUID) Array() Array {
+ if len(uuid) != 16 {
+ panic("invalid uuid")
+ }
+ var a Array
+ copy(a[:], uuid)
+ return a
+}
+
+// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+// , or "" if uuid is invalid.
+func (uuid UUID) String() string {
+ if len(uuid) != 16 {
+ return ""
+ }
+ var buf [36]byte
+ encodeHex(buf[:], uuid)
+ return string(buf[:])
+}
+
+// URN returns the RFC 2141 URN form of uuid,
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
+func (uuid UUID) URN() string {
+ if len(uuid) != 16 {
+ return ""
+ }
+ var buf [36 + 9]byte
+ copy(buf[:], "urn:uuid:")
+ encodeHex(buf[9:], uuid)
+ return string(buf[:])
+}
+
+func encodeHex(dst []byte, uuid UUID) {
+ hex.Encode(dst[:], uuid[:4])
+ dst[8] = '-'
+ hex.Encode(dst[9:13], uuid[4:6])
+ dst[13] = '-'
+ hex.Encode(dst[14:18], uuid[6:8])
+ dst[18] = '-'
+ hex.Encode(dst[19:23], uuid[8:10])
+ dst[23] = '-'
+ hex.Encode(dst[24:], uuid[10:])
+}
+
+// Variant returns the variant encoded in uuid. It returns Invalid if
+// uuid is invalid.
+func (uuid UUID) Variant() Variant {
+ if len(uuid) != 16 {
+ return Invalid
+ }
+ switch {
+ case (uuid[8] & 0xc0) == 0x80:
+ return RFC4122
+ case (uuid[8] & 0xe0) == 0xc0:
+ return Microsoft
+ case (uuid[8] & 0xe0) == 0xe0:
+ return Future
+ default:
+ return Reserved
+ }
+}
+
+// Version returns the version of uuid. It returns false if uuid is not
+// valid.
+func (uuid UUID) Version() (Version, bool) {
+ if len(uuid) != 16 {
+ return 0, false
+ }
+ return Version(uuid[6] >> 4), true
+}
+
+func (v Version) String() string {
+ if v > 15 {
+ return fmt.Sprintf("BAD_VERSION_%d", v)
+ }
+ return fmt.Sprintf("VERSION_%d", v)
+}
+
+func (v Variant) String() string {
+ switch v {
+ case RFC4122:
+ return "RFC4122"
+ case Reserved:
+ return "Reserved"
+ case Microsoft:
+ return "Microsoft"
+ case Future:
+ return "Future"
+ case Invalid:
+ return "Invalid"
+ }
+ return fmt.Sprintf("BadVariant%d", int(v))
+}
+
+// SetRand sets the random number generator to r, which implents io.Reader.
+// If r.Read returns an error when the package requests random data then
+// a panic will be issued.
+//
+// Calling SetRand with nil sets the random number generator to the default
+// generator.
+func SetRand(r io.Reader) {
+ if r == nil {
+ rander = rand.Reader
+ return
+ }
+ rander = r
+}
diff --git a/vendor/github.com/pborman/uuid/version1.go b/vendor/github.com/pborman/uuid/version1.go
new file mode 100644
index 00000000..0127eacf
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/version1.go
@@ -0,0 +1,41 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+ "encoding/binary"
+)
+
+// NewUUID returns a Version 1 UUID based on the current NodeID and clock
+// sequence, and the current time. If the NodeID has not been set by SetNodeID
+// or SetNodeInterface then it will be set automatically. If the NodeID cannot
+// be set NewUUID returns nil. If clock sequence has not been set by
+// SetClockSequence then it will be set automatically. If GetTime fails to
+// return the current NewUUID returns nil.
+func NewUUID() UUID {
+ if nodeID == nil {
+ SetNodeInterface("")
+ }
+
+ now, seq, err := GetTime()
+ if err != nil {
+ return nil
+ }
+
+ uuid := make([]byte, 16)
+
+ time_low := uint32(now & 0xffffffff)
+ time_mid := uint16((now >> 32) & 0xffff)
+ time_hi := uint16((now >> 48) & 0x0fff)
+ time_hi |= 0x1000 // Version 1
+
+ binary.BigEndian.PutUint32(uuid[0:], time_low)
+ binary.BigEndian.PutUint16(uuid[4:], time_mid)
+ binary.BigEndian.PutUint16(uuid[6:], time_hi)
+ binary.BigEndian.PutUint16(uuid[8:], seq)
+ copy(uuid[10:], nodeID)
+
+ return uuid
+}
diff --git a/vendor/github.com/pborman/uuid/version4.go b/vendor/github.com/pborman/uuid/version4.go
new file mode 100644
index 00000000..b3d4a368
--- /dev/null
+++ b/vendor/github.com/pborman/uuid/version4.go
@@ -0,0 +1,25 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+// Random returns a Random (Version 4) UUID or panics.
+//
+// The strength of the UUIDs is based on the strength of the crypto/rand
+// package.
+//
+// A note about uniqueness derived from from the UUID Wikipedia entry:
+//
+// Randomly generated UUIDs have 122 random bits. One's annual risk of being
+// hit by a meteorite is estimated to be one chance in 17 billion, that
+// means the probability is about 0.00000000006 (6 × 10−11),
+// equivalent to the odds of creating a few tens of trillions of UUIDs in a
+// year and having one duplicate.
+func NewRandom() UUID {
+ uuid := make([]byte, 16)
+ randomBits([]byte(uuid))
+ uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
+ uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
+ return uuid
+}
diff --git a/vendor/github.com/peterhellberg/giphy/client.go b/vendor/github.com/peterhellberg/giphy/client.go
new file mode 100644
index 00000000..09c005c2
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/client.go
@@ -0,0 +1,117 @@
+package giphy
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+)
+
+// DefaultClient is the default Giphy API client
+var DefaultClient = NewClient()
+
+// PublicBetaKey is the public beta key for the Giphy API
+var PublicBetaKey = "dc6zaTOxFJmzC"
+
+// A Client communicates with the Giphy API.
+type Client struct {
+ // APIKey is the key used for requests to the Giphy API
+ APIKey string
+
+ // Limit is the limit used for requests to the Giphy API
+ Limit int
+
+ // Rating is the rating used for requests to the Giphy API
+ Rating string
+
+ // BaseURL is the base url for Giphy API.
+ BaseURL *url.URL
+
+ // BasePath is the base path for the gifs endpoints
+ BasePath string
+
+ // User agent used for HTTP requests to Giphy API.
+ UserAgent string
+
+ // HTTP client used to communicate with the Giphy API.
+ httpClient *http.Client
+}
+
+// NewClient returns a new Giphy API client.
+// If no *http.Client were provided then http.DefaultClient is used.
+func NewClient(httpClients ...*http.Client) *Client {
+ var httpClient *http.Client
+
+ if len(httpClients) > 0 && httpClients[0] != nil {
+ httpClient = httpClients[0]
+ } else {
+ cloned := *http.DefaultClient
+ httpClient = &cloned
+ }
+
+ c := &Client{
+ APIKey: Env("GIPHY_API_KEY", PublicBetaKey),
+ Rating: Env("GIPHY_RATING", "g"),
+ Limit: EnvInt("GIPHY_LIMIT", 10),
+ BaseURL: &url.URL{
+ Scheme: Env("GIPHY_BASE_URL_SCHEME", "https"),
+ Host: Env("GIPHY_BASE_URL_HOST", "api.giphy.com"),
+ },
+ BasePath: Env("GIPHY_BASE_PATH", "/v1"),
+ UserAgent: Env("GIPHY_USER_AGENT", "giphy.go"),
+ httpClient: httpClient,
+ }
+
+ return c
+}
+
+// NewRequest creates an API request.
+func (c *Client) NewRequest(s string) (*http.Request, error) {
+ rel, err := url.Parse(c.BasePath + s)
+ if err != nil {
+ return nil, err
+ }
+
+ q := rel.Query()
+ q.Set("api_key", c.APIKey)
+ q.Set("rating", c.Rating)
+ rel.RawQuery = q.Encode()
+
+ u := c.BaseURL.ResolveReference(rel)
+
+ if EnvBool("GIPHY_VERBOSE", false) {
+ fmt.Println("giphy: GET", u.String())
+ }
+
+ req, err := http.NewRequest("GET", u.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Add("User-Agent", c.UserAgent)
+ return req, nil
+}
+
+// Do sends an API request and returns the API response. The API response is
+// decoded and stored in the value pointed to by v, or returned as an error if
+// an API error has occurred.
+func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
+ // Make sure to close the connection after replying to this request
+ req.Close = true
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return resp, err
+ }
+ defer resp.Body.Close()
+
+ if v != nil {
+ err = json.NewDecoder(resp.Body).Decode(v)
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("error reading response from %s %s: %s", req.Method, req.URL.RequestURI(), err)
+ }
+
+ return resp, nil
+}
diff --git a/vendor/github.com/peterhellberg/giphy/cmd/giphy/main.go b/vendor/github.com/peterhellberg/giphy/cmd/giphy/main.go
new file mode 100644
index 00000000..b621f092
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/cmd/giphy/main.go
@@ -0,0 +1,140 @@
+/*
+
+A command line client for the Giphy API
+
+Installation
+
+Just go get the command:
+
+ go get -u github.com/peterhellberg/giphy/cmd/giphy
+
+Configuration
+
+The command line client can be used straight out of the box, but
+there are also a few environment variables that you can use in order
+to override the default configuration.
+
+ Environment variable | Default value
+ ----------------------|--------------
+ GIPHY_API_KEY | dc6zaTOxFJmzC
+ GIPHY_RATING | g
+ GIPHY_LIMIT | 10
+ GIPHY_BASE_URL_SCHEME | http
+ GIPHY_BASE_URL_HOST | api.giphy.com
+ GIPHY_BASE_PATH | /v1
+ GIPHY_USER_AGENT | giphy.go
+
+Usage
+
+The command line client consists of a few sub commands.
+
+ Commands:
+ search, s [args]
+ gif, id [args]
+ random, rand, r [args]
+ translate, trans, t [args]
+ trending, trend, tr [args]
+
+*/
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/peterhellberg/giphy"
+)
+
+func main() {
+ g := giphy.DefaultClient
+
+ if len(os.Args) < 2 {
+ fmt.Println(strings.Join([]string{
+ "Commands:",
+ "search, s [args]",
+ "gif, id [args]",
+ "random, rand, r [args]",
+ "translate, trans, t [args]",
+ "trending, trend, tr [args]",
+ }, "\n\t"))
+
+ return
+ }
+
+ args := os.Args[1:]
+
+ switch args[0] {
+ default:
+ search(g, args)
+ case "search", "s":
+ search(g, args[1:])
+ case "gif", "id":
+ gif(g, args[1:])
+ case "random", "rand", "r":
+ random(g, args[1:])
+ case "translate", "trans", "t":
+ translate(g, args[1:])
+ case "trending", "trend", "tr":
+ trending(g, args[1:])
+ }
+}
+
+func search(c *giphy.Client, args []string) {
+ res, err := c.Search(args)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ for _, d := range res.Data {
+ fmt.Println(d.Images.Original.URL)
+ }
+}
+
+func gif(c *giphy.Client, args []string) {
+ if len(args) == 0 {
+ fmt.Println("missing Giphy id")
+ os.Exit(1)
+ }
+
+ res, err := c.GIF(args[0])
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ fmt.Println(res.Data.Images.Original.URL)
+}
+
+func random(c *giphy.Client, args []string) {
+ res, err := c.Random(args)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ fmt.Println(res.Data.ImageOriginalURL)
+}
+
+func translate(c *giphy.Client, args []string) {
+ res, err := c.Translate(args)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ fmt.Println(res.Data.Images.Original.URL)
+}
+
+func trending(c *giphy.Client, args []string) {
+ res, err := c.Trending(args)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ for _, d := range res.Data {
+ fmt.Println(d.Images.Original.URL)
+ }
+}
diff --git a/vendor/github.com/peterhellberg/giphy/env.go b/vendor/github.com/peterhellberg/giphy/env.go
new file mode 100644
index 00000000..b41944ce
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/env.go
@@ -0,0 +1,34 @@
+package giphy
+
+import (
+ "os"
+ "strconv"
+)
+
+// Env returns a string from the ENV, or fallback variable
+func Env(key, fallback string) string {
+ v := os.Getenv(key)
+ if v != "" {
+ return v
+ }
+
+ return fallback
+}
+
+// EnvBool returns a bool from the ENV, or fallback variable
+func EnvBool(key string, fallback bool) bool {
+ if b, err := strconv.ParseBool(os.Getenv(key)); err == nil {
+ return b
+ }
+
+ return fallback
+}
+
+// EnvInt returns an int from the ENV, or fallback variable
+func EnvInt(key string, fallback int) int {
+ if i, err := strconv.Atoi(os.Getenv(key)); err == nil {
+ return i
+ }
+
+ return fallback
+}
diff --git a/vendor/github.com/peterhellberg/giphy/errors.go b/vendor/github.com/peterhellberg/giphy/errors.go
new file mode 100644
index 00000000..a98bc2f8
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/errors.go
@@ -0,0 +1,17 @@
+package giphy
+
+import "errors"
+
+var (
+ // ErrNoImageFound is the error returned when no image was found
+ ErrNoImageFound = errors.New("no image found")
+
+ // ErrUnknown is used for unknown errors from the Giphy API
+ ErrUnknown = errors.New("unknown error")
+
+ // ErrNoTrendingImagesFound is returned when no trending images were found
+ ErrNoTrendingImagesFound = errors.New("no trending images found")
+
+ // ErrNoRawData is returned if there was no data property in response
+ ErrNoRawData = errors.New("no raw data")
+)
diff --git a/vendor/github.com/peterhellberg/giphy/gif.go b/vendor/github.com/peterhellberg/giphy/gif.go
new file mode 100644
index 00000000..8c317c97
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/gif.go
@@ -0,0 +1,44 @@
+package giphy
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+// GIF returns a ID response from the Giphy API
+func (c *Client) GIF(id string) (GIF, error) {
+ if strings.ContainsAny(id, "/&?") {
+ return GIF{}, fmt.Errorf("Invalid giphy id: `%v`", id)
+ }
+
+ req, err := c.NewRequest("/gifs/" + id)
+ if err != nil {
+ return GIF{}, err
+ }
+
+ var gif GIF
+ if _, err = c.Do(req, &gif); err != nil {
+ return GIF{}, err
+ }
+
+ if gif.RawData == nil || gif.RawData[0] == '[' {
+ return GIF{}, ErrNoImageFound
+ }
+
+ // Check if the first character in Data is a {
+ if gif.RawData[0] == '{' {
+ var d Data
+
+ err = json.Unmarshal(gif.RawData, &d)
+ if err != nil {
+ return GIF{}, err
+ }
+
+ gif.Data = d
+
+ return gif, nil
+ }
+
+ return GIF{}, ErrUnknown
+}
diff --git a/vendor/github.com/peterhellberg/giphy/random.go b/vendor/github.com/peterhellberg/giphy/random.go
new file mode 100644
index 00000000..1374ed24
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/random.go
@@ -0,0 +1,37 @@
+package giphy
+
+import (
+ "encoding/json"
+ "strings"
+)
+
+// Random returns a random response from the Giphy API
+func (c *Client) Random(args []string) (Random, error) {
+ argsStr := strings.Join(args, " ")
+
+ req, err := c.NewRequest("/gifs/random?tag=" + argsStr)
+ if err != nil {
+ return Random{}, err
+ }
+
+ var random Random
+ if _, err = c.Do(req, &random); err != nil {
+ return Random{}, err
+ }
+
+ // Check if the first character in Data is a [
+ if random.RawData == nil || random.RawData[0] == '[' {
+ return Random{}, ErrNoImageFound
+ }
+
+ var d RandomData
+
+ err = json.Unmarshal(random.RawData, &d)
+ if err != nil {
+ return Random{}, err
+ }
+
+ random.Data = d
+
+ return random, nil
+}
diff --git a/vendor/github.com/peterhellberg/giphy/search.go b/vendor/github.com/peterhellberg/giphy/search.go
new file mode 100644
index 00000000..ca5af117
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/search.go
@@ -0,0 +1,24 @@
+package giphy
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Search returns a search response from the Giphy API
+func (c *Client) Search(args []string) (Search, error) {
+ argsStr := strings.Join(args, " ")
+
+ path := fmt.Sprintf("/gifs/search?limit=%v&q=%s", c.Limit, argsStr)
+ req, err := c.NewRequest(path)
+ if err != nil {
+ return Search{}, err
+ }
+
+ var search Search
+ if _, err = c.Do(req, &search); err != nil {
+ return Search{}, err
+ }
+
+ return search, nil
+}
diff --git a/vendor/github.com/peterhellberg/giphy/translate.go b/vendor/github.com/peterhellberg/giphy/translate.go
new file mode 100644
index 00000000..e3454b28
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/translate.go
@@ -0,0 +1,37 @@
+package giphy
+
+import (
+ "encoding/json"
+ "strings"
+)
+
+// Translate returns a translate response from the Giphy API
+func (c *Client) Translate(args []string) (Translate, error) {
+ argsStr := strings.Join(args, " ")
+
+ req, err := c.NewRequest("/gifs/translate?s=" + argsStr)
+ if err != nil {
+ return Translate{}, err
+ }
+
+ var translate Translate
+ if _, err = c.Do(req, &translate); err != nil {
+ return Translate{}, err
+ }
+
+ if len(translate.RawData) == 0 {
+ return Translate{}, ErrNoRawData
+ }
+
+ // Check if the first character in Data is a [
+ if translate.RawData[0] == '[' {
+ return Translate{}, ErrNoImageFound
+ }
+
+ err = json.Unmarshal(translate.RawData, &translate.Data)
+ if err != nil {
+ return Translate{}, err
+ }
+
+ return translate, nil
+}
diff --git a/vendor/github.com/peterhellberg/giphy/trending.go b/vendor/github.com/peterhellberg/giphy/trending.go
new file mode 100644
index 00000000..671e0d51
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/trending.go
@@ -0,0 +1,23 @@
+package giphy
+
+import "fmt"
+
+// Trending returns a trending response from the Giphy API
+func (c *Client) Trending(args ...[]string) (Trending, error) {
+ path := fmt.Sprintf("/gifs/trending?limit=%v", c.Limit)
+ req, err := c.NewRequest(path)
+ if err != nil {
+ return Trending{}, err
+ }
+
+ var res Trending
+ if _, err = c.Do(req, &res); err != nil {
+ return res, err
+ }
+
+ if len(res.Data) == 0 {
+ return res, ErrNoTrendingImagesFound
+ }
+
+ return res, nil
+}
diff --git a/vendor/github.com/peterhellberg/giphy/types.go b/vendor/github.com/peterhellberg/giphy/types.go
new file mode 100644
index 00000000..e7a7ebfc
--- /dev/null
+++ b/vendor/github.com/peterhellberg/giphy/types.go
@@ -0,0 +1,116 @@
+package giphy
+
+import "encoding/json"
+
+// Search represents a search response from the Giphy API
+type Search struct {
+ Data []Data `json:"data"`
+ Meta Meta `json:"meta"`
+ Pagination Pagination `json:"pagination"`
+}
+
+// GIF represents a ID response from the Giphy API
+type GIF struct {
+ Data Data
+ RawData json.RawMessage `json:"data"`
+ Meta Meta `json:"meta"`
+}
+
+// Random represents a random response from the Giphy API
+type Random struct {
+ Data RandomData
+ RawData json.RawMessage `json:"data"`
+ Meta Meta `json:"meta"`
+}
+
+// Translate represents a translate response from the Giphy API
+type Translate struct {
+ Data
+ RawData json.RawMessage `json:"data"`
+ Meta Meta `json:"meta"`
+}
+
+// Trending represents a trending response from the Giphy API
+type Trending struct {
+ Data []Data `json:"data"`
+ Meta Meta `json:"meta"`
+ Pagination Pagination `json:"pagination"`
+}
+
+// Data contains all the fields in a data response from the Giphy API
+type Data struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+ URL string `json:"url"`
+ BitlyGifURL string `json:"bitly_gif_url"`
+ BitlyURL string `json:"bitly_url"`
+ EmbedURL string `json:"embed_url"`
+ Username string `json:"username"`
+ Source string `json:"source"`
+ Rating string `json:"rating"`
+ Caption string `json:"caption"`
+ ContentURL string `json:"content_url"`
+ ImportDatetime string `json:"import_datetime"`
+ TrendingDatetime string `json:"trending_datetime"`
+ Images Images `json:"images"`
+}
+
+// RandomData represents data section in random response from the Giphy API
+type RandomData struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+ URL string `json:"url"`
+ ImageOriginalURL string `json:"image_original_url"`
+ ImageURL string `json:"image_url"`
+ ImageMp4URL string `json:"image_mp4_url"`
+ ImageFrames string `json:"image_frames"`
+ ImageWidth string `json:"image_width"`
+ ImageHeight string `json:"image_height"`
+ FixedHeightDownsampledURL string `json:"fixed_height_downsampled_url"`
+ FixedHeightDownsampledWidth string `json:"fixed_height_downsampled_width"`
+ FixedHeightDownsampledHeight string `json:"fixed_height_downsampled_height"`
+ FixedWidthDownsampledURL string `json:"fixed_width_downsampled_url"`
+ FixedWidthDownsampledWidth string `json:"fixed_width_downsampled_width"`
+ FixedWidthDownsampledHeight string `json:"fixed_width_downsampled_height"`
+ Rating string `json:"rating"`
+ Username string `json:"username"`
+ Caption string `json:"caption"`
+ Tags []string `json:"tags"`
+}
+
+// Images represents all the different types of images
+type Images struct {
+ FixedHeight Image `json:"fixed_height"`
+ FixedHeightStill Image `json:"fixed_height_still"`
+ FixedHeightDownsampled Image `json:"fixed_height_downsampled"`
+ FixedWidth Image `json:"fixed_width"`
+ FixedWidthStill Image `json:"fixed_width_still"`
+ FixedWidthDownsampled Image `json:"fixed_width_downsampled"`
+ Downsized Image `json:"downsized"`
+ DownsizedStill Image `json:"downsized_still"`
+ Original Image `json:"original"`
+ OriginalStill Image `json:"original_still"`
+}
+
+// Image represents an image
+type Image struct {
+ URL string `json:"url"`
+ Width string `json:"width"`
+ Height string `json:"height"`
+ Size string `json:"size,omitempty"`
+ Frames string `json:"frames,omitempty"`
+ Mp4 string `json:"mp4,omitempty"`
+}
+
+// Pagination represents the pagination section in a Giphy API response
+type Pagination struct {
+ TotalCount int `json:"total_count"`
+ Count int `json:"count"`
+ Offset int `json:"offset"`
+}
+
+// Meta represents the meta section in a Giphy API response
+type Meta struct {
+ Status int `json:"status"`
+ Msg string `json:"msg"`
+}
diff --git a/vendor/github.com/sorcix/irc/LICENSE b/vendor/github.com/sorcix/irc/LICENSE
new file mode 100644
index 00000000..10cecc4c
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/LICENSE
@@ -0,0 +1,22 @@
+Copyright 2014 Vic Demuzere
+
+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.
diff --git a/vendor/github.com/sorcix/irc/constants.go b/vendor/github.com/sorcix/irc/constants.go
new file mode 100644
index 00000000..d4812ba3
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/constants.go
@@ -0,0 +1,298 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+package irc
+
+// Various prefixes extracted from RFC1459.
+const (
+ Channel = '#' // Normal channel
+ Distributed = '&' // Distributed channel
+
+ Owner = '~' // Channel owner +q (non-standard)
+ Admin = '&' // Channel admin +a (non-standard)
+ Operator = '@' // Channel operator +o
+ HalfOperator = '%' // Channel half operator +h (non-standard)
+ Voice = '+' // User has voice +v
+)
+
+// User modes as defined by RFC1459 section 4.2.3.2.
+const (
+ UserModeInvisible = 'i' // User is invisible
+ UserModeServerNotices = 's' // User wants to receive server notices
+ UserModeWallops = 'w' // User wants to receive Wallops
+ UserModeOperator = 'o' // Server operator
+)
+
+// Channel modes as defined by RFC1459 section 4.2.3.1
+const (
+ ModeOperator = 'o' // Operator privileges
+ ModeVoice = 'v' // Ability to speak on a moderated channel
+ ModePrivate = 'p' // Private channel
+ ModeSecret = 's' // Secret channel
+ ModeInviteOnly = 'i' // Users can't join without invite
+ ModeTopic = 't' // Topic can only be set by an operator
+ ModeModerated = 'm' // Only voiced users and operators can talk
+ ModeLimit = 'l' // User limit
+ ModeKey = 'k' // Channel password
+
+ ModeOwner = 'q' // Owner privileges (non-standard)
+ ModeAdmin = 'a' // Admin privileges (non-standard)
+ ModeHalfOperator = 'h' // Half-operator privileges (non-standard)
+)
+
+// IRC commands extracted from RFC2812 section 3 and RFC2813 section 4.
+const (
+ PASS = "PASS"
+ NICK = "NICK"
+ USER = "USER"
+ OPER = "OPER"
+ MODE = "MODE"
+ SERVICE = "SERVICE"
+ QUIT = "QUIT"
+ SQUIT = "SQUIT"
+ JOIN = "JOIN"
+ PART = "PART"
+ TOPIC = "TOPIC"
+ NAMES = "NAMES"
+ LIST = "LIST"
+ INVITE = "INVITE"
+ KICK = "KICK"
+ PRIVMSG = "PRIVMSG"
+ NOTICE = "NOTICE"
+ MOTD = "MOTD"
+ LUSERS = "LUSERS"
+ VERSION = "VERSION"
+ STATS = "STATS"
+ LINKS = "LINKS"
+ TIME = "TIME"
+ CONNECT = "CONNECT"
+ TRACE = "TRACE"
+ ADMIN = "ADMIN"
+ INFO = "INFO"
+ SERVLIST = "SERVLIST"
+ SQUERY = "SQUERY"
+ WHO = "WHO"
+ WHOIS = "WHOIS"
+ WHOWAS = "WHOWAS"
+ KILL = "KILL"
+ PING = "PING"
+ PONG = "PONG"
+ ERROR = "ERROR"
+ AWAY = "AWAY"
+ REHASH = "REHASH"
+ DIE = "DIE"
+ RESTART = "RESTART"
+ SUMMON = "SUMMON"
+ USERS = "USERS"
+ WALLOPS = "WALLOPS"
+ USERHOST = "USERHOST"
+ ISON = "ISON"
+ SERVER = "SERVER"
+ NJOIN = "NJOIN"
+)
+
+// Numeric IRC replies extracted from RFC2812 section 5.
+const (
+ RPL_WELCOME = "001"
+ RPL_YOURHOST = "002"
+ RPL_CREATED = "003"
+ RPL_MYINFO = "004"
+ RPL_BOUNCE = "005"
+ RPL_ISUPPORT = "005"
+ RPL_USERHOST = "302"
+ RPL_ISON = "303"
+ RPL_AWAY = "301"
+ RPL_UNAWAY = "305"
+ RPL_NOWAWAY = "306"
+ RPL_WHOISUSER = "311"
+ RPL_WHOISSERVER = "312"
+ RPL_WHOISOPERATOR = "313"
+ RPL_WHOISIDLE = "317"
+ RPL_ENDOFWHOIS = "318"
+ RPL_WHOISCHANNELS = "319"
+ RPL_WHOWASUSER = "314"
+ RPL_ENDOFWHOWAS = "369"
+ RPL_LISTSTART = "321"
+ RPL_LIST = "322"
+ RPL_LISTEND = "323"
+ RPL_UNIQOPIS = "325"
+ RPL_CHANNELMODEIS = "324"
+ RPL_NOTOPIC = "331"
+ RPL_TOPIC = "332"
+ RPL_INVITING = "341"
+ RPL_SUMMONING = "342"
+ RPL_INVITELIST = "346"
+ RPL_ENDOFINVITELIST = "347"
+ RPL_EXCEPTLIST = "348"
+ RPL_ENDOFEXCEPTLIST = "349"
+ RPL_VERSION = "351"
+ RPL_WHOREPLY = "352"
+ RPL_ENDOFWHO = "315"
+ RPL_NAMREPLY = "353"
+ RPL_ENDOFNAMES = "366"
+ RPL_LINKS = "364"
+ RPL_ENDOFLINKS = "365"
+ RPL_BANLIST = "367"
+ RPL_ENDOFBANLIST = "368"
+ RPL_INFO = "371"
+ RPL_ENDOFINFO = "374"
+ RPL_MOTDSTART = "375"
+ RPL_MOTD = "372"
+ RPL_ENDOFMOTD = "376"
+ RPL_YOUREOPER = "381"
+ RPL_REHASHING = "382"
+ RPL_YOURESERVICE = "383"
+ RPL_TIME = "391"
+ RPL_USERSSTART = "392"
+ RPL_USERS = "393"
+ RPL_ENDOFUSERS = "394"
+ RPL_NOUSERS = "395"
+ RPL_TRACELINK = "200"
+ RPL_TRACECONNECTING = "201"
+ RPL_TRACEHANDSHAKE = "202"
+ RPL_TRACEUNKNOWN = "203"
+ RPL_TRACEOPERATOR = "204"
+ RPL_TRACEUSER = "205"
+ RPL_TRACESERVER = "206"
+ RPL_TRACESERVICE = "207"
+ RPL_TRACENEWTYPE = "208"
+ RPL_TRACECLASS = "209"
+ RPL_TRACERECONNECT = "210"
+ RPL_TRACELOG = "261"
+ RPL_TRACEEND = "262"
+ RPL_STATSLINKINFO = "211"
+ RPL_STATSCOMMANDS = "212"
+ RPL_ENDOFSTATS = "219"
+ RPL_STATSUPTIME = "242"
+ RPL_STATSOLINE = "243"
+ RPL_UMODEIS = "221"
+ RPL_SERVLIST = "234"
+ RPL_SERVLISTEND = "235"
+ RPL_LUSERCLIENT = "251"
+ RPL_LUSEROP = "252"
+ RPL_LUSERUNKNOWN = "253"
+ RPL_LUSERCHANNELS = "254"
+ RPL_LUSERME = "255"
+ RPL_ADMINME = "256"
+ RPL_ADMINLOC1 = "257"
+ RPL_ADMINLOC2 = "258"
+ RPL_ADMINEMAIL = "259"
+ RPL_TRYAGAIN = "263"
+ ERR_NOSUCHNICK = "401"
+ ERR_NOSUCHSERVER = "402"
+ ERR_NOSUCHCHANNEL = "403"
+ ERR_CANNOTSENDTOCHAN = "404"
+ ERR_TOOMANYCHANNELS = "405"
+ ERR_WASNOSUCHNICK = "406"
+ ERR_TOOMANYTARGETS = "407"
+ ERR_NOSUCHSERVICE = "408"
+ ERR_NOORIGIN = "409"
+ ERR_NORECIPIENT = "411"
+ ERR_NOTEXTTOSEND = "412"
+ ERR_NOTOPLEVEL = "413"
+ ERR_WILDTOPLEVEL = "414"
+ ERR_BADMASK = "415"
+ ERR_UNKNOWNCOMMAND = "421"
+ ERR_NOMOTD = "422"
+ ERR_NOADMININFO = "423"
+ ERR_FILEERROR = "424"
+ ERR_NONICKNAMEGIVEN = "431"
+ ERR_ERRONEUSNICKNAME = "432"
+ ERR_NICKNAMEINUSE = "433"
+ ERR_NICKCOLLISION = "436"
+ ERR_UNAVAILRESOURCE = "437"
+ ERR_USERNOTINCHANNEL = "441"
+ ERR_NOTONCHANNEL = "442"
+ ERR_USERONCHANNEL = "443"
+ ERR_NOLOGIN = "444"
+ ERR_SUMMONDISABLED = "445"
+ ERR_USERSDISABLED = "446"
+ ERR_NOTREGISTERED = "451"
+ ERR_NEEDMOREPARAMS = "461"
+ ERR_ALREADYREGISTRED = "462"
+ ERR_NOPERMFORHOST = "463"
+ ERR_PASSWDMISMATCH = "464"
+ ERR_YOUREBANNEDCREEP = "465"
+ ERR_YOUWILLBEBANNED = "466"
+ ERR_KEYSET = "467"
+ ERR_CHANNELISFULL = "471"
+ ERR_UNKNOWNMODE = "472"
+ ERR_INVITEONLYCHAN = "473"
+ ERR_BANNEDFROMCHAN = "474"
+ ERR_BADCHANNELKEY = "475"
+ ERR_BADCHANMASK = "476"
+ ERR_NOCHANMODES = "477"
+ ERR_BANLISTFULL = "478"
+ ERR_NOPRIVILEGES = "481"
+ ERR_CHANOPRIVSNEEDED = "482"
+ ERR_CANTKILLSERVER = "483"
+ ERR_RESTRICTED = "484"
+ ERR_UNIQOPPRIVSNEEDED = "485"
+ ERR_NOOPERHOST = "491"
+ ERR_UMODEUNKNOWNFLAG = "501"
+ ERR_USERSDONTMATCH = "502"
+)
+
+// IRC commands extracted from the IRCv3 spec at http://www.ircv3.org/.
+const (
+ CAP = "CAP"
+ CAP_LS = "LS" // Subcommand (param)
+ CAP_LIST = "LIST" // Subcommand (param)
+ CAP_REQ = "REQ" // Subcommand (param)
+ CAP_ACK = "ACK" // Subcommand (param)
+ CAP_NAK = "NAK" // Subcommand (param)
+ CAP_CLEAR = "CLEAR" // Subcommand (param)
+ CAP_END = "END" // Subcommand (param)
+
+ AUTHENTICATE = "AUTHENTICATE"
+)
+
+// Numeric IRC replies extracted from the IRCv3 spec.
+const (
+ RPL_LOGGEDIN = "900"
+ RPL_LOGGEDOUT = "901"
+ RPL_NICKLOCKED = "902"
+ RPL_SASLSUCCESS = "903"
+ ERR_SASLFAIL = "904"
+ ERR_SASLTOOLONG = "905"
+ ERR_SASLABORTED = "906"
+ ERR_SASLALREADY = "907"
+ RPL_SASLMECHS = "908"
+)
+
+// RFC2812, section 5.3
+const (
+ RPL_STATSCLINE = "213"
+ RPL_STATSNLINE = "214"
+ RPL_STATSILINE = "215"
+ RPL_STATSKLINE = "216"
+ RPL_STATSQLINE = "217"
+ RPL_STATSYLINE = "218"
+ RPL_SERVICEINFO = "231"
+ RPL_ENDOFSERVICES = "232"
+ RPL_SERVICE = "233"
+ RPL_STATSVLINE = "240"
+ RPL_STATSLLINE = "241"
+ RPL_STATSHLINE = "244"
+ RPL_STATSSLINE = "245"
+ RPL_STATSPING = "246"
+ RPL_STATSBLINE = "247"
+ RPL_STATSDLINE = "250"
+ RPL_NONE = "300"
+ RPL_WHOISCHANOP = "316"
+ RPL_KILLDONE = "361"
+ RPL_CLOSING = "362"
+ RPL_CLOSEEND = "363"
+ RPL_INFOSTART = "373"
+ RPL_MYPORTIS = "384"
+ ERR_NOSERVICEHOST = "492"
+)
+
+// Other constants
+const (
+ ERR_TOOMANYMATCHES = "416" // Used on IRCNet
+ RPL_TOPICWHOTIME = "333" // From ircu, in use on Freenode
+ RPL_LOCALUSERS = "265" // From aircd, Hybrid, Hybrid, Bahamut, in use on Freenode
+ RPL_GLOBALUSERS = "266" // From aircd, Hybrid, Hybrid, Bahamut, in use on Freenode
+)
diff --git a/vendor/github.com/sorcix/irc/ctcp/ctcp.go b/vendor/github.com/sorcix/irc/ctcp/ctcp.go
new file mode 100644
index 00000000..7ead788d
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/ctcp/ctcp.go
@@ -0,0 +1,144 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+package ctcp
+
+// Sources:
+// http://www.irchelp.org/irchelp/rfc/ctcpspec.html
+// http://www.kvirc.net/doc/doc_ctcp_handling.html
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+ "time"
+)
+
+// Various constants used for formatting CTCP messages.
+const (
+ delimiter byte = 0x01 // Prefix and suffix for CTCP tagged messages.
+ space byte = 0x20 // Token separator
+
+ empty = "" // The empty string
+
+ timeFormat = time.RFC1123Z
+ versionFormat = "Go v%s (" + runtime.GOOS + ", " + runtime.GOARCH + ")"
+)
+
+// Tags extracted from the CTCP spec.
+const (
+ ACTION = "ACTION"
+ PING = "PING"
+ PONG = "PONG"
+ VERSION = "VERSION"
+ USERINFO = "USERINFO"
+ CLIENTINFO = "CLIENTINFO"
+ FINGER = "FINGER"
+ SOURCE = "SOURCE"
+ TIME = "TIME"
+)
+
+// Decode attempts to decode CTCP tagged data inside given message text.
+//
+// If the message text does not contain tagged data, ok will be false.
+//
+// <text> ::= <delim> <tag> [<SPACE> <message>] <delim>
+// <delim> ::= 0x01
+//
+func Decode(text string) (tag, message string, ok bool) {
+
+ // Fast path, return if this text does not contain a CTCP message.
+ if len(text) < 3 || text[0] != delimiter || text[len(text)-1] != delimiter {
+ return empty, empty, false
+ }
+
+ s := strings.IndexByte(text, space)
+
+ if s < 0 {
+
+ // Messages may contain only a tag.
+ return text[1 : len(text)-1], empty, true
+ }
+
+ return text[1:s], text[s+1 : len(text)-1], true
+}
+
+// Encode returns the IRC message text for CTCP tagged data.
+//
+// <text> ::= <delim> <tag> [<SPACE> <message>] <delim>
+// <delim> ::= 0x01
+//
+func Encode(tag, message string) (text string) {
+
+ switch {
+
+ // We can't build a valid CTCP tagged message without at least a tag.
+ case len(tag) <= 0:
+ return empty
+
+ // Tagged data with a message
+ case len(message) > 0:
+ return string(delimiter) + tag + string(space) + message + string(delimiter)
+
+ // Tagged data without a message
+ default:
+ return string(delimiter) + tag + string(delimiter)
+
+ }
+}
+
+// Action is a shortcut for Encode(ctcp.ACTION, message).
+func Action(message string) string {
+ return Encode(ACTION, message)
+}
+
+// Ping is a shortcut for Encode(ctcp.PING, message).
+func Ping(message string) string {
+ return Encode(PING, message)
+}
+
+// Pong is a shortcut for Encode(ctcp.PONG, message).
+func Pong(message string) string {
+ return Encode(PONG, message)
+}
+
+// Version is a shortcut for Encode(ctcp.VERSION, message).
+func Version(message string) string {
+ return Encode(VERSION, message)
+}
+
+// VersionReply is a shortcut for ENCODE(ctcp.VERSION, go version info).
+func VersionReply() string {
+ return Encode(VERSION, fmt.Sprintf(versionFormat, runtime.Version()))
+}
+
+// UserInfo is a shortcut for Encode(ctcp.USERINFO, message).
+func UserInfo(message string) string {
+ return Encode(USERINFO, message)
+}
+
+// ClientInfo is a shortcut for Encode(ctcp.CLIENTINFO, message).
+func ClientInfo(message string) string {
+ return Encode(CLIENTINFO, message)
+}
+
+// Finger is a shortcut for Encode(ctcp.FINGER, message).
+func Finger(message string) string {
+ return Encode(FINGER, message)
+}
+
+// Source is a shortcut for Encode(ctcp.SOURCE, message).
+func Source(message string) string {
+ return Encode(SOURCE, message)
+}
+
+// Time is a shortcut for Encode(ctcp.TIME, message).
+func Time(message string) string {
+ return Encode(TIME, message)
+}
+
+// TimeReply is a shortcut for Encode(ctcp.TIME, currenttime).
+func TimeReply() string {
+ return Encode(TIME, time.Now().Format(timeFormat))
+}
diff --git a/vendor/github.com/sorcix/irc/ctcp/doc.go b/vendor/github.com/sorcix/irc/ctcp/doc.go
new file mode 100644
index 00000000..f0308d86
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/ctcp/doc.go
@@ -0,0 +1,31 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+// Package ctcp implements partial support for the Client-to-Client Protocol.
+//
+// CTCP defines extended messages using the standard PRIVMSG and NOTICE
+// commands in IRC. This means that any CTCP messages are embedded inside the
+// normal message text. Clients that don't support CTCP simply show
+// the encoded message to the user.
+//
+// Most IRC clients support only a subset of the protocol, and only a few
+// commands are actually used. This package aims to implement the most basic
+// CTCP messages: a single command per IRC message. Quoting is not supported.
+//
+// Example using the irc.Message type:
+//
+// m := irc.ParseMessage(...)
+//
+// if tag, text, ok := ctcp.Decode(m.Trailing); ok {
+// // This is a CTCP message.
+// } else {
+// // This is not a CTCP message.
+// }
+//
+// Similar, for encoding messages:
+//
+// m.Trailing = ctcp.Encode("ACTION","wants a cookie!")
+//
+// Do not send a complete IRC message to Decode, it won't work.
+package ctcp
diff --git a/vendor/github.com/sorcix/irc/doc.go b/vendor/github.com/sorcix/irc/doc.go
new file mode 100644
index 00000000..0effeb8a
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/doc.go
@@ -0,0 +1,36 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+// Package irc allows your application to speak the IRC protocol.
+//
+// The Message and Prefix structs provide translation to and from raw IRC messages:
+//
+// // Parse the IRC-encoded data and store the result in a new struct:
+// message := irc.ParseMessage(raw)
+//
+// // Translate back to a raw IRC message string:
+// raw = message.String()
+//
+// Decoder and Encoder can be used to decode and encode messages in a stream:
+//
+// // Create a decoder that reads from given io.Reader
+// dec := irc.NewDecoder(reader)
+//
+// // Decode the next IRC message
+// message, err := dec.Decode()
+//
+// // Create an encoder that writes to given io.Writer
+// enc := irc.NewEncoder(writer)
+//
+// // Send a message to the writer.
+// enc.Encode(message)
+//
+// The Conn type combines an Encoder and Decoder for a duplex connection.
+//
+// c, err := irc.Dial("irc.server.net:6667")
+//
+// // Methods from both Encoder and Decoder are available
+// message, err := c.Decode()
+//
+package irc
diff --git a/vendor/github.com/sorcix/irc/message.go b/vendor/github.com/sorcix/irc/message.go
new file mode 100644
index 00000000..088938dc
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/message.go
@@ -0,0 +1,308 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+package irc
+
+import (
+ "bytes"
+ "strings"
+)
+
+// Various constants used for formatting IRC messages.
+const (
+ prefix byte = 0x3A // Prefix or last argument
+ prefixUser byte = 0x21 // Username
+ prefixHost byte = 0x40 // Hostname
+ space byte = 0x20 // Separator
+
+ maxLength = 510 // Maximum length is 512 - 2 for the line endings.
+)
+
+func cutsetFunc(r rune) bool {
+ // Characters to trim from prefixes/messages.
+ return r == '\r' || r == '\n'
+}
+
+// Sender represents objects that are able to send messages to an IRC server.
+//
+// As there might be a message queue, it is possible that Send returns a nil
+// error, but the message is not sent (yet). The error value is only used when
+// it is certain that sending the message is impossible.
+//
+// This interface is not used inside this package, and shouldn't have been
+// defined here in the first place. For backwards compatibility only.
+type Sender interface {
+ Send(*Message) error
+}
+
+// Prefix represents the prefix (sender) of an IRC message.
+// See RFC1459 section 2.3.1.
+//
+// <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
+//
+type Prefix struct {
+ Name string // Nick- or servername
+ User string // Username
+ Host string // Hostname
+}
+
+// ParsePrefix takes a string and attempts to create a Prefix struct.
+func ParsePrefix(raw string) (p *Prefix) {
+
+ p = new(Prefix)
+
+ user := indexByte(raw, prefixUser)
+ host := indexByte(raw, prefixHost)
+
+ switch {
+
+ case user > 0 && host > user:
+ p.Name = raw[:user]
+ p.User = raw[user+1 : host]
+ p.Host = raw[host+1:]
+
+ case user > 0:
+ p.Name = raw[:user]
+ p.User = raw[user+1:]
+
+ case host > 0:
+ p.Name = raw[:host]
+ p.Host = raw[host+1:]
+
+ default:
+ p.Name = raw
+
+ }
+
+ return p
+}
+
+// Len calculates the length of the string representation of this prefix.
+func (p *Prefix) Len() (length int) {
+ length = len(p.Name)
+ if len(p.User) > 0 {
+ length = length + len(p.User) + 1
+ }
+ if len(p.Host) > 0 {
+ length = length + len(p.Host) + 1
+ }
+ return
+}
+
+// Bytes returns a []byte representation of this prefix.
+func (p *Prefix) Bytes() []byte {
+ buffer := new(bytes.Buffer)
+ p.writeTo(buffer)
+ return buffer.Bytes()
+}
+
+// String returns a string representation of this prefix.
+func (p *Prefix) String() (s string) {
+ // Benchmarks revealed that in this case simple string concatenation
+ // is actually faster than using a ByteBuffer as in (*Message).String()
+ s = p.Name
+ if len(p.User) > 0 {
+ s = s + string(prefixUser) + p.User
+ }
+ if len(p.Host) > 0 {
+ s = s + string(prefixHost) + p.Host
+ }
+ return
+}
+
+// IsHostmask returns true if this prefix looks like a user hostmask.
+func (p *Prefix) IsHostmask() bool {
+ return len(p.User) > 0 && len(p.Host) > 0
+}
+
+// IsServer returns true if this prefix looks like a server name.
+func (p *Prefix) IsServer() bool {
+ return len(p.User) <= 0 && len(p.Host) <= 0 // && indexByte(p.Name, '.') > 0
+}
+
+// writeTo is an utility function to write the prefix to the bytes.Buffer in Message.String().
+func (p *Prefix) writeTo(buffer *bytes.Buffer) {
+ buffer.WriteString(p.Name)
+ if len(p.User) > 0 {
+ buffer.WriteByte(prefixUser)
+ buffer.WriteString(p.User)
+ }
+ if len(p.Host) > 0 {
+ buffer.WriteByte(prefixHost)
+ buffer.WriteString(p.Host)
+ }
+ return
+}
+
+// Message represents an IRC protocol message.
+// See RFC1459 section 2.3.1.
+//
+// <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
+// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
+// <command> ::= <letter> { <letter> } | <number> <number> <number>
+// <SPACE> ::= ' ' { ' ' }
+// <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
+//
+// <middle> ::= <Any *non-empty* sequence of octets not including SPACE
+// or NUL or CR or LF, the first of which may not be ':'>
+// <trailing> ::= <Any, possibly *empty*, sequence of octets not including
+// NUL or CR or LF>
+//
+// <crlf> ::= CR LF
+type Message struct {
+ *Prefix
+ Command string
+ Params []string
+ Trailing string
+
+ // When set to true, the trailing prefix (:) will be added even if the trailing message is empty.
+ EmptyTrailing bool
+}
+
+// ParseMessage takes a string and attempts to create a Message struct.
+// Returns nil if the Message is invalid.
+func ParseMessage(raw string) (m *Message) {
+
+ // Ignore empty messages.
+ if raw = strings.TrimFunc(raw, cutsetFunc); len(raw) < 2 {
+ return nil
+ }
+
+ i, j := 0, 0
+
+ m = new(Message)
+
+ if raw[0] == prefix {
+
+ // Prefix ends with a space.
+ i = indexByte(raw, space)
+
+ // Prefix string must not be empty if the indicator is present.
+ if i < 2 {
+ return nil
+ }
+
+ m.Prefix = ParsePrefix(raw[1:i])
+
+ // Skip space at the end of the prefix
+ i++
+ }
+
+ // Find end of command
+ j = i + indexByte(raw[i:], space)
+
+ // Extract command
+ if j > i {
+ m.Command = strings.ToUpper(raw[i:j])
+ } else {
+ m.Command = strings.ToUpper(raw[i:])
+
+ // We're done here!
+ return m
+ }
+
+ // Skip space after command
+ j++
+
+ // Find prefix for trailer
+ i = indexByte(raw[j:], prefix)
+
+ if i < 0 || raw[j+i-1] != space {
+
+ // There is no trailing argument!
+ m.Params = strings.Split(raw[j:], string(space))
+
+ // We're done here!
+ return m
+ }
+
+ // Compensate for index on substring
+ i = i + j
+
+ // Check if we need to parse arguments.
+ if i > j {
+ m.Params = strings.Split(raw[j:i-1], string(space))
+ }
+
+ m.Trailing = raw[i+1:]
+
+ // We need to re-encode the trailing argument even if it was empty.
+ if len(m.Trailing) <= 0 {
+ m.EmptyTrailing = true
+ }
+
+ return m
+
+}
+
+// Len calculates the length of the string representation of this message.
+func (m *Message) Len() (length int) {
+
+ if m.Prefix != nil {
+ length = m.Prefix.Len() + 2 // Include prefix and trailing space
+ }
+
+ length = length + len(m.Command)
+
+ if len(m.Params) > 0 {
+ length = length + len(m.Params)
+ for _, param := range m.Params {
+ length = length + len(param)
+ }
+ }
+
+ if len(m.Trailing) > 0 || m.EmptyTrailing {
+ length = length + len(m.Trailing) + 2 // Include prefix and space
+ }
+
+ return
+}
+
+// Bytes returns a []byte representation of this message.
+//
+// As noted in rfc2812 section 2.3, messages should not exceed 512 characters
+// in length. This method forces that limit by discarding any characters
+// exceeding the length limit.
+func (m *Message) Bytes() []byte {
+
+ buffer := new(bytes.Buffer)
+
+ // Message prefix
+ if m.Prefix != nil {
+ buffer.WriteByte(prefix)
+ m.Prefix.writeTo(buffer)
+ buffer.WriteByte(space)
+ }
+
+ // Command is required
+ buffer.WriteString(m.Command)
+
+ // Space separated list of arguments
+ if len(m.Params) > 0 {
+ buffer.WriteByte(space)
+ buffer.WriteString(strings.Join(m.Params, string(space)))
+ }
+
+ if len(m.Trailing) > 0 || m.EmptyTrailing {
+ buffer.WriteByte(space)
+ buffer.WriteByte(prefix)
+ buffer.WriteString(m.Trailing)
+ }
+
+ // We need the limit the buffer length.
+ if buffer.Len() > (maxLength) {
+ buffer.Truncate(maxLength)
+ }
+
+ return buffer.Bytes()
+}
+
+// String returns a string representation of this message.
+//
+// As noted in rfc2812 section 2.3, messages should not exceed 512 characters
+// in length. This method forces that limit by discarding any characters
+// exceeding the length limit.
+func (m *Message) String() string {
+ return string(m.Bytes())
+}
diff --git a/vendor/github.com/sorcix/irc/stream.go b/vendor/github.com/sorcix/irc/stream.go
new file mode 100644
index 00000000..c4af9af1
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/stream.go
@@ -0,0 +1,134 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+package irc
+
+import (
+ "bufio"
+ "io"
+ "net"
+ "sync"
+)
+
+// Messages are delimited with CR and LF line endings,
+// we're using the last one to split the stream. Both are removed
+// during message parsing.
+const delim byte = '\n'
+
+var endline = []byte("\r\n")
+
+// A Conn represents an IRC network protocol connection.
+// It consists of an Encoder and Decoder to manage I/O.
+type Conn struct {
+ Encoder
+ Decoder
+
+ conn io.ReadWriteCloser
+}
+
+// NewConn returns a new Conn using rwc for I/O.
+func NewConn(rwc io.ReadWriteCloser) *Conn {
+ return &Conn{
+ Encoder: Encoder{
+ writer: rwc,
+ },
+ Decoder: Decoder{
+ reader: bufio.NewReader(rwc),
+ },
+ conn: rwc,
+ }
+}
+
+// Dial connects to the given address using net.Dial and
+// then returns a new Conn for the connection.
+func Dial(addr string) (*Conn, error) {
+ c, err := net.Dial("tcp", addr)
+
+ if err != nil {
+ return nil, err
+ }
+
+ return NewConn(c), nil
+}
+
+// Close closes the underlying ReadWriteCloser.
+func (c *Conn) Close() error {
+ return c.conn.Close()
+}
+
+// A Decoder reads Message objects from an input stream.
+type Decoder struct {
+ reader *bufio.Reader
+ line string
+ mu sync.Mutex
+}
+
+// NewDecoder returns a new Decoder that reads from r.
+func NewDecoder(r io.Reader) *Decoder {
+ return &Decoder{
+ reader: bufio.NewReader(r),
+ }
+}
+
+// Decode attempts to read a single Message from the stream.
+//
+// Returns a non-nil error if the read failed.
+func (dec *Decoder) Decode() (m *Message, err error) {
+
+ dec.mu.Lock()
+ dec.line, err = dec.reader.ReadString(delim)
+ dec.mu.Unlock()
+
+ if err != nil {
+ return nil, err
+ }
+
+ return ParseMessage(dec.line), nil
+}
+
+// An Encoder writes Message objects to an output stream.
+type Encoder struct {
+ writer io.Writer
+ mu sync.Mutex
+}
+
+// NewEncoder returns a new Encoder that writes to w.
+func NewEncoder(w io.Writer) *Encoder {
+ return &Encoder{
+ writer: w,
+ }
+}
+
+// Encode writes the IRC encoding of m to the stream.
+//
+// This method may be used from multiple goroutines.
+//
+// Returns an non-nil error if the write to the underlying stream stopped early.
+func (enc *Encoder) Encode(m *Message) (err error) {
+
+ _, err = enc.Write(m.Bytes())
+
+ return
+}
+
+// Write writes len(p) bytes from p followed by CR+LF.
+//
+// This method can be used simultaneously from multiple goroutines,
+// it guarantees to serialize access. However, writing a single IRC message
+// using multiple Write calls will cause corruption.
+func (enc *Encoder) Write(p []byte) (n int, err error) {
+
+ enc.mu.Lock()
+ n, err = enc.writer.Write(p)
+
+ if err != nil {
+ enc.mu.Unlock()
+ return
+ }
+
+ _, err = enc.writer.Write(endline)
+ enc.mu.Unlock()
+
+ return
+}
diff --git a/vendor/github.com/sorcix/irc/strings.go b/vendor/github.com/sorcix/irc/strings.go
new file mode 100644
index 00000000..550739f4
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/strings.go
@@ -0,0 +1,17 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+// +build go1.2
+
+// Documented in strings_legacy.go
+
+package irc
+
+import (
+ "strings"
+)
+
+func indexByte(s string, c byte) int {
+ return strings.IndexByte(s, c)
+}
diff --git a/vendor/github.com/sorcix/irc/strings_legacy.go b/vendor/github.com/sorcix/irc/strings_legacy.go
new file mode 100644
index 00000000..f9328ec7
--- /dev/null
+++ b/vendor/github.com/sorcix/irc/strings_legacy.go
@@ -0,0 +1,22 @@
+// Copyright 2014 Vic Demuzere
+//
+// Use of this source code is governed by the MIT license.
+
+// +build !go1.2
+
+// Debian Wheezy only ships Go 1.0:
+// https://github.com/sorcix/irc/issues/4
+//
+// This code may be removed when Wheezy is no longer supported.
+
+package irc
+
+// indexByte implements strings.IndexByte for Go versions < 1.2.
+func indexByte(s string, c byte) int {
+ for i := range s {
+ if s[i] == c {
+ return i
+ }
+ }
+ return -1
+}
diff --git a/vendor/github.com/thoj/go-ircevent/LICENSE b/vendor/github.com/thoj/go-ircevent/LICENSE
new file mode 100644
index 00000000..d6bf3577
--- /dev/null
+++ b/vendor/github.com/thoj/go-ircevent/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2009 Thomas Jager. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/thoj/go-ircevent/irc.go b/vendor/github.com/thoj/go-ircevent/irc.go
new file mode 100644
index 00000000..9043e888
--- /dev/null
+++ b/vendor/github.com/thoj/go-ircevent/irc.go
@@ -0,0 +1,473 @@
+// Copyright 2009 Thomas Jager <mail@jager.no> All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+This package provides an event based IRC client library. It allows to
+register callbacks for the events you need to handle. Its features
+include handling standard CTCP, reconnecting on errors and detecting
+stones servers.
+Details of the IRC protocol can be found in the following RFCs:
+https://tools.ietf.org/html/rfc1459
+https://tools.ietf.org/html/rfc2810
+https://tools.ietf.org/html/rfc2811
+https://tools.ietf.org/html/rfc2812
+https://tools.ietf.org/html/rfc2813
+The details of the client-to-client protocol (CTCP) can be found here: http://www.irchelp.org/irchelp/rfc/ctcpspec.html
+*/
+
+package irc
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const (
+ VERSION = "go-ircevent v2.1"
+)
+
+var ErrDisconnected = errors.New("Disconnect Called")
+
+// Read data from a connection. To be used as a goroutine.
+func (irc *Connection) readLoop() {
+ defer irc.Done()
+ br := bufio.NewReaderSize(irc.socket, 512)
+
+ errChan := irc.ErrorChan()
+
+ for {
+ select {
+ case <-irc.end:
+ return
+ default:
+ // Set a read deadline based on the combined timeout and ping frequency
+ // We should ALWAYS have received a response from the server within the timeout
+ // after our own pings
+ if irc.socket != nil {
+ irc.socket.SetReadDeadline(time.Now().Add(irc.Timeout + irc.PingFreq))
+ }
+
+ msg, err := br.ReadString('\n')
+
+ // We got past our blocking read, so bin timeout
+ if irc.socket != nil {
+ var zero time.Time
+ irc.socket.SetReadDeadline(zero)
+ }
+
+ if err != nil {
+ errChan <- err
+ return
+ }
+
+ if irc.Debug {
+ irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg))
+ }
+
+ irc.lastMessage = time.Now()
+ event, err := parseToEvent(msg)
+ event.Connection = irc
+ if err == nil {
+ /* XXX: len(args) == 0: args should be empty */
+ irc.RunCallbacks(event)
+ }
+ }
+ }
+}
+
+//Parse raw irc messages
+func parseToEvent(msg string) (*Event, error) {
+ msg = strings.TrimSuffix(msg, "\n") //Remove \r\n
+ msg = strings.TrimSuffix(msg, "\r")
+ event := &Event{Raw: msg}
+ if len(msg) < 5 {
+ return nil, errors.New("Malformed msg from server")
+ }
+ if msg[0] == ':' {
+ if i := strings.Index(msg, " "); i > -1 {
+ event.Source = msg[1:i]
+ msg = msg[i+1 : len(msg)]
+
+ } else {
+ return nil, errors.New("Malformed msg from server")
+ }
+
+ if i, j := strings.Index(event.Source, "!"), strings.Index(event.Source, "@"); i > -1 && j > -1 && i < j {
+ event.Nick = event.Source[0:i]
+ event.User = event.Source[i+1 : j]
+ event.Host = event.Source[j+1 : len(event.Source)]
+ }
+ }
+
+ split := strings.SplitN(msg, " :", 2)
+ args := strings.Split(split[0], " ")
+ event.Code = strings.ToUpper(args[0])
+ event.Arguments = args[1:]
+ if len(split) > 1 {
+ event.Arguments = append(event.Arguments, split[1])
+ }
+ return event, nil
+
+}
+
+// Loop to write to a connection. To be used as a goroutine.
+func (irc *Connection) writeLoop() {
+ defer irc.Done()
+ errChan := irc.ErrorChan()
+ for {
+ select {
+ case <-irc.end:
+ return
+ case b, ok := <-irc.pwrite:
+ if !ok || b == "" || irc.socket == nil {
+ return
+ }
+
+ if irc.Debug {
+ irc.Log.Printf("--> %s\n", strings.TrimSpace(b))
+ }
+
+ // Set a write deadline based on the time out
+ irc.socket.SetWriteDeadline(time.Now().Add(irc.Timeout))
+
+ _, err := irc.socket.Write([]byte(b))
+
+ // Past blocking write, bin timeout
+ var zero time.Time
+ irc.socket.SetWriteDeadline(zero)
+
+ if err != nil {
+ errChan <- err
+ return
+ }
+ }
+ }
+ return
+}
+
+// Pings the server if we have not received any messages for 5 minutes
+// to keep the connection alive. To be used as a goroutine.
+func (irc *Connection) pingLoop() {
+ defer irc.Done()
+ ticker := time.NewTicker(1 * time.Minute) // Tick every minute for monitoring
+ ticker2 := time.NewTicker(irc.PingFreq) // Tick at the ping frequency.
+ for {
+ select {
+ case <-ticker.C:
+ //Ping if we haven't received anything from the server within the keep alive period
+ if time.Since(irc.lastMessage) >= irc.KeepAlive {
+ irc.SendRawf("PING %d", time.Now().UnixNano())
+ }
+ case <-ticker2.C:
+ //Ping at the ping frequency
+ irc.SendRawf("PING %d", time.Now().UnixNano())
+ //Try to recapture nickname if it's not as configured.
+ if irc.nick != irc.nickcurrent {
+ irc.nickcurrent = irc.nick
+ irc.SendRawf("NICK %s", irc.nick)
+ }
+ case <-irc.end:
+ ticker.Stop()
+ ticker2.Stop()
+ return
+ }
+ }
+}
+
+// Main loop to control the connection.
+func (irc *Connection) Loop() {
+ errChan := irc.ErrorChan()
+ for !irc.quit {
+ err := <-errChan
+ irc.Log.Printf("Error, disconnected: %s\n", err)
+ for !irc.quit {
+ if err = irc.Reconnect(); err != nil {
+ irc.Log.Printf("Error while reconnecting: %s\n", err)
+ time.Sleep(60 * time.Second)
+ } else {
+ errChan = irc.ErrorChan()
+ break
+ }
+ }
+ }
+}
+
+// Quit the current connection and disconnect from the server
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.6
+func (irc *Connection) Quit() {
+ quit := "QUIT"
+
+ if irc.QuitMessage != "" {
+ quit = fmt.Sprintf("QUIT :%s", irc.QuitMessage)
+ }
+
+ irc.SendRaw(quit)
+ irc.stopped = true
+ irc.quit = true
+}
+
+// Use the connection to join a given channel.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.1
+func (irc *Connection) Join(channel string) {
+ irc.pwrite <- fmt.Sprintf("JOIN %s\r\n", channel)
+}
+
+// Leave a given channel.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.2
+func (irc *Connection) Part(channel string) {
+ irc.pwrite <- fmt.Sprintf("PART %s\r\n", channel)
+}
+
+// Send a notification to a nickname. This is similar to Privmsg but must not receive replies.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
+func (irc *Connection) Notice(target, message string) {
+ irc.pwrite <- fmt.Sprintf("NOTICE %s :%s\r\n", target, message)
+}
+
+// Send a formated notification to a nickname.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.2
+func (irc *Connection) Noticef(target, format string, a ...interface{}) {
+ irc.Notice(target, fmt.Sprintf(format, a...))
+}
+
+// Send (action) message to a target (channel or nickname).
+// No clear RFC on this one...
+func (irc *Connection) Action(target, message string) {
+ irc.pwrite <- fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001\r\n", target, message)
+}
+
+// Send formatted (action) message to a target (channel or nickname).
+func (irc *Connection) Actionf(target, format string, a ...interface{}) {
+ irc.Action(target, fmt.Sprintf(format, a...))
+}
+
+// Send (private) message to a target (channel or nickname).
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.4.1
+func (irc *Connection) Privmsg(target, message string) {
+ irc.pwrite <- fmt.Sprintf("PRIVMSG %s :%s\r\n", target, message)
+}
+
+// Send formated string to specified target (channel or nickname).
+func (irc *Connection) Privmsgf(target, format string, a ...interface{}) {
+ irc.Privmsg(target, fmt.Sprintf(format, a...))
+}
+
+// Kick <user> from <channel> with <msg>. For no message, pass empty string ("")
+func (irc *Connection) Kick(user, channel, msg string) {
+ var cmd bytes.Buffer
+ cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, user))
+ if msg != "" {
+ cmd.WriteString(fmt.Sprintf(" :%s", msg))
+ }
+ cmd.WriteString("\r\n")
+ irc.pwrite <- cmd.String()
+}
+
+// Kick all <users> from <channel> with <msg>. For no message, pass
+// empty string ("")
+func (irc *Connection) MultiKick(users []string, channel string, msg string) {
+ var cmd bytes.Buffer
+ cmd.WriteString(fmt.Sprintf("KICK %s %s", channel, strings.Join(users, ",")))
+ if msg != "" {
+ cmd.WriteString(fmt.Sprintf(" :%s", msg))
+ }
+ cmd.WriteString("\r\n")
+ irc.pwrite <- cmd.String()
+}
+
+// Send raw string.
+func (irc *Connection) SendRaw(message string) {
+ irc.pwrite <- message + "\r\n"
+}
+
+// Send raw formated string.
+func (irc *Connection) SendRawf(format string, a ...interface{}) {
+ irc.SendRaw(fmt.Sprintf(format, a...))
+}
+
+// Set (new) nickname.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1.2
+func (irc *Connection) Nick(n string) {
+ irc.nick = n
+ irc.SendRawf("NICK %s", n)
+}
+
+// Determine nick currently used with the connection.
+func (irc *Connection) GetNick() string {
+ return irc.nickcurrent
+}
+
+// Query information about a particular nickname.
+// RFC 1459: https://tools.ietf.org/html/rfc1459#section-4.5.2
+func (irc *Connection) Whois(nick string) {
+ irc.SendRawf("WHOIS %s", nick)
+}
+
+// Query information about a given nickname in the server.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.5.1
+func (irc *Connection) Who(nick string) {
+ irc.SendRawf("WHO %s", nick)
+}
+
+// Set different modes for a target (channel or nickname).
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.2.3
+func (irc *Connection) Mode(target string, modestring ...string) {
+ if len(modestring) > 0 {
+ mode := strings.Join(modestring, " ")
+ irc.SendRawf("MODE %s %s", target, mode)
+ return
+ }
+ irc.SendRawf("MODE %s", target)
+}
+
+func (irc *Connection) ErrorChan() chan error {
+ return irc.Error
+}
+
+// Returns true if the connection is connected to an IRC server.
+func (irc *Connection) Connected() bool {
+ return !irc.stopped
+}
+
+// A disconnect sends all buffered messages (if possible),
+// stops all goroutines and then closes the socket.
+func (irc *Connection) Disconnect() {
+ for event := range irc.events {
+ irc.ClearCallback(event)
+ }
+ if irc.end != nil {
+ close(irc.end)
+ }
+
+ irc.end = nil
+
+ if irc.pwrite != nil {
+ close(irc.pwrite)
+ }
+
+ irc.Wait()
+ if irc.socket != nil {
+ irc.socket.Close()
+ }
+ irc.socket = nil
+ irc.ErrorChan() <- ErrDisconnected
+}
+
+// Reconnect to a server using the current connection.
+func (irc *Connection) Reconnect() error {
+ if irc.end != nil {
+ close(irc.end)
+ }
+
+ irc.end = nil
+
+ irc.Wait() //make sure that wait group is cleared ensuring that all spawned goroutines have completed
+
+ irc.end = make(chan struct{})
+ return irc.Connect(irc.Server)
+}
+
+// Connect to a given server using the current connection configuration.
+// This function also takes care of identification if a password is provided.
+// RFC 1459 details: https://tools.ietf.org/html/rfc1459#section-4.1
+func (irc *Connection) Connect(server string) error {
+ irc.Server = server
+ // mark Server as stopped since there can be an error during connect
+ irc.stopped = true
+
+ // make sure everything is ready for connection
+ if len(irc.Server) == 0 {
+ return errors.New("empty 'server'")
+ }
+ if strings.Count(irc.Server, ":") != 1 {
+ return errors.New("wrong number of ':' in address")
+ }
+ if strings.Index(irc.Server, ":") == 0 {
+ return errors.New("hostname is missing")
+ }
+ if strings.Index(irc.Server, ":") == len(irc.Server)-1 {
+ return errors.New("port missing")
+ }
+ // check for valid range
+ ports := strings.Split(irc.Server, ":")[1]
+ port, err := strconv.Atoi(ports)
+ if err != nil {
+ return errors.New("extracting port failed")
+ }
+ if !((port >= 0) && (port <= 65535)) {
+ return errors.New("port number outside valid range")
+ }
+ if irc.Log == nil {
+ return errors.New("'Log' points to nil")
+ }
+ if len(irc.nick) == 0 {
+ return errors.New("empty 'nick'")
+ }
+ if len(irc.user) == 0 {
+ return errors.New("empty 'user'")
+ }
+
+ if irc.UseTLS {
+ dialer := &net.Dialer{Timeout: irc.Timeout}
+ irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.Server, irc.TLSConfig)
+ } else {
+ irc.socket, err = net.DialTimeout("tcp", irc.Server, irc.Timeout)
+ }
+ if err != nil {
+ return err
+ }
+
+ irc.stopped = false
+ irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr())
+
+ irc.pwrite = make(chan string, 10)
+ irc.Error = make(chan error, 2)
+ irc.Add(3)
+ go irc.readLoop()
+ go irc.writeLoop()
+ go irc.pingLoop()
+ if len(irc.Password) > 0 {
+ irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
+ }
+ irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick)
+ irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user)
+ return nil
+}
+
+// Create a connection with the (publicly visible) nickname and username.
+// The nickname is later used to address the user. Returns nil if nick
+// or user are empty.
+func IRC(nick, user string) *Connection {
+ // catch invalid values
+ if len(nick) == 0 {
+ return nil
+ }
+ if len(user) == 0 {
+ return nil
+ }
+
+ irc := &Connection{
+ nick: nick,
+ nickcurrent: nick,
+ user: user,
+ Log: log.New(os.Stdout, "", log.LstdFlags),
+ end: make(chan struct{}),
+ Version: VERSION,
+ KeepAlive: 4 * time.Minute,
+ Timeout: 1 * time.Minute,
+ PingFreq: 15 * time.Minute,
+ QuitMessage: "",
+ }
+ irc.setupCallbacks()
+ return irc
+}
diff --git a/vendor/github.com/thoj/go-ircevent/irc_callback.go b/vendor/github.com/thoj/go-ircevent/irc_callback.go
new file mode 100644
index 00000000..109cbb1f
--- /dev/null
+++ b/vendor/github.com/thoj/go-ircevent/irc_callback.go
@@ -0,0 +1,221 @@
+package irc
+
+import (
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Register a callback to a connection and event code. A callback is a function
+// which takes only an Event pointer as parameter. Valid event codes are all
+// IRC/CTCP commands and error/response codes. This function returns the ID of
+// the registered callback for later management.
+func (irc *Connection) AddCallback(eventcode string, callback func(*Event)) int {
+ eventcode = strings.ToUpper(eventcode)
+ id := 0
+ if _, ok := irc.events[eventcode]; !ok {
+ irc.events[eventcode] = make(map[int]func(*Event))
+ id = 0
+ } else {
+ id = len(irc.events[eventcode])
+ }
+ irc.events[eventcode][id] = callback
+ return id
+}
+
+// Remove callback i (ID) from the given event code. This functions returns
+// true upon success, false if any error occurs.
+func (irc *Connection) RemoveCallback(eventcode string, i int) bool {
+ eventcode = strings.ToUpper(eventcode)
+
+ if event, ok := irc.events[eventcode]; ok {
+ if _, ok := event[i]; ok {
+ delete(irc.events[eventcode], i)
+ return true
+ }
+ irc.Log.Printf("Event found, but no callback found at id %s\n", i)
+ return false
+ }
+
+ irc.Log.Println("Event not found")
+ return false
+}
+
+// Remove all callbacks from a given event code. It returns true
+// if given event code is found and cleared.
+func (irc *Connection) ClearCallback(eventcode string) bool {
+ eventcode = strings.ToUpper(eventcode)
+
+ if _, ok := irc.events[eventcode]; ok {
+ irc.events[eventcode] = make(map[int]func(*Event))
+ return true
+ }
+
+ irc.Log.Println("Event not found")
+ return false
+}
+
+// Replace callback i (ID) associated with a given event code with a new callback function.
+func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*Event)) {
+ eventcode = strings.ToUpper(eventcode)
+
+ if event, ok := irc.events[eventcode]; ok {
+ if _, ok := event[i]; ok {
+ event[i] = callback
+ return
+ }
+ irc.Log.Printf("Event found, but no callback found at id %s\n", i)
+ }
+ irc.Log.Printf("Event not found. Use AddCallBack\n")
+}
+
+// Execute all callbacks associated with a given event.
+func (irc *Connection) RunCallbacks(event *Event) {
+ msg := event.Message()
+ if event.Code == "PRIVMSG" && len(msg) > 2 && msg[0] == '\x01' {
+ event.Code = "CTCP" //Unknown CTCP
+
+ if i := strings.LastIndex(msg, "\x01"); i > 0 {
+ msg = msg[1:i]
+ } else {
+ irc.Log.Printf("Invalid CTCP Message: %s\n", strconv.Quote(msg))
+ return
+ }
+
+ if msg == "VERSION" {
+ event.Code = "CTCP_VERSION"
+
+ } else if msg == "TIME" {
+ event.Code = "CTCP_TIME"
+
+ } else if strings.HasPrefix(msg, "PING") {
+ event.Code = "CTCP_PING"
+
+ } else if msg == "USERINFO" {
+ event.Code = "CTCP_USERINFO"
+
+ } else if msg == "CLIENTINFO" {
+ event.Code = "CTCP_CLIENTINFO"
+
+ } else if strings.HasPrefix(msg, "ACTION") {
+ event.Code = "CTCP_ACTION"
+ if len(msg) > 6 {
+ msg = msg[7:]
+ } else {
+ msg = ""
+ }
+ }
+
+ event.Arguments[len(event.Arguments)-1] = msg
+ }
+
+ if callbacks, ok := irc.events[event.Code]; ok {
+ if irc.VerboseCallbackHandler {
+ irc.Log.Printf("%v (%v) >> %#v\n", event.Code, len(callbacks), event)
+ }
+
+ for _, callback := range callbacks {
+ callback(event)
+ }
+ } else if irc.VerboseCallbackHandler {
+ irc.Log.Printf("%v (0) >> %#v\n", event.Code, event)
+ }
+
+ if callbacks, ok := irc.events["*"]; ok {
+ if irc.VerboseCallbackHandler {
+ irc.Log.Printf("%v (0) >> %#v\n", event.Code, event)
+ }
+
+ for _, callback := range callbacks {
+ callback(event)
+ }
+ }
+}
+
+// Set up some initial callbacks to handle the IRC/CTCP protocol.
+func (irc *Connection) setupCallbacks() {
+ irc.events = make(map[string]map[int]func(*Event))
+
+ //Handle error events. This has to be called in a new thred to allow
+ //readLoop to exit
+ irc.AddCallback("ERROR", func(e *Event) { go irc.Disconnect() })
+
+ //Handle ping events
+ irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) })
+
+ //Version handler
+ irc.AddCallback("CTCP_VERSION", func(e *Event) {
+ irc.SendRawf("NOTICE %s :\x01VERSION %s\x01", e.Nick, irc.Version)
+ })
+
+ irc.AddCallback("CTCP_USERINFO", func(e *Event) {
+ irc.SendRawf("NOTICE %s :\x01USERINFO %s\x01", e.Nick, irc.user)
+ })
+
+ irc.AddCallback("CTCP_CLIENTINFO", func(e *Event) {
+ irc.SendRawf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick)
+ })
+
+ irc.AddCallback("CTCP_TIME", func(e *Event) {
+ ltime := time.Now()
+ irc.SendRawf("NOTICE %s :\x01TIME %s\x01", e.Nick, ltime.String())
+ })
+
+ irc.AddCallback("CTCP_PING", func(e *Event) { irc.SendRawf("NOTICE %s :\x01%s\x01", e.Nick, e.Message()) })
+
+ // 437: ERR_UNAVAILRESOURCE "<nick/channel> :Nick/channel is temporarily unavailable"
+ // Add a _ to current nick. If irc.nickcurrent is empty this cannot
+ // work. It has to be set somewhere first in case the nick is already
+ // taken or unavailable from the beginning.
+ irc.AddCallback("437", func(e *Event) {
+ // If irc.nickcurrent hasn't been set yet, set to irc.nick
+ if irc.nickcurrent == "" {
+ irc.nickcurrent = irc.nick
+ }
+
+ if len(irc.nickcurrent) > 8 {
+ irc.nickcurrent = "_" + irc.nickcurrent
+ } else {
+ irc.nickcurrent = irc.nickcurrent + "_"
+ }
+ irc.SendRawf("NICK %s", irc.nickcurrent)
+ })
+
+ // 433: ERR_NICKNAMEINUSE "<nick> :Nickname is already in use"
+ // Add a _ to current nick.
+ irc.AddCallback("433", func(e *Event) {
+ // If irc.nickcurrent hasn't been set yet, set to irc.nick
+ if irc.nickcurrent == "" {
+ irc.nickcurrent = irc.nick
+ }
+
+ if len(irc.nickcurrent) > 8 {
+ irc.nickcurrent = "_" + irc.nickcurrent
+ } else {
+ irc.nickcurrent = irc.nickcurrent + "_"
+ }
+ irc.SendRawf("NICK %s", irc.nickcurrent)
+ })
+
+ irc.AddCallback("PONG", func(e *Event) {
+ ns, _ := strconv.ParseInt(e.Message(), 10, 64)
+ delta := time.Duration(time.Now().UnixNano() - ns)
+ if irc.Debug {
+ irc.Log.Printf("Lag: %vs\n", delta)
+ }
+ })
+
+ // NICK Define a nickname.
+ // Set irc.nickcurrent to the new nick actually used in this connection.
+ irc.AddCallback("NICK", func(e *Event) {
+ if e.Nick == irc.nick {
+ irc.nickcurrent = e.Message()
+ }
+ })
+
+ // 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>"
+ // Set irc.nickcurrent to the actually used nick in this connection.
+ irc.AddCallback("001", func(e *Event) {
+ irc.nickcurrent = e.Arguments[0]
+ })
+}
diff --git a/vendor/github.com/thoj/go-ircevent/irc_struct.go b/vendor/github.com/thoj/go-ircevent/irc_struct.go
new file mode 100644
index 00000000..3e4a438f
--- /dev/null
+++ b/vendor/github.com/thoj/go-ircevent/irc_struct.go
@@ -0,0 +1,68 @@
+// Copyright 2009 Thomas Jager <mail@jager.no> All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package irc
+
+import (
+ "crypto/tls"
+ "log"
+ "net"
+ "sync"
+ "time"
+)
+
+type Connection struct {
+ sync.WaitGroup
+ Debug bool
+ Error chan error
+ Password string
+ UseTLS bool
+ TLSConfig *tls.Config
+ Version string
+ Timeout time.Duration
+ PingFreq time.Duration
+ KeepAlive time.Duration
+ Server string
+
+ socket net.Conn
+ pwrite chan string
+ end chan struct{}
+
+ nick string //The nickname we want.
+ nickcurrent string //The nickname we currently have.
+ user string
+ registered bool
+ events map[string]map[int]func(*Event)
+
+ QuitMessage string
+ lastMessage time.Time
+
+ VerboseCallbackHandler bool
+ Log *log.Logger
+
+ stopped bool
+ quit bool
+}
+
+// A struct to represent an event.
+type Event struct {
+ Code string
+ Raw string
+ Nick string //<nick>
+ Host string //<nick>!<usr>@<host>
+ Source string //<host>
+ User string //<usr>
+ Arguments []string
+ Connection *Connection
+}
+
+// Retrieve the last message from Event arguments.
+// This function leaves the arguments untouched and
+// returns an empty string if there are none.
+func (e *Event) Message() string {
+ if len(e.Arguments) == 0 {
+ return ""
+ }
+ return e.Arguments[len(e.Arguments)-1]
+}
diff --git a/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go b/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go
new file mode 100644
index 00000000..82202e1c
--- /dev/null
+++ b/vendor/github.com/thoj/go-ircevent/irc_test_fuzz.go
@@ -0,0 +1,14 @@
+// +build gofuzz
+
+package irc
+
+func Fuzz(data []byte) int {
+ b := bytes.NewBuffer(data)
+ event, err := parseToEvent(b.String())
+ if err == nil {
+ irc := IRC("go-eventirc", "go-eventirc")
+ irc.RunCallbacks(event)
+ return 1
+ }
+ return 0
+}