From 09875fe1603307080f3a4172985c5dca3bd9912d Mon Sep 17 00:00:00 2001 From: Duco van Amstel Date: Sun, 18 Nov 2018 17:55:05 +0000 Subject: Update direct dependencies where possible --- vendor/github.com/Philipp15b/go-steam/auth.go | 41 +- vendor/github.com/alecthomas/log4go/.gitignore | 2 - vendor/github.com/alecthomas/log4go/LICENSE | 13 - vendor/github.com/alecthomas/log4go/README | 14 - vendor/github.com/alecthomas/log4go/config.go | 288 -- vendor/github.com/alecthomas/log4go/filelog.go | 264 -- vendor/github.com/alecthomas/log4go/log4go.go | 484 --- vendor/github.com/alecthomas/log4go/pattlog.go | 126 - vendor/github.com/alecthomas/log4go/socklog.go | 57 - vendor/github.com/alecthomas/log4go/termlog.go | 49 - vendor/github.com/alecthomas/log4go/wrapper.go | 278 -- .../telegram-bot-api/.gitignore | 1 + .../telegram-bot-api/.travis.yml | 5 +- .../go-telegram-bot-api/telegram-bot-api/README.md | 27 +- .../go-telegram-bot-api/telegram-bot-api/bot.go | 17 +- .../telegram-bot-api/configs.go | 133 +- .../telegram-bot-api/helpers.go | 60 +- .../go-telegram-bot-api/telegram-bot-api/log.go | 27 + .../telegram-bot-api/passport.go | 315 ++ .../go-telegram-bot-api/telegram-bot-api/types.go | 35 + vendor/github.com/google/gops/agent/agent.go | 55 +- vendor/github.com/google/gops/internal/internal.go | 10 + vendor/github.com/google/gops/signal/signal.go | 3 + vendor/github.com/gorilla/schema/cache.go | 54 +- vendor/github.com/gorilla/schema/converter.go | 2 +- vendor/github.com/gorilla/schema/decoder.go | 149 +- vendor/github.com/gorilla/schema/doc.go | 6 +- vendor/github.com/gorilla/schema/encoder.go | 40 +- vendor/github.com/hashicorp/golang-lru/2q.go | 21 +- vendor/github.com/hashicorp/golang-lru/arc.go | 16 +- vendor/github.com/hashicorp/golang-lru/doc.go | 21 + vendor/github.com/hashicorp/golang-lru/go.mod | 1 + vendor/github.com/hashicorp/golang-lru/lru.go | 28 +- .../hashicorp/golang-lru/simplelru/lru.go | 17 +- .../golang-lru/simplelru/lru_interface.go | 36 + vendor/github.com/hashicorp/hcl/go.mod | 3 + vendor/github.com/hashicorp/hcl/go.sum | 2 + .../github.com/hashicorp/hcl/hcl/parser/parser.go | 6 + .../github.com/hashicorp/hcl/hcl/printer/nodes.go | 190 +- .../hashicorp/hcl/hcl/scanner/scanner.go | 29 +- vendor/github.com/jpillora/backoff/README.md | 2 +- vendor/github.com/jpillora/backoff/backoff.go | 13 +- vendor/github.com/labstack/echo/.travis.yml | 2 +- vendor/github.com/labstack/echo/Gopkg.lock | 26 +- vendor/github.com/labstack/echo/Gopkg.toml | 83 +- vendor/github.com/labstack/echo/Makefile | 4 + vendor/github.com/labstack/echo/README.md | 8 +- vendor/github.com/labstack/echo/bind.go | 2 +- vendor/github.com/labstack/echo/context.go | 24 +- vendor/github.com/labstack/echo/echo.go | 128 +- vendor/github.com/labstack/echo/echo_go1.8.go | 25 - vendor/github.com/labstack/echo/group.go | 4 +- .../labstack/echo/middleware/basic_auth.go | 6 +- .../labstack/echo/middleware/body_dump.go | 3 +- .../labstack/echo/middleware/body_limit.go | 1 + vendor/github.com/labstack/echo/middleware/csrf.go | 8 +- vendor/github.com/labstack/echo/middleware/jwt.go | 12 +- .../labstack/echo/middleware/key_auth.go | 8 +- .../github.com/labstack/echo/middleware/logger.go | 8 +- .../github.com/labstack/echo/middleware/proxy.go | 8 +- .../github.com/labstack/echo/middleware/static.go | 119 +- vendor/github.com/labstack/echo/response.go | 24 +- vendor/github.com/labstack/echo/router.go | 65 +- vendor/github.com/labstack/echo/util_go17.go | 12 - vendor/github.com/labstack/echo/util_go18.go | 10 - vendor/github.com/lrstanley/girc/.travis.yml | 7 +- vendor/github.com/lrstanley/girc/commands.go | 4 +- vendor/github.com/lrstanley/girc/constants.go | 1 + vendor/github.com/lrstanley/girc/ctcp.go | 27 +- vendor/github.com/lrstanley/girc/event.go | 26 +- vendor/github.com/lrstanley/girc/format.go | 2 +- vendor/github.com/lrstanley/girc/go.mod | 1 + vendor/github.com/lrstanley/girc/handler.go | 2 +- .../github.com/magiconair/properties/.travis.yml | 2 +- .../github.com/magiconair/properties/CHANGELOG.md | 8 + vendor/github.com/magiconair/properties/decode.go | 2 +- vendor/github.com/magiconair/properties/doc.go | 6 +- .../github.com/magiconair/properties/integrate.go | 2 +- vendor/github.com/magiconair/properties/lex.go | 2 +- vendor/github.com/magiconair/properties/load.go | 257 +- vendor/github.com/magiconair/properties/parser.go | 2 +- .../github.com/magiconair/properties/properties.go | 13 +- .../github.com/magiconair/properties/rangecheck.go | 2 +- .../mattermost/mattermost-server/LICENSE.txt | 897 +++++ .../mattermost/mattermost-server/NOTICE.txt | 3721 +++++++++++++++++++ .../mattermost/mattermost-server/mlog/default.go | 50 + .../mattermost/mattermost-server/mlog/global.go | 44 + .../mattermost/mattermost-server/mlog/log.go | 173 + .../mattermost/mattermost-server/mlog/stdlog.go | 87 + .../mattermost/mattermost-server/mlog/sugar.go | 28 + .../mattermost/mattermost-server/model/access.go | 96 + .../mattermost-server/model/analytics_row.go | 41 + .../mattermost/mattermost-server/model/audit.go | 30 + .../mattermost/mattermost-server/model/audits.go | 34 + .../mattermost-server/model/authorize.go | 142 + .../mattermost/mattermost-server/model/builtin.go | 9 + .../mattermost-server/model/bundle_info.go | 32 + .../mattermost/mattermost-server/model/channel.go | 222 ++ .../mattermost-server/model/channel_count.go | 54 + .../mattermost-server/model/channel_data.go | 34 + .../mattermost-server/model/channel_list.go | 53 + .../mattermost-server/model/channel_member.go | 156 + .../model/channel_member_history.go | 11 + .../model/channel_member_history_result.go | 15 + .../mattermost-server/model/channel_mentions.go | 28 + .../mattermost-server/model/channel_search.go | 26 + .../mattermost-server/model/channel_stats.go | 25 + .../mattermost-server/model/channel_view.go | 41 + .../mattermost/mattermost-server/model/client4.go | 3839 ++++++++++++++++++++ .../mattermost-server/model/cluster_discovery.go | 133 + .../mattermost-server/model/cluster_info.go | 44 + .../mattermost-server/model/cluster_message.go | 48 + .../mattermost-server/model/cluster_stats.go | 27 + .../mattermost/mattermost-server/model/command.go | 139 + .../mattermost-server/model/command_args.go | 34 + .../mattermost-server/model/command_response.go | 67 + .../mattermost-server/model/command_webhook.go | 65 + .../mattermost-server/model/compliance.go | 119 + .../mattermost-server/model/compliance_post.go | 117 + .../mattermost/mattermost-server/model/config.go | 2569 +++++++++++++ .../model/data_retention_policy.go | 27 + .../mattermost/mattermost-server/model/emoji.go | 88 + .../mattermost-server/model/emoji_data.go | 6 + .../mattermost-server/model/emoji_search.go | 25 + .../mattermost/mattermost-server/model/file.go | 34 + .../mattermost-server/model/file_info.go | 170 + .../mattermost/mattermost-server/model/gitlab.go | 8 + .../mattermost-server/model/incoming_webhook.go | 216 ++ .../mattermost-server/model/initial_load.go | 30 + .../mattermost/mattermost-server/model/job.go | 120 + .../mattermost/mattermost-server/model/ldap.go | 8 + .../mattermost/mattermost-server/model/license.go | 218 ++ .../mattermost/mattermost-server/model/manifest.go | 294 ++ .../mattermost-server/model/message_export.go | 27 + .../mattermost-server/model/mfa_secret.go | 25 + .../mattermost-server/model/migration.go | 8 + .../mattermost/mattermost-server/model/oauth.go | 152 + .../mattermost-server/model/outgoing_webhook.go | 264 ++ .../mattermost-server/model/permission.go | 466 +++ .../mattermost-server/model/plugin_key_value.go | 32 + .../mattermost-server/model/plugin_status.go | 42 + .../mattermost-server/model/plugins_response.go | 29 + .../mattermost/mattermost-server/model/post.go | 587 +++ .../mattermost-server/model/post_list.go | 138 + .../mattermost-server/model/post_search_results.go | 40 + .../mattermost-server/model/preference.go | 118 + .../mattermost-server/model/preferences.go | 27 + .../mattermost-server/model/push_notification.go | 70 + .../mattermost-server/model/push_response.go | 54 + .../mattermost/mattermost-server/model/reaction.go | 76 + .../mattermost/mattermost-server/model/role.go | 363 ++ .../mattermost/mattermost-server/model/saml.go | 37 + .../mattermost-server/model/scheduled_task.go | 77 + .../mattermost/mattermost-server/model/scheme.go | 208 ++ .../mattermost-server/model/search_params.go | 228 ++ .../mattermost-server/model/security_bulletin.go | 41 + .../mattermost/mattermost-server/model/session.go | 164 + .../mattermost-server/model/slack_attachment.go | 84 + .../mattermost/mattermost-server/model/status.go | 80 + .../mattermost-server/model/suggest_command.go | 25 + .../mattermost-server/model/switch_request.go | 53 + .../mattermost/mattermost-server/model/system.go | 47 + .../mattermost/mattermost-server/model/team.go | 296 ++ .../mattermost-server/model/team_member.go | 102 + .../mattermost-server/model/team_search.go | 35 + .../mattermost-server/model/team_stats.go | 26 + .../mattermost-server/model/terms_of_service.go | 70 + .../mattermost/mattermost-server/model/timezone.go | 628 ++++ .../mattermost/mattermost-server/model/token.go | 40 + .../mattermost/mattermost-server/model/user.go | 641 ++++ .../mattermost-server/model/user_access_token.go | 65 + .../model/user_access_token_search.go | 35 + .../mattermost-server/model/user_autocomplete.go | 61 + .../mattermost-server/model/user_search.go | 32 + .../mattermost-server/model/users_stats.go | 24 + .../mattermost/mattermost-server/model/utils.go | 566 +++ .../mattermost/mattermost-server/model/version.go | 156 + .../mattermost/mattermost-server/model/webrtc.go | 39 + .../mattermost-server/model/websocket_client.go | 195 + .../mattermost-server/model/websocket_message.go | 165 + .../mattermost-server/model/websocket_request.go | 34 + .../mattermost-server/utils/jsonutils/json.go | 56 + .../mattermost-server/utils/markdown/autolink.go | 253 ++ .../utils/markdown/block_quote.go | 62 + .../mattermost-server/utils/markdown/blocks.go | 153 + .../mattermost-server/utils/markdown/document.go | 22 + .../utils/markdown/fenced_code.go | 112 + .../mattermost-server/utils/markdown/html.go | 192 + .../utils/markdown/html_entities.go | 2132 +++++++++++ .../utils/markdown/indented_code.go | 98 + .../mattermost-server/utils/markdown/inlines.go | 664 ++++ .../mattermost-server/utils/markdown/inspect.go | 78 + .../mattermost-server/utils/markdown/lines.go | 27 + .../mattermost-server/utils/markdown/links.go | 184 + .../mattermost-server/utils/markdown/list.go | 220 ++ .../mattermost-server/utils/markdown/markdown.go | 148 + .../mattermost-server/utils/markdown/paragraph.go | 71 + .../utils/markdown/reference_definition.go | 75 + vendor/github.com/mattermost/platform/LICENSE.txt | 897 ----- vendor/github.com/mattermost/platform/NOTICE.txt | 2935 --------------- .../github.com/mattermost/platform/model/access.go | 114 - .../mattermost/platform/model/analytics_row.go | 55 - .../github.com/mattermost/platform/model/audit.go | 39 - .../github.com/mattermost/platform/model/audits.go | 39 - .../mattermost/platform/model/authorization.go | 522 --- .../mattermost/platform/model/authorize.go | 159 - .../mattermost/platform/model/builtin.go | 9 - .../mattermost/platform/model/bundle_info.go | 23 - .../mattermost/platform/model/channel.go | 226 -- .../mattermost/platform/model/channel_count.go | 63 - .../mattermost/platform/model/channel_data.go | 43 - .../mattermost/platform/model/channel_list.go | 63 - .../mattermost/platform/model/channel_member.go | 171 - .../platform/model/channel_member_history.go | 12 - .../mattermost/platform/model/channel_search.go | 35 - .../mattermost/platform/model/channel_stats.go | 34 - .../mattermost/platform/model/channel_view.go | 59 - .../github.com/mattermost/platform/model/client.go | 2379 ------------ .../mattermost/platform/model/client4.go | 3225 ---------------- .../mattermost/platform/model/cluster_discovery.go | 133 - .../mattermost/platform/model/cluster_info.go | 62 - .../mattermost/platform/model/cluster_message.go | 55 - .../mattermost/platform/model/cluster_stats.go | 36 - .../mattermost/platform/model/command.go | 157 - .../mattermost/platform/model/command_args.go | 43 - .../mattermost/platform/model/command_response.go | 65 - .../mattermost/platform/model/command_webhook.go | 65 - .../mattermost/platform/model/compliance.go | 133 - .../mattermost/platform/model/compliance_post.go | 114 - .../github.com/mattermost/platform/model/config.go | 2168 ----------- .../platform/model/data_retention_policy.go | 36 - .../github.com/mattermost/platform/model/emoji.go | 96 - .../github.com/mattermost/platform/model/file.go | 43 - .../mattermost/platform/model/file_info.go | 178 - .../github.com/mattermost/platform/model/gitlab.go | 8 - .../mattermost/platform/model/incoming_webhook.go | 224 -- .../mattermost/platform/model/initial_load.go | 39 - vendor/github.com/mattermost/platform/model/job.go | 127 - .../github.com/mattermost/platform/model/ldap.go | 9 - .../mattermost/platform/model/license.go | 209 -- .../mattermost/platform/model/manifest.go | 246 -- .../mattermost/platform/model/message_export.go | 18 - .../mattermost/platform/model/mfa_secret.go | 34 - .../github.com/mattermost/platform/model/oauth.go | 191 - .../mattermost/platform/model/outgoing_webhook.go | 284 -- .../mattermost/platform/model/plugin_key_value.go | 32 - .../mattermost/platform/model/plugins_response.go | 39 - .../github.com/mattermost/platform/model/post.go | 394 -- .../mattermost/platform/model/post_list.go | 134 - .../mattermost/platform/model/preference.go | 122 - .../mattermost/platform/model/preferences.go | 31 - .../mattermost/platform/model/push_notification.go | 77 - .../mattermost/platform/model/push_response.go | 57 - .../mattermost/platform/model/reaction.go | 82 - .../github.com/mattermost/platform/model/saml.go | 49 - .../mattermost/platform/model/scheduled_task.go | 110 - .../mattermost/platform/model/search_params.go | 175 - .../mattermost/platform/model/security_bulletin.go | 55 - .../mattermost/platform/model/session.go | 151 - .../mattermost/platform/model/slack_attachment.go | 59 - .../github.com/mattermost/platform/model/status.go | 78 - .../mattermost/platform/model/suggest_command.go | 34 - .../mattermost/platform/model/switch_request.go | 62 - .../github.com/mattermost/platform/model/system.go | 42 - .../github.com/mattermost/platform/model/team.go | 330 -- .../mattermost/platform/model/team_member.go | 122 - .../mattermost/platform/model/team_search.go | 35 - .../mattermost/platform/model/team_stats.go | 35 - .../github.com/mattermost/platform/model/token.go | 40 - .../github.com/mattermost/platform/model/user.go | 653 ---- .../mattermost/platform/model/user_access_token.go | 83 - .../mattermost/platform/model/user_autocomplete.go | 83 - .../mattermost/platform/model/user_search.go | 41 - .../github.com/mattermost/platform/model/utils.go | 494 --- .../mattermost/platform/model/version.go | 147 - .../github.com/mattermost/platform/model/webrtc.go | 53 - .../mattermost/platform/model/websocket_client.go | 167 - .../mattermost/platform/model/websocket_message.go | 149 - .../mattermost/platform/model/websocket_request.go | 43 - .../github.com/mitchellh/mapstructure/.travis.yml | 2 +- .../github.com/mitchellh/mapstructure/CHANGELOG.md | 21 + .../mitchellh/mapstructure/decode_hooks.go | 46 + vendor/github.com/mitchellh/mapstructure/go.mod | 1 + .../mitchellh/mapstructure/mapstructure.go | 176 +- vendor/github.com/pelletier/go-toml/.travis.yml | 5 +- vendor/github.com/pelletier/go-toml/marshal.go | 13 +- vendor/github.com/pelletier/go-toml/test.sh | 3 - vendor/github.com/pelletier/go-toml/toml.go | 58 + .../github.com/pelletier/go-toml/tomltree_write.go | 50 +- .../github.com/peterhellberg/emojilib/.travis.yml | 9 +- vendor/github.com/peterhellberg/emojilib/LICENSE | 2 +- vendor/github.com/peterhellberg/emojilib/README.md | 2 +- .../github.com/peterhellberg/emojilib/generated.go | 1194 +++++- vendor/github.com/rs/xid/hostid_darwin.go | 4 +- vendor/github.com/rs/xid/hostid_freebsd.go | 4 +- vendor/github.com/rs/xid/hostid_windows.go | 21 +- vendor/github.com/rs/xid/id.go | 12 +- vendor/github.com/spf13/afero/.travis.yml | 2 +- vendor/github.com/spf13/afero/basepath.go | 47 +- vendor/github.com/spf13/afero/cacheOnReadFs.go | 6 +- vendor/github.com/spf13/afero/copyOnWriteFs.go | 53 +- vendor/github.com/spf13/afero/go.mod | 1 + vendor/github.com/spf13/afero/lstater.go | 27 + vendor/github.com/spf13/afero/match.go | 4 +- vendor/github.com/spf13/afero/mem/file.go | 3 + vendor/github.com/spf13/afero/os.go | 7 + vendor/github.com/spf13/afero/path.go | 18 +- vendor/github.com/spf13/afero/readonlyfs.go | 10 + vendor/github.com/spf13/afero/unionFile.go | 189 +- vendor/github.com/spf13/cast/.travis.yml | 5 +- vendor/github.com/spf13/cast/cast.go | 12 + vendor/github.com/spf13/cast/caste.go | 83 + vendor/github.com/spf13/cast/go.mod | 7 + vendor/github.com/spf13/cast/go.sum | 6 + .../github.com/spf13/jwalterweatherman/README.md | 2 +- vendor/github.com/spf13/jwalterweatherman/go.mod | 1 + vendor/github.com/spf13/pflag/bytes.go | 209 ++ vendor/github.com/spf13/pflag/flag.go | 104 +- vendor/github.com/spf13/pflag/golangflag.go | 4 + vendor/github.com/spf13/pflag/string_to_int.go | 149 + vendor/github.com/spf13/pflag/string_to_string.go | 160 + vendor/github.com/spf13/viper/.travis.yml | 5 +- vendor/github.com/spf13/viper/README.md | 45 +- vendor/github.com/spf13/viper/go.mod | 16 + vendor/github.com/spf13/viper/go.sum | 26 + vendor/github.com/spf13/viper/nohup.out | 1 - vendor/github.com/spf13/viper/viper.go | 75 +- 327 files changed, 29950 insertions(+), 22792 deletions(-) delete mode 100644 vendor/github.com/alecthomas/log4go/.gitignore delete mode 100644 vendor/github.com/alecthomas/log4go/LICENSE delete mode 100644 vendor/github.com/alecthomas/log4go/README delete mode 100644 vendor/github.com/alecthomas/log4go/config.go delete mode 100644 vendor/github.com/alecthomas/log4go/filelog.go delete mode 100644 vendor/github.com/alecthomas/log4go/log4go.go delete mode 100644 vendor/github.com/alecthomas/log4go/pattlog.go delete mode 100644 vendor/github.com/alecthomas/log4go/socklog.go delete mode 100644 vendor/github.com/alecthomas/log4go/termlog.go delete mode 100644 vendor/github.com/alecthomas/log4go/wrapper.go create mode 100644 vendor/github.com/go-telegram-bot-api/telegram-bot-api/log.go create mode 100644 vendor/github.com/go-telegram-bot-api/telegram-bot-api/passport.go create mode 100644 vendor/github.com/hashicorp/golang-lru/doc.go create mode 100644 vendor/github.com/hashicorp/golang-lru/go.mod create mode 100644 vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go create mode 100644 vendor/github.com/hashicorp/hcl/go.mod create mode 100644 vendor/github.com/hashicorp/hcl/go.sum delete mode 100644 vendor/github.com/labstack/echo/echo_go1.8.go delete mode 100644 vendor/github.com/labstack/echo/util_go17.go delete mode 100644 vendor/github.com/labstack/echo/util_go18.go create mode 100644 vendor/github.com/lrstanley/girc/go.mod create mode 100644 vendor/github.com/mattermost/mattermost-server/LICENSE.txt create mode 100644 vendor/github.com/mattermost/mattermost-server/NOTICE.txt create mode 100644 vendor/github.com/mattermost/mattermost-server/mlog/default.go create mode 100644 vendor/github.com/mattermost/mattermost-server/mlog/global.go create mode 100644 vendor/github.com/mattermost/mattermost-server/mlog/log.go create mode 100644 vendor/github.com/mattermost/mattermost-server/mlog/stdlog.go create mode 100644 vendor/github.com/mattermost/mattermost-server/mlog/sugar.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/access.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/analytics_row.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/audit.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/audits.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/authorize.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/builtin.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/bundle_info.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_count.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_data.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_list.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_member.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_member_history.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_member_history_result.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_mentions.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_search.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_stats.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/channel_view.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/client4.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/cluster_discovery.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/cluster_info.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/cluster_message.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/cluster_stats.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/command.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/command_args.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/command_response.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/command_webhook.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/compliance.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/compliance_post.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/config.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/data_retention_policy.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/emoji.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/emoji_data.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/emoji_search.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/file.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/file_info.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/gitlab.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/incoming_webhook.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/initial_load.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/job.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/ldap.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/license.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/manifest.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/message_export.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/mfa_secret.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/migration.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/oauth.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/outgoing_webhook.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/permission.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/plugin_key_value.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/plugin_status.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/plugins_response.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/post.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/post_list.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/post_search_results.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/preference.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/preferences.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/push_notification.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/push_response.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/reaction.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/role.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/saml.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/scheduled_task.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/scheme.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/search_params.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/security_bulletin.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/session.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/slack_attachment.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/status.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/suggest_command.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/switch_request.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/system.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/team.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/team_member.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/team_search.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/team_stats.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/terms_of_service.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/timezone.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/token.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/user.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/user_access_token.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/user_access_token_search.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/user_autocomplete.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/user_search.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/users_stats.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/utils.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/version.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/webrtc.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/websocket_client.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/websocket_message.go create mode 100644 vendor/github.com/mattermost/mattermost-server/model/websocket_request.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/jsonutils/json.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/autolink.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/block_quote.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/blocks.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/document.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/fenced_code.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/html.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/html_entities.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/indented_code.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/inlines.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/inspect.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/lines.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/links.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/list.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/markdown.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/paragraph.go create mode 100644 vendor/github.com/mattermost/mattermost-server/utils/markdown/reference_definition.go delete mode 100644 vendor/github.com/mattermost/platform/LICENSE.txt delete mode 100644 vendor/github.com/mattermost/platform/NOTICE.txt delete mode 100644 vendor/github.com/mattermost/platform/model/access.go delete mode 100644 vendor/github.com/mattermost/platform/model/analytics_row.go delete mode 100644 vendor/github.com/mattermost/platform/model/audit.go delete mode 100644 vendor/github.com/mattermost/platform/model/audits.go delete mode 100644 vendor/github.com/mattermost/platform/model/authorization.go delete mode 100644 vendor/github.com/mattermost/platform/model/authorize.go delete mode 100644 vendor/github.com/mattermost/platform/model/builtin.go delete mode 100644 vendor/github.com/mattermost/platform/model/bundle_info.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_count.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_data.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_list.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_member.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_member_history.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_search.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_stats.go delete mode 100644 vendor/github.com/mattermost/platform/model/channel_view.go delete mode 100644 vendor/github.com/mattermost/platform/model/client.go delete mode 100644 vendor/github.com/mattermost/platform/model/client4.go delete mode 100644 vendor/github.com/mattermost/platform/model/cluster_discovery.go delete mode 100644 vendor/github.com/mattermost/platform/model/cluster_info.go delete mode 100644 vendor/github.com/mattermost/platform/model/cluster_message.go delete mode 100644 vendor/github.com/mattermost/platform/model/cluster_stats.go delete mode 100644 vendor/github.com/mattermost/platform/model/command.go delete mode 100644 vendor/github.com/mattermost/platform/model/command_args.go delete mode 100644 vendor/github.com/mattermost/platform/model/command_response.go delete mode 100644 vendor/github.com/mattermost/platform/model/command_webhook.go delete mode 100644 vendor/github.com/mattermost/platform/model/compliance.go delete mode 100644 vendor/github.com/mattermost/platform/model/compliance_post.go delete mode 100644 vendor/github.com/mattermost/platform/model/config.go delete mode 100644 vendor/github.com/mattermost/platform/model/data_retention_policy.go delete mode 100644 vendor/github.com/mattermost/platform/model/emoji.go delete mode 100644 vendor/github.com/mattermost/platform/model/file.go delete mode 100644 vendor/github.com/mattermost/platform/model/file_info.go delete mode 100644 vendor/github.com/mattermost/platform/model/gitlab.go delete mode 100644 vendor/github.com/mattermost/platform/model/incoming_webhook.go delete mode 100644 vendor/github.com/mattermost/platform/model/initial_load.go delete mode 100644 vendor/github.com/mattermost/platform/model/job.go delete mode 100644 vendor/github.com/mattermost/platform/model/ldap.go delete mode 100644 vendor/github.com/mattermost/platform/model/license.go delete mode 100644 vendor/github.com/mattermost/platform/model/manifest.go delete mode 100644 vendor/github.com/mattermost/platform/model/message_export.go delete mode 100644 vendor/github.com/mattermost/platform/model/mfa_secret.go delete mode 100644 vendor/github.com/mattermost/platform/model/oauth.go delete mode 100644 vendor/github.com/mattermost/platform/model/outgoing_webhook.go delete mode 100644 vendor/github.com/mattermost/platform/model/plugin_key_value.go delete mode 100644 vendor/github.com/mattermost/platform/model/plugins_response.go delete mode 100644 vendor/github.com/mattermost/platform/model/post.go delete mode 100644 vendor/github.com/mattermost/platform/model/post_list.go delete mode 100644 vendor/github.com/mattermost/platform/model/preference.go delete mode 100644 vendor/github.com/mattermost/platform/model/preferences.go delete mode 100644 vendor/github.com/mattermost/platform/model/push_notification.go delete mode 100644 vendor/github.com/mattermost/platform/model/push_response.go delete mode 100644 vendor/github.com/mattermost/platform/model/reaction.go delete mode 100644 vendor/github.com/mattermost/platform/model/saml.go delete mode 100644 vendor/github.com/mattermost/platform/model/scheduled_task.go delete mode 100644 vendor/github.com/mattermost/platform/model/search_params.go delete mode 100644 vendor/github.com/mattermost/platform/model/security_bulletin.go delete mode 100644 vendor/github.com/mattermost/platform/model/session.go delete mode 100644 vendor/github.com/mattermost/platform/model/slack_attachment.go delete mode 100644 vendor/github.com/mattermost/platform/model/status.go delete mode 100644 vendor/github.com/mattermost/platform/model/suggest_command.go delete mode 100644 vendor/github.com/mattermost/platform/model/switch_request.go delete mode 100644 vendor/github.com/mattermost/platform/model/system.go delete mode 100644 vendor/github.com/mattermost/platform/model/team.go delete mode 100644 vendor/github.com/mattermost/platform/model/team_member.go delete mode 100644 vendor/github.com/mattermost/platform/model/team_search.go delete mode 100644 vendor/github.com/mattermost/platform/model/team_stats.go delete mode 100644 vendor/github.com/mattermost/platform/model/token.go delete mode 100644 vendor/github.com/mattermost/platform/model/user.go delete mode 100644 vendor/github.com/mattermost/platform/model/user_access_token.go delete mode 100644 vendor/github.com/mattermost/platform/model/user_autocomplete.go delete mode 100644 vendor/github.com/mattermost/platform/model/user_search.go delete mode 100644 vendor/github.com/mattermost/platform/model/utils.go delete mode 100644 vendor/github.com/mattermost/platform/model/version.go delete mode 100644 vendor/github.com/mattermost/platform/model/webrtc.go delete mode 100644 vendor/github.com/mattermost/platform/model/websocket_client.go delete mode 100644 vendor/github.com/mattermost/platform/model/websocket_message.go delete mode 100644 vendor/github.com/mattermost/platform/model/websocket_request.go create mode 100644 vendor/github.com/mitchellh/mapstructure/CHANGELOG.md create mode 100644 vendor/github.com/mitchellh/mapstructure/go.mod create mode 100644 vendor/github.com/spf13/afero/go.mod create mode 100644 vendor/github.com/spf13/afero/lstater.go create mode 100644 vendor/github.com/spf13/cast/go.mod create mode 100644 vendor/github.com/spf13/cast/go.sum create mode 100644 vendor/github.com/spf13/jwalterweatherman/go.mod create mode 100644 vendor/github.com/spf13/pflag/bytes.go create mode 100644 vendor/github.com/spf13/pflag/string_to_int.go create mode 100644 vendor/github.com/spf13/pflag/string_to_string.go create mode 100644 vendor/github.com/spf13/viper/go.mod create mode 100644 vendor/github.com/spf13/viper/go.sum delete mode 100644 vendor/github.com/spf13/viper/nohup.out (limited to 'vendor/github.com') diff --git a/vendor/github.com/Philipp15b/go-steam/auth.go b/vendor/github.com/Philipp15b/go-steam/auth.go index b67de335..16e8c19b 100644 --- a/vendor/github.com/Philipp15b/go-steam/auth.go +++ b/vendor/github.com/Philipp15b/go-steam/auth.go @@ -2,13 +2,14 @@ package steam import ( "crypto/sha1" + "sync/atomic" + "time" + . "github.com/Philipp15b/go-steam/protocol" . "github.com/Philipp15b/go-steam/protocol/protobuf" . "github.com/Philipp15b/go-steam/protocol/steamlang" . "github.com/Philipp15b/go-steam/steamid" "github.com/golang/protobuf/proto" - "sync/atomic" - "time" ) type Auth struct { @@ -19,23 +20,41 @@ type Auth struct { type SentryHash []byte type LogOnDetails struct { - Username string - Password string - AuthCode string + Username string + + // If logging into an account without a login key, the account's password. + Password string + + // If you have a Steam Guard email code, you can provide it here. + AuthCode string + + // If you have a Steam Guard mobile two-factor authentication code, you can provide it here. TwoFactorCode string SentryFileHash SentryHash + LoginKey string + + // true if you want to get a login key which can be used in lieu of + // a password for subsequent logins. false or omitted otherwise. + ShouldRememberPassword bool } // Log on with the given details. You must always specify username and -// password. For the first login, don't set an authcode or a hash and you'll receive an error +// password OR username and loginkey. For the first login, don't set an authcode or a hash and you'll +// receive an error (EResult_AccountLogonDenied) // and Steam will send you an authcode. Then you have to login again, this time with the authcode. // Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows // you to login without using an authcode in the future. // // If you don't use Steam Guard, username and password are enough. +// +// After the event EMsg_ClientNewLoginKey is received you can use the LoginKey +// to login instead of using the password. func (a *Auth) LogOn(details *LogOnDetails) { - if len(details.Username) == 0 || len(details.Password) == 0 { - panic("Username and password must be set!") + if details.Username == "" { + panic("Username must be set!") + } + if details.Password == "" && details.LoginKey == "" { + panic("Password or LoginKey must be set!") } logon := new(CMsgClientLogon) @@ -50,6 +69,12 @@ func (a *Auth) LogOn(details *LogOnDetails) { logon.ClientLanguage = proto.String("english") logon.ProtocolVersion = proto.Uint32(MsgClientLogon_CurrentProtocol) logon.ShaSentryfile = details.SentryFileHash + if details.LoginKey != "" { + logon.LoginKey = proto.String(details.LoginKey) + } + if details.ShouldRememberPassword { + logon.ShouldRememberPassword = proto.Bool(details.ShouldRememberPassword) + } atomic.StoreUint64(&a.client.steamId, uint64(NewIdAdv(0, 1, int32(EUniverse_Public), int32(EAccountType_Individual)))) diff --git a/vendor/github.com/alecthomas/log4go/.gitignore b/vendor/github.com/alecthomas/log4go/.gitignore deleted file mode 100644 index f6207cd8..00000000 --- a/vendor/github.com/alecthomas/log4go/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.sw[op] -.DS_Store diff --git a/vendor/github.com/alecthomas/log4go/LICENSE b/vendor/github.com/alecthomas/log4go/LICENSE deleted file mode 100644 index 7093402b..00000000 --- a/vendor/github.com/alecthomas/log4go/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2010, Kyle Lemons . 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/README b/vendor/github.com/alecthomas/log4go/README deleted file mode 100644 index 3361567f..00000000 --- a/vendor/github.com/alecthomas/log4go/README +++ /dev/null @@ -1,14 +0,0 @@ -# This is an unmaintained fork, left only so it doesn't break imports. - -Please see http://log4go.googlecode.com/ - -Installation: -- Run `goinstall log4go.googlecode.com/hg` - -Usage: -- Add the following import: -import l4g "log4go.googlecode.com/hg" - -Acknowledgements: -- pomack - For providing awesome patches to bring log4go up to the latest Go spec diff --git a/vendor/github.com/alecthomas/log4go/config.go b/vendor/github.com/alecthomas/log4go/config.go deleted file mode 100644 index 577c3eb2..00000000 --- a/vendor/github.com/alecthomas/log4go/config.go +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "encoding/xml" - "fmt" - "io/ioutil" - "os" - "strconv" - "strings" -) - -type xmlProperty struct { - Name string `xml:"name,attr"` - Value string `xml:",chardata"` -} - -type xmlFilter struct { - Enabled string `xml:"enabled,attr"` - Tag string `xml:"tag"` - Level string `xml:"level"` - Type string `xml:"type"` - Property []xmlProperty `xml:"property"` -} - -type xmlLoggerConfig struct { - Filter []xmlFilter `xml:"filter"` -} - -// Load XML configuration; see examples/example.xml for documentation -func (log Logger) LoadConfiguration(filename string) { - log.Close() - - // Open the configuration file - fd, err := os.Open(filename) - if err != nil { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err) - os.Exit(1) - } - - contents, err := ioutil.ReadAll(fd) - if err != nil { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err) - os.Exit(1) - } - - xc := new(xmlLoggerConfig) - if err := xml.Unmarshal(contents, xc); err != nil { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err) - os.Exit(1) - } - - for _, xmlfilt := range xc.Filter { - var filt LogWriter - var lvl Level - bad, good, enabled := false, true, false - - // Check required children - if len(xmlfilt.Enabled) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename) - bad = true - } else { - enabled = xmlfilt.Enabled != "false" - } - if len(xmlfilt.Tag) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename) - bad = true - } - if len(xmlfilt.Type) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename) - bad = true - } - if len(xmlfilt.Level) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename) - bad = true - } - - switch xmlfilt.Level { - case "FINEST": - lvl = FINEST - case "FINE": - lvl = FINE - case "DEBUG": - lvl = DEBUG - case "TRACE": - lvl = TRACE - case "INFO": - lvl = INFO - case "WARNING": - lvl = WARNING - case "ERROR": - lvl = ERROR - case "CRITICAL": - lvl = CRITICAL - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level) - bad = true - } - - // Just so all of the required attributes are errored at the same time if missing - if bad { - os.Exit(1) - } - - switch xmlfilt.Type { - case "console": - filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled) - case "file": - filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled) - case "xml": - filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled) - case "socket": - filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled) - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type) - os.Exit(1) - } - - // Just so all of the required params are errored at the same time if wrong - if !good { - os.Exit(1) - } - - // If we're disabled (syntax and correctness checks only), don't add to logger - if !enabled { - continue - } - - log[xmlfilt.Tag] = &Filter{lvl, filt} - } -} - -func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) { - // Parse properties - for _, prop := range props { - switch prop.Name { - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename) - } - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - return NewConsoleLogWriter(), true -} - -// Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024) -func strToNumSuffix(str string, mult int) int { - num := 1 - if len(str) > 1 { - switch str[len(str)-1] { - case 'G', 'g': - num *= mult - fallthrough - case 'M', 'm': - num *= mult - fallthrough - case 'K', 'k': - num *= mult - str = str[0 : len(str)-1] - } - } - parsed, _ := strconv.Atoi(str) - return parsed * num -} -func xmlToFileLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) { - file := "" - format := "[%D %T] [%L] (%S) %M" - maxlines := 0 - maxsize := 0 - daily := false - rotate := false - - // Parse properties - for _, prop := range props { - switch prop.Name { - case "filename": - file = strings.Trim(prop.Value, " \r\n") - case "format": - format = strings.Trim(prop.Value, " \r\n") - case "maxlines": - maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) - case "maxsize": - maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) - case "daily": - daily = strings.Trim(prop.Value, " \r\n") != "false" - case "rotate": - rotate = strings.Trim(prop.Value, " \r\n") != "false" - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) - } - } - - // Check properties - if len(file) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename) - return nil, false - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - flw := NewFileLogWriter(file, rotate) - flw.SetFormat(format) - flw.SetRotateLines(maxlines) - flw.SetRotateSize(maxsize) - flw.SetRotateDaily(daily) - return flw, true -} - -func xmlToXMLLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) { - file := "" - maxrecords := 0 - maxsize := 0 - daily := false - rotate := false - - // Parse properties - for _, prop := range props { - switch prop.Name { - case "filename": - file = strings.Trim(prop.Value, " \r\n") - case "maxrecords": - maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) - case "maxsize": - maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) - case "daily": - daily = strings.Trim(prop.Value, " \r\n") != "false" - case "rotate": - rotate = strings.Trim(prop.Value, " \r\n") != "false" - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename) - } - } - - // Check properties - if len(file) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename) - return nil, false - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - xlw := NewXMLLogWriter(file, rotate) - xlw.SetRotateLines(maxrecords) - xlw.SetRotateSize(maxsize) - xlw.SetRotateDaily(daily) - return xlw, true -} - -func xmlToSocketLogWriter(filename string, props []xmlProperty, enabled bool) (SocketLogWriter, bool) { - endpoint := "" - protocol := "udp" - - // Parse properties - for _, prop := range props { - switch prop.Name { - case "endpoint": - endpoint = strings.Trim(prop.Value, " \r\n") - case "protocol": - protocol = strings.Trim(prop.Value, " \r\n") - default: - fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) - } - } - - // Check properties - if len(endpoint) == 0 { - fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename) - return nil, false - } - - // If it's disabled, we're just checking syntax - if !enabled { - return nil, true - } - - return NewSocketLogWriter(protocol, endpoint), true -} diff --git a/vendor/github.com/alecthomas/log4go/filelog.go b/vendor/github.com/alecthomas/log4go/filelog.go deleted file mode 100644 index ee0ab0c0..00000000 --- a/vendor/github.com/alecthomas/log4go/filelog.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "fmt" - "os" - "time" -) - -// This log writer sends output to a file -type FileLogWriter struct { - rec chan *LogRecord - rot chan bool - - // The opened file - filename string - file *os.File - - // The logging format - format string - - // File header/trailer - header, trailer string - - // Rotate at linecount - maxlines int - maxlines_curlines int - - // Rotate at size - maxsize int - maxsize_cursize int - - // Rotate daily - daily bool - daily_opendate int - - // Keep old logfiles (.001, .002, etc) - rotate bool - maxbackup int -} - -// This is the FileLogWriter's output method -func (w *FileLogWriter) LogWrite(rec *LogRecord) { - w.rec <- rec -} - -func (w *FileLogWriter) Close() { - close(w.rec) - w.file.Sync() -} - -// NewFileLogWriter creates a new LogWriter which writes to the given file and -// has rotation enabled if rotate is true. -// -// If rotate is true, any time a new log file is opened, the old one is renamed -// with a .### extension to preserve it. The various Set* methods can be used -// to configure log rotation based on lines, size, and daily. -// -// The standard log-line format is: -// [%D %T] [%L] (%S) %M -func NewFileLogWriter(fname string, rotate bool) *FileLogWriter { - w := &FileLogWriter{ - rec: make(chan *LogRecord, LogBufferLength), - rot: make(chan bool), - filename: fname, - format: "[%D %T] [%L] (%S) %M", - rotate: rotate, - maxbackup: 999, - } - - // open the file for the first time - if err := w.intRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return nil - } - - go func() { - defer func() { - if w.file != nil { - fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) - w.file.Close() - } - }() - - for { - select { - case <-w.rot: - if err := w.intRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return - } - case rec, ok := <-w.rec: - if !ok { - return - } - now := time.Now() - if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) || - (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) || - (w.daily && now.Day() != w.daily_opendate) { - if err := w.intRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return - } - } - - // Perform the write - n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec)) - if err != nil { - fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) - return - } - - // Update the counts - w.maxlines_curlines++ - w.maxsize_cursize += n - } - } - }() - - return w -} - -// Request that the logs rotate -func (w *FileLogWriter) Rotate() { - w.rot <- true -} - -// If this is called in a threaded context, it MUST be synchronized -func (w *FileLogWriter) intRotate() error { - // Close any log file that may be open - if w.file != nil { - fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) - w.file.Close() - } - - // If we are keeping log files, move it to the next available number - if w.rotate { - _, err := os.Lstat(w.filename) - if err == nil { // file exists - // Find the next available number - num := 1 - fname := "" - if w.daily && time.Now().Day() != w.daily_opendate { - yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02") - - for ; err == nil && num <= 999; num++ { - fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num) - _, err = os.Lstat(fname) - } - // return error if the last file checked still existed - if err == nil { - return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename) - } - } else { - num = w.maxbackup - 1 - for ; num >= 1; num-- { - fname = w.filename + fmt.Sprintf(".%d", num) - nfname := w.filename + fmt.Sprintf(".%d", num+1) - _, err = os.Lstat(fname) - if err == nil { - os.Rename(fname, nfname) - } - } - } - - w.file.Close() - // Rename the file to its newfound home - err = os.Rename(w.filename, fname) - if err != nil { - return fmt.Errorf("Rotate: %s\n", err) - } - } - } - - // Open the log file - fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) - if err != nil { - return err - } - w.file = fd - - now := time.Now() - fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now})) - - // Set the daily open date to the current date - w.daily_opendate = now.Day() - - // initialize rotation values - w.maxlines_curlines = 0 - w.maxsize_cursize = 0 - - return nil -} - -// Set the logging format (chainable). Must be called before the first log -// message is written. -func (w *FileLogWriter) SetFormat(format string) *FileLogWriter { - w.format = format - return w -} - -// Set the logfile header and footer (chainable). Must be called before the first log -// message is written. These are formatted similar to the FormatLogRecord (e.g. -// you can use %D and %T in your header/footer for date and time). -func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter { - w.header, w.trailer = head, foot - if w.maxlines_curlines == 0 { - fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()})) - } - return w -} - -// Set rotate at linecount (chainable). Must be called before the first log -// message is written. -func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines) - w.maxlines = maxlines - return w -} - -// Set rotate at size (chainable). Must be called before the first log message -// is written. -func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize) - w.maxsize = maxsize - return w -} - -// Set rotate daily (chainable). Must be called before the first log message is -// written. -func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily) - w.daily = daily - return w -} - -// Set max backup files. Must be called before the first log message -// is written. -func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter { - w.maxbackup = maxbackup - return w -} - -// SetRotate changes whether or not the old logs are kept. (chainable) Must be -// called before the first log message is written. If rotate is false, the -// files are overwritten; otherwise, they are rotated to another file before the -// new log is opened. -func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter { - //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate) - w.rotate = rotate - return w -} - -// NewXMLLogWriter is a utility method for creating a FileLogWriter set up to -// output XML record log messages instead of line-based ones. -func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter { - return NewFileLogWriter(fname, rotate).SetFormat( - ` - %D %T - %S - %M - `).SetHeadFoot("", "") -} diff --git a/vendor/github.com/alecthomas/log4go/log4go.go b/vendor/github.com/alecthomas/log4go/log4go.go deleted file mode 100644 index 822e890c..00000000 --- a/vendor/github.com/alecthomas/log4go/log4go.go +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -// Package log4go provides level-based and highly configurable logging. -// -// Enhanced Logging -// -// This is inspired by the logging functionality in Java. Essentially, you create a Logger -// object and create output filters for it. You can send whatever you want to the Logger, -// and it will filter that based on your settings and send it to the outputs. This way, you -// can put as much debug code in your program as you want, and when you're done you can filter -// out the mundane messages so only the important ones show up. -// -// Utility functions are provided to make life easier. Here is some example code to get started: -// -// log := log4go.NewLogger() -// log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter()) -// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) -// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) -// -// The first two lines can be combined with the utility NewDefaultLogger: -// -// log := log4go.NewDefaultLogger(log4go.DEBUG) -// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) -// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) -// -// Usage notes: -// - The ConsoleLogWriter does not display the source of the message to standard -// output, but the FileLogWriter does. -// - The utility functions (Info, Debug, Warn, etc) derive their source from the -// calling function, and this incurs extra overhead. -// -// Changes from 2.0: -// - The external interface has remained mostly stable, but a lot of the -// internals have been changed, so if you depended on any of this or created -// your own LogWriter, then you will probably have to update your code. In -// particular, Logger is now a map and ConsoleLogWriter is now a channel -// behind-the-scenes, and the LogWrite method no longer has return values. -// -// Future work: (please let me know if you think I should work on any of these particularly) -// - Log file rotation -// - Logging configuration files ala log4j -// - Have the ability to remove filters? -// - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows -// for another method of logging -// - Add an XML filter type -package log4go - -import ( - "errors" - "fmt" - "os" - "runtime" - "strings" - "time" -) - -// Version information -const ( - L4G_VERSION = "log4go-v3.0.1" - L4G_MAJOR = 3 - L4G_MINOR = 0 - L4G_BUILD = 1 -) - -/****** Constants ******/ - -// These are the integer logging levels used by the logger -type Level int - -const ( - FINEST Level = iota - FINE - DEBUG - TRACE - INFO - WARNING - ERROR - CRITICAL -) - -// Logging level strings -var ( - levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} -) - -func (l Level) String() string { - if l < 0 || int(l) > len(levelStrings) { - return "UNKNOWN" - } - return levelStrings[int(l)] -} - -/****** Variables ******/ -var ( - // LogBufferLength specifies how many log messages a particular log4go - // logger can buffer at a time before writing them. - LogBufferLength = 32 -) - -/****** LogRecord ******/ - -// A LogRecord contains all of the pertinent information for each message -type LogRecord struct { - Level Level // The log level - Created time.Time // The time at which the log message was created (nanoseconds) - Source string // The message source - Message string // The log message -} - -/****** LogWriter ******/ - -// This is an interface for anything that should be able to write logs -type LogWriter interface { - // This will be called to log a LogRecord message. - LogWrite(rec *LogRecord) - - // This should clean up anything lingering about the LogWriter, as it is called before - // the LogWriter is removed. LogWrite should not be called after Close. - Close() -} - -/****** Logger ******/ - -// A Filter represents the log level below which no log records are written to -// the associated LogWriter. -type Filter struct { - Level Level - LogWriter -} - -// A Logger represents a collection of Filters through which log messages are -// written. -type Logger map[string]*Filter - -// Create a new logger. -// -// DEPRECATED: Use make(Logger) instead. -func NewLogger() Logger { - os.Stderr.WriteString("warning: use of deprecated NewLogger\n") - return make(Logger) -} - -// Create a new logger with a "stdout" filter configured to send log messages at -// or above lvl to standard output. -// -// DEPRECATED: use NewDefaultLogger instead. -func NewConsoleLogger(lvl Level) Logger { - os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n") - return Logger{ - "stdout": &Filter{lvl, NewConsoleLogWriter()}, - } -} - -// Create a new logger with a "stdout" filter configured to send log messages at -// or above lvl to standard output. -func NewDefaultLogger(lvl Level) Logger { - return Logger{ - "stdout": &Filter{lvl, NewConsoleLogWriter()}, - } -} - -// Closes all log writers in preparation for exiting the program or a -// reconfiguration of logging. Calling this is not really imperative, unless -// you want to guarantee that all log messages are written. Close removes -// all filters (and thus all LogWriters) from the logger. -func (log Logger) Close() { - // Close all open loggers - for name, filt := range log { - filt.Close() - delete(log, name) - } -} - -// Add a new LogWriter to the Logger which will only log messages at lvl or -// higher. This function should not be called from multiple goroutines. -// Returns the logger for chaining. -func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger { - log[name] = &Filter{lvl, writer} - return log -} - -/******* Logging *******/ -// Send a formatted log message internally -func (log Logger) intLogf(lvl Level, format string, args ...interface{}) { - skip := true - - // Determine if any logging will be done - for _, filt := range log { - if lvl >= filt.Level { - skip = false - break - } - } - if skip { - return - } - - // Determine caller func - pc, _, lineno, ok := runtime.Caller(2) - src := "" - if ok { - src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) - } - - msg := format - if len(args) > 0 { - msg = fmt.Sprintf(format, args...) - } - - // Make the log record - rec := &LogRecord{ - Level: lvl, - Created: time.Now(), - Source: src, - Message: msg, - } - - // Dispatch the logs - for _, filt := range log { - if lvl < filt.Level { - continue - } - filt.LogWrite(rec) - } -} - -// Send a closure log message internally -func (log Logger) intLogc(lvl Level, closure func() string) { - skip := true - - // Determine if any logging will be done - for _, filt := range log { - if lvl >= filt.Level { - skip = false - break - } - } - if skip { - return - } - - // Determine caller func - pc, _, lineno, ok := runtime.Caller(2) - src := "" - if ok { - src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) - } - - // Make the log record - rec := &LogRecord{ - Level: lvl, - Created: time.Now(), - Source: src, - Message: closure(), - } - - // Dispatch the logs - for _, filt := range log { - if lvl < filt.Level { - continue - } - filt.LogWrite(rec) - } -} - -// Send a log message with manual level, source, and message. -func (log Logger) Log(lvl Level, source, message string) { - skip := true - - // Determine if any logging will be done - for _, filt := range log { - if lvl >= filt.Level { - skip = false - break - } - } - if skip { - return - } - - // Make the log record - rec := &LogRecord{ - Level: lvl, - Created: time.Now(), - Source: source, - Message: message, - } - - // Dispatch the logs - for _, filt := range log { - if lvl < filt.Level { - continue - } - filt.LogWrite(rec) - } -} - -// Logf logs a formatted log message at the given log level, using the caller as -// its source. -func (log Logger) Logf(lvl Level, format string, args ...interface{}) { - log.intLogf(lvl, format, args...) -} - -// Logc logs a string returned by the closure at the given log level, using the caller as -// its source. If no log message would be written, the closure is never called. -func (log Logger) Logc(lvl Level, closure func() string) { - log.intLogc(lvl, closure) -} - -// Finest logs a message at the finest log level. -// See Debug for an explanation of the arguments. -func (log Logger) Finest(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINEST - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Fine logs a message at the fine log level. -// See Debug for an explanation of the arguments. -func (log Logger) Fine(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Debug is a utility method for debug log messages. -// The behavior of Debug depends on the first argument: -// - arg0 is a string -// When given a string as the first argument, this behaves like Logf but with -// the DEBUG log level: the first argument is interpreted as a format for the -// latter arguments. -// - arg0 is a func()string -// When given a closure of type func()string, this logs the string returned by -// the closure iff it will be logged. The closure runs at most one time. -// - arg0 is interface{} -// When given anything else, the log message will be each of the arguments -// formatted with %v and separated by spaces (ala Sprint). -func (log Logger) Debug(arg0 interface{}, args ...interface{}) { - const ( - lvl = DEBUG - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Trace logs a message at the trace log level. -// See Debug for an explanation of the arguments. -func (log Logger) Trace(arg0 interface{}, args ...interface{}) { - const ( - lvl = TRACE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Info logs a message at the info log level. -// See Debug for an explanation of the arguments. -func (log Logger) Info(arg0 interface{}, args ...interface{}) { - const ( - lvl = INFO - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - log.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - log.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Warn logs a message at the warning log level and returns the formatted error. -// At the warning level and higher, there is no performance benefit if the -// message is not actually logged, because all formats are processed and all -// closures are executed to format the error message. -// See Debug for further explanation of the arguments. -func (log Logger) Warn(arg0 interface{}, args ...interface{}) error { - const ( - lvl = WARNING - ) - var msg string - switch first := arg0.(type) { - case string: - // Use the string as a format string - msg = fmt.Sprintf(first, args...) - case func() string: - // Log the closure (no other arguments used) - msg = first() - default: - // Build a format string so that it will be similar to Sprint - msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - } - log.intLogf(lvl, msg) - return errors.New(msg) -} - -// Error logs a message at the error log level and returns the formatted error, -// See Warn for an explanation of the performance and Debug for an explanation -// of the parameters. -func (log Logger) Error(arg0 interface{}, args ...interface{}) error { - const ( - lvl = ERROR - ) - var msg string - switch first := arg0.(type) { - case string: - // Use the string as a format string - msg = fmt.Sprintf(first, args...) - case func() string: - // Log the closure (no other arguments used) - msg = first() - default: - // Build a format string so that it will be similar to Sprint - msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - } - log.intLogf(lvl, msg) - return errors.New(msg) -} - -// Critical logs a message at the critical log level and returns the formatted error, -// See Warn for an explanation of the performance and Debug for an explanation -// of the parameters. -func (log Logger) Critical(arg0 interface{}, args ...interface{}) error { - const ( - lvl = CRITICAL - ) - var msg string - switch first := arg0.(type) { - case string: - // Use the string as a format string - msg = fmt.Sprintf(first, args...) - case func() string: - // Log the closure (no other arguments used) - msg = first() - default: - // Build a format string so that it will be similar to Sprint - msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - } - log.intLogf(lvl, msg) - return errors.New(msg) -} diff --git a/vendor/github.com/alecthomas/log4go/pattlog.go b/vendor/github.com/alecthomas/log4go/pattlog.go deleted file mode 100644 index 82b4e36b..00000000 --- a/vendor/github.com/alecthomas/log4go/pattlog.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "bytes" - "fmt" - "io" - "strings" -) - -const ( - FORMAT_DEFAULT = "[%D %T] [%L] (%S) %M" - FORMAT_SHORT = "[%t %d] [%L] %M" - FORMAT_ABBREV = "[%L] %M" -) - -type formatCacheType struct { - LastUpdateSeconds int64 - shortTime, shortDate string - longTime, longDate string -} - -var formatCache = &formatCacheType{} - -// Known format codes: -// %T - Time (15:04:05 MST) -// %t - Time (15:04) -// %D - Date (2006/01/02) -// %d - Date (01/02/06) -// %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT) -// %S - Source -// %M - Message -// Ignores unknown formats -// Recommended: "[%D %T] [%L] (%S) %M" -func FormatLogRecord(format string, rec *LogRecord) string { - if rec == nil { - return "" - } - if len(format) == 0 { - return "" - } - - out := bytes.NewBuffer(make([]byte, 0, 64)) - secs := rec.Created.UnixNano() / 1e9 - - cache := *formatCache - if cache.LastUpdateSeconds != secs { - month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year() - hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second() - zone, _ := rec.Created.Zone() - updated := &formatCacheType{ - LastUpdateSeconds: secs, - shortTime: fmt.Sprintf("%02d:%02d", hour, minute), - shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100), - longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone), - longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day), - } - cache = *updated - formatCache = updated - } - - // Split the string into pieces by % signs - pieces := bytes.Split([]byte(format), []byte{'%'}) - - // Iterate over the pieces, replacing known formats - for i, piece := range pieces { - if i > 0 && len(piece) > 0 { - switch piece[0] { - case 'T': - out.WriteString(cache.longTime) - case 't': - out.WriteString(cache.shortTime) - case 'D': - out.WriteString(cache.longDate) - case 'd': - out.WriteString(cache.shortDate) - case 'L': - out.WriteString(levelStrings[rec.Level]) - case 'S': - out.WriteString(rec.Source) - case 's': - slice := strings.Split(rec.Source, "/") - out.WriteString(slice[len(slice)-1]) - case 'M': - out.WriteString(rec.Message) - } - if len(piece) > 1 { - out.Write(piece[1:]) - } - } else if len(piece) > 0 { - out.Write(piece) - } - } - out.WriteByte('\n') - - return out.String() -} - -// This is the standard writer that prints to standard output. -type FormatLogWriter chan *LogRecord - -// This creates a new FormatLogWriter -func NewFormatLogWriter(out io.Writer, format string) FormatLogWriter { - records := make(FormatLogWriter, LogBufferLength) - go records.run(out, format) - return records -} - -func (w FormatLogWriter) run(out io.Writer, format string) { - for rec := range w { - fmt.Fprint(out, FormatLogRecord(format, rec)) - } -} - -// This is the FormatLogWriter's output method. This will block if the output -// buffer is full. -func (w FormatLogWriter) LogWrite(rec *LogRecord) { - w <- rec -} - -// Close stops the logger from sending messages to standard output. Attempts to -// send log messages to this logger after a Close have undefined behavior. -func (w FormatLogWriter) Close() { - close(w) -} diff --git a/vendor/github.com/alecthomas/log4go/socklog.go b/vendor/github.com/alecthomas/log4go/socklog.go deleted file mode 100644 index 1d224a99..00000000 --- a/vendor/github.com/alecthomas/log4go/socklog.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "encoding/json" - "fmt" - "net" - "os" -) - -// This log writer sends output to a socket -type SocketLogWriter chan *LogRecord - -// This is the SocketLogWriter's output method -func (w SocketLogWriter) LogWrite(rec *LogRecord) { - w <- rec -} - -func (w SocketLogWriter) Close() { - close(w) -} - -func NewSocketLogWriter(proto, hostport string) SocketLogWriter { - sock, err := net.Dial(proto, hostport) - if err != nil { - fmt.Fprintf(os.Stderr, "NewSocketLogWriter(%q): %s\n", hostport, err) - return nil - } - - w := SocketLogWriter(make(chan *LogRecord, LogBufferLength)) - - go func() { - defer func() { - if sock != nil && proto == "tcp" { - sock.Close() - } - }() - - for rec := range w { - // Marshall into JSON - js, err := json.Marshal(rec) - if err != nil { - fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err) - return - } - - _, err = sock.Write(js) - if err != nil { - fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err) - return - } - } - }() - - return w -} diff --git a/vendor/github.com/alecthomas/log4go/termlog.go b/vendor/github.com/alecthomas/log4go/termlog.go deleted file mode 100644 index 8a941e26..00000000 --- a/vendor/github.com/alecthomas/log4go/termlog.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "fmt" - "io" - "os" - "time" -) - -var stdout io.Writer = os.Stdout - -// This is the standard writer that prints to standard output. -type ConsoleLogWriter struct { - format string - w chan *LogRecord -} - -// This creates a new ConsoleLogWriter -func NewConsoleLogWriter() *ConsoleLogWriter { - consoleWriter := &ConsoleLogWriter{ - format: "[%T %D] [%L] (%S) %M", - w: make(chan *LogRecord, LogBufferLength), - } - go consoleWriter.run(stdout) - return consoleWriter -} -func (c *ConsoleLogWriter) SetFormat(format string) { - c.format = format -} -func (c *ConsoleLogWriter) run(out io.Writer) { - for rec := range c.w { - fmt.Fprint(out, FormatLogRecord(c.format, rec)) - } -} - -// This is the ConsoleLogWriter's output method. This will block if the output -// buffer is full. -func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) { - c.w <- rec -} - -// Close stops the logger from sending messages to standard output. Attempts to -// send log messages to this logger after a Close have undefined behavior. -func (c *ConsoleLogWriter) Close() { - close(c.w) - time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete -} diff --git a/vendor/github.com/alecthomas/log4go/wrapper.go b/vendor/github.com/alecthomas/log4go/wrapper.go deleted file mode 100644 index 2ae222b0..00000000 --- a/vendor/github.com/alecthomas/log4go/wrapper.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (C) 2010, Kyle Lemons . All rights reserved. - -package log4go - -import ( - "errors" - "fmt" - "os" - "strings" -) - -var ( - Global Logger -) - -func init() { - Global = NewDefaultLogger(DEBUG) -} - -// Wrapper for (*Logger).LoadConfiguration -func LoadConfiguration(filename string) { - Global.LoadConfiguration(filename) -} - -// Wrapper for (*Logger).AddFilter -func AddFilter(name string, lvl Level, writer LogWriter) { - Global.AddFilter(name, lvl, writer) -} - -// Wrapper for (*Logger).Close (closes and removes all logwriters) -func Close() { - Global.Close() -} - -func Crash(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(CRITICAL, strings.Repeat(" %v", len(args))[1:], args...) - } - panic(args) -} - -// Logs the given message and crashes the program -func Crashf(format string, args ...interface{}) { - Global.intLogf(CRITICAL, format, args...) - Global.Close() // so that hopefully the messages get logged - panic(fmt.Sprintf(format, args...)) -} - -// Compatibility with `log` -func Exit(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...) - } - Global.Close() // so that hopefully the messages get logged - os.Exit(0) -} - -// Compatibility with `log` -func Exitf(format string, args ...interface{}) { - Global.intLogf(ERROR, format, args...) - Global.Close() // so that hopefully the messages get logged - os.Exit(0) -} - -// Compatibility with `log` -func Stderr(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...) - } -} - -// Compatibility with `log` -func Stderrf(format string, args ...interface{}) { - Global.intLogf(ERROR, format, args...) -} - -// Compatibility with `log` -func Stdout(args ...interface{}) { - if len(args) > 0 { - Global.intLogf(INFO, strings.Repeat(" %v", len(args))[1:], args...) - } -} - -// Compatibility with `log` -func Stdoutf(format string, args ...interface{}) { - Global.intLogf(INFO, format, args...) -} - -// Send a log message manually -// Wrapper for (*Logger).Log -func Log(lvl Level, source, message string) { - Global.Log(lvl, source, message) -} - -// Send a formatted log message easily -// Wrapper for (*Logger).Logf -func Logf(lvl Level, format string, args ...interface{}) { - Global.intLogf(lvl, format, args...) -} - -// Send a closure log message -// Wrapper for (*Logger).Logc -func Logc(lvl Level, closure func() string) { - Global.intLogc(lvl, closure) -} - -// Utility for finest log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Finest -func Finest(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINEST - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for fine log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Fine -func Fine(arg0 interface{}, args ...interface{}) { - const ( - lvl = FINE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for debug log messages -// When given a string as the first argument, this behaves like Logf but with the DEBUG log level (e.g. the first argument is interpreted as a format for the latter arguments) -// When given a closure of type func()string, this logs the string returned by the closure iff it will be logged. The closure runs at most one time. -// When given anything else, the log message will be each of the arguments formatted with %v and separated by spaces (ala Sprint). -// Wrapper for (*Logger).Debug -func Debug(arg0 interface{}, args ...interface{}) { - const ( - lvl = DEBUG - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for trace log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Trace -func Trace(arg0 interface{}, args ...interface{}) { - const ( - lvl = TRACE - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for info log messages (see Debug() for parameter explanation) -// Wrapper for (*Logger).Info -func Info(arg0 interface{}, args ...interface{}) { - const ( - lvl = INFO - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - case func() string: - // Log the closure (no other arguments used) - Global.intLogc(lvl, first) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) - } -} - -// Utility for warn log messages (returns an error for easy function returns) (see Debug() for parameter explanation) -// These functions will execute a closure exactly once, to build the error message for the return -// Wrapper for (*Logger).Warn -func Warn(arg0 interface{}, args ...interface{}) error { - const ( - lvl = WARNING - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - return errors.New(fmt.Sprintf(first, args...)) - case func() string: - // Log the closure (no other arguments used) - str := first() - Global.intLogf(lvl, "%s", str) - return errors.New(str) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) - } - return nil -} - -// Utility for error log messages (returns an error for easy function returns) (see Debug() for parameter explanation) -// These functions will execute a closure exactly once, to build the error message for the return -// Wrapper for (*Logger).Error -func Error(arg0 interface{}, args ...interface{}) error { - const ( - lvl = ERROR - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - return errors.New(fmt.Sprintf(first, args...)) - case func() string: - // Log the closure (no other arguments used) - str := first() - Global.intLogf(lvl, "%s", str) - return errors.New(str) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) - } - return nil -} - -// Utility for critical log messages (returns an error for easy function returns) (see Debug() for parameter explanation) -// These functions will execute a closure exactly once, to build the error message for the return -// Wrapper for (*Logger).Critical -func Critical(arg0 interface{}, args ...interface{}) error { - const ( - lvl = CRITICAL - ) - switch first := arg0.(type) { - case string: - // Use the string as a format string - Global.intLogf(lvl, first, args...) - return errors.New(fmt.Sprintf(first, args...)) - case func() string: - // Log the closure (no other arguments used) - str := first() - Global.intLogf(lvl, "%s", str) - return errors.New(str) - default: - // Build a format string so that it will be similar to Sprint - Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) - return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) - } - return nil -} diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.gitignore b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.gitignore index aa7ac806..fb5a5e83 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.gitignore +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.gitignore @@ -1,2 +1,3 @@ .idea/ coverage.out +tmp/ diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.travis.yml b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.travis.yml index 8408fb7b..5769aa14 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.travis.yml +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/.travis.yml @@ -1,5 +1,6 @@ language: go go: - - 1.4 - - tip \ No newline at end of file + - '1.10' + - '1.11' + - tip diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/README.md b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/README.md index d9a6873d..93250611 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/README.md +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/README.md @@ -3,10 +3,6 @@ [![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) [![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api) -All methods have been added, and all features should be available. -If you want a feature that hasn't been added yet or something is broken, -open an issue and I'll see what I can do. - All methods are fairly self explanatory, and reading the godoc page should explain everything. If something isn't clear, open an issue or submit a pull request. @@ -16,14 +12,14 @@ without any additional features. There are other projects for creating something with plugins and command handlers without having to design all that yourself. -Use `github.com/go-telegram-bot-api/telegram-bot-api` for the latest -version, or use `gopkg.in/telegram-bot-api.v4` for the stable build. - Join [the development group](https://telegram.me/go_telegram_bot_api) if you want to ask questions or discuss development. ## Example +First, ensure the library is installed and up to date by running +`go get -u github.com/go-telegram-bot-api/telegram-bot-api`. + This is a very simple bot that just displays any gotten updates, then replies it to that chat. @@ -32,7 +28,8 @@ package main import ( "log" - "gopkg.in/telegram-bot-api.v4" + + "github.com/go-telegram-bot-api/telegram-bot-api" ) func main() { @@ -51,7 +48,7 @@ func main() { updates, err := bot.GetUpdatesChan(u) for update := range updates { - if update.Message == nil { + if update.Message == nil { // ignore any non-Message Updates continue } @@ -65,6 +62,11 @@ func main() { } ``` +There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki) +with detailed information on how to do many differen kinds of things. +It's a great place to get started on using keyboards, commands, or other +kinds of reply markup. + If you need to use webhooks (if you wish to run on Google App Engine), you may use a slightly different method. @@ -72,9 +74,10 @@ you may use a slightly different method. package main import ( - "gopkg.in/telegram-bot-api.v4" "log" "net/http" + + "github.com/go-telegram-bot-api/telegram-bot-api" ) func main() { @@ -96,7 +99,7 @@ func main() { log.Fatal(err) } if info.LastErrorDate != 0 { - log.Printf("[Telegram callback failed]%s", info.LastErrorMessage) + log.Printf("Telegram callback failed: %s", info.LastErrorMessage) } updates := bot.ListenForWebhook("/" + bot.Token) go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil) @@ -114,5 +117,5 @@ properly signed. openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes -Now that [Let's Encrypt](https://letsencrypt.org) has entered public beta, +Now that [Let's Encrypt](https://letsencrypt.org) is available, you may wish to generate your free TLS certificate there. diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go index 8fb6200e..d56aaf82 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "os" @@ -28,6 +27,7 @@ type BotAPI struct { Self User `json:"-"` Client *http.Client `json:"-"` + shutdownChannel chan interface{} } // NewBotAPI creates a new BotAPI instance. @@ -46,6 +46,7 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { Token: token, Client: client, Buffer: 100, + shutdownChannel: make(chan interface{}), } self, err := bot.GetMe() @@ -484,6 +485,12 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { go func() { for { + select { + case <-bot.shutdownChannel: + return + default: + } + updates, err := bot.GetUpdates(config) if err != nil { log.Println(err) @@ -505,6 +512,14 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { return ch, nil } +// StopReceivingUpdates stops the go routine which receives updates +func (bot *BotAPI) StopReceivingUpdates() { + if bot.Debug { + log.Println("Stopping the update receiver routine...") + } + close(bot.shutdownChannel) +} + // ListenForWebhook registers a http handler for a webhook. func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { ch := make(chan Update, bot.Buffer) diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go index 574b3dd9..181d4e43 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go @@ -243,7 +243,8 @@ func (config ForwardConfig) method() string { // PhotoConfig contains information about a SendPhoto request. type PhotoConfig struct { BaseFile - Caption string + Caption string + ParseMode string } // Params returns a map[string]string representation of PhotoConfig. @@ -252,6 +253,9 @@ func (config PhotoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -267,7 +271,11 @@ func (config PhotoConfig) values() (url.Values, error) { v.Add(config.name(), config.FileID) if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } + return v, nil } @@ -285,6 +293,7 @@ func (config PhotoConfig) method() string { type AudioConfig struct { BaseFile Caption string + ParseMode string Duration int Performer string Title string @@ -310,6 +319,9 @@ func (config AudioConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -331,6 +343,9 @@ func (config AudioConfig) params() (map[string]string, error) { } if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -349,7 +364,8 @@ func (config AudioConfig) method() string { // DocumentConfig contains information about a SendDocument request. type DocumentConfig struct { BaseFile - Caption string + Caption string + ParseMode string } // values returns a url.Values representation of DocumentConfig. @@ -362,6 +378,9 @@ func (config DocumentConfig) values() (url.Values, error) { v.Add(config.name(), config.FileID) if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -373,6 +392,9 @@ func (config DocumentConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -425,8 +447,9 @@ func (config StickerConfig) method() string { // VideoConfig contains information about a SendVideo request. type VideoConfig struct { BaseFile - Duration int - Caption string + Duration int + Caption string + ParseMode string } // values returns a url.Values representation of VideoConfig. @@ -442,6 +465,9 @@ func (config VideoConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -453,6 +479,9 @@ func (config VideoConfig) params() (map[string]string, error) { if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -468,6 +497,59 @@ func (config VideoConfig) method() string { return "sendVideo" } +// AnimationConfig contains information about a SendAnimation request. +type AnimationConfig struct { + BaseFile + Duration int + Caption string + ParseMode string +} + +// values returns a url.Values representation of AnimationConfig. +func (config AnimationConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + + v.Add(config.name(), config.FileID) + if config.Duration != 0 { + v.Add("duration", strconv.Itoa(config.Duration)) + } + if config.Caption != "" { + v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } + } + + return v, nil +} + +// params returns a map[string]string representation of AnimationConfig. +func (config AnimationConfig) params() (map[string]string, error) { + params, _ := config.BaseFile.params() + + if config.Caption != "" { + params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } + } + + return params, nil +} + +// name returns the field name for the Animation. +func (config AnimationConfig) name() string { + return "animation" +} + +// method returns Telegram API method name for sending Animation. +func (config AnimationConfig) method() string { + return "sendAnimation" +} + // VideoNoteConfig contains information about a SendVideoNote request. type VideoNoteConfig struct { BaseFile @@ -522,8 +604,9 @@ func (config VideoNoteConfig) method() string { // VoiceConfig contains information about a SendVoice request. type VoiceConfig struct { BaseFile - Caption string - Duration int + Caption string + ParseMode string + Duration int } // values returns a url.Values representation of VoiceConfig. @@ -539,6 +622,9 @@ func (config VoiceConfig) values() (url.Values, error) { } if config.Caption != "" { v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } } return v, nil @@ -553,6 +639,9 @@ func (config VoiceConfig) params() (map[string]string, error) { } if config.Caption != "" { params["caption"] = config.Caption + if config.ParseMode != "" { + params["parse_mode"] = config.ParseMode + } } return params, nil @@ -568,6 +657,32 @@ func (config VoiceConfig) method() string { return "sendVoice" } +// MediaGroupConfig contains information about a sendMediaGroup request. +type MediaGroupConfig struct { + BaseChat + InputMedia []interface{} +} + +func (config MediaGroupConfig) values() (url.Values, error) { + v, err := config.BaseChat.values() + if err != nil { + return v, err + } + + data, err := json.Marshal(config.InputMedia) + if err != nil { + return v, err + } + + v.Add("media", string(data)) + + return v, nil +} + +func (config MediaGroupConfig) method() string { + return "sendMediaGroup" +} + // LocationConfig contains information about a SendLocation request. type LocationConfig struct { BaseChat @@ -786,13 +901,17 @@ func (config EditMessageTextConfig) method() string { // EditMessageCaptionConfig allows you to modify the caption of a message. type EditMessageCaptionConfig struct { BaseEdit - Caption string + Caption string + ParseMode string } func (config EditMessageCaptionConfig) values() (url.Values, error) { v, _ := config.BaseEdit.values() v.Add("caption", config.Caption) + if config.ParseMode != "" { + v.Add("parse_mode", config.ParseMode) + } return v, nil } diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go index b5480ead..f49cbbab 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go @@ -1,7 +1,6 @@ package tgbotapi import ( - "log" "net/url" ) @@ -14,11 +13,12 @@ func NewMessage(chatID int64, text string) MessageConfig { ChatID: chatID, ReplyToMessageID: 0, }, - Text: text, + Text: text, DisableWebPagePreview: false, } } +// NewDeleteMessage creates a request to delete a message. func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig { return DeleteMessageConfig{ ChatID: chatID, @@ -201,6 +201,35 @@ func NewVideoShare(chatID int64, fileID string) VideoConfig { } } +// NewAnimationUpload creates a new animation uploader. +// +// chatID is where to send it, file is a string path to the file, +// FileReader, or FileBytes. +func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + File: file, + UseExisting: false, + }, + } +} + +// NewAnimationShare shares an existing animation. +// You may use this to reshare an existing animation without reuploading it. +// +// chatID is where to send it, fileID is the ID of the animation +// already uploaded. +func NewAnimationShare(chatID int64, fileID string) AnimationConfig { + return AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + FileID: fileID, + UseExisting: true, + }, + } +} + // NewVideoNoteUpload creates a new video note uploader. // // chatID is where to send it, file is a string path to the file, @@ -261,6 +290,33 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig { } } +// NewMediaGroup creates a new media group. Files should be an array of +// two to ten InputMediaPhoto or InputMediaVideo. +func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig { + return MediaGroupConfig{ + BaseChat: BaseChat{ + ChatID: chatID, + }, + InputMedia: files, + } +} + +// NewInputMediaPhoto creates a new InputMediaPhoto. +func NewInputMediaPhoto(media string) InputMediaPhoto { + return InputMediaPhoto{ + Type: "photo", + Media: media, + } +} + +// NewInputMediaVideo creates a new InputMediaVideo. +func NewInputMediaVideo(media string) InputMediaVideo { + return InputMediaVideo{ + Type: "video", + Media: media, + } +} + // NewContact allows you to send a shared contact. func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig { return ContactConfig{ diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/log.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/log.go new file mode 100644 index 00000000..18725514 --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/log.go @@ -0,0 +1,27 @@ +package tgbotapi + +import ( + "errors" + stdlog "log" + "os" +) + +// BotLogger is an interface that represents the required methods to log data. +// +// Instead of requiring the standard logger, we can just specify the methods we +// use and allow users to pass anything that implements these. +type BotLogger interface { + Println(v ...interface{}) + Printf(format string, v ...interface{}) +} + +var log BotLogger = stdlog.New(os.Stderr, "", stdlog.LstdFlags) + +// SetLogger specifies the logger that the package should use. +func SetLogger(logger BotLogger) error { + if logger == nil { + return errors.New("logger is nil") + } + log = logger + return nil +} diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/passport.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/passport.go new file mode 100644 index 00000000..5f55006d --- /dev/null +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/passport.go @@ -0,0 +1,315 @@ +package tgbotapi + +// PassportRequestInfoConfig allows you to request passport info +type PassportRequestInfoConfig struct { + BotID int `json:"bot_id"` + Scope *PassportScope `json:"scope"` + Nonce string `json:"nonce"` + PublicKey string `json:"public_key"` +} + +// PassportScopeElement supports using one or one of several elements. +type PassportScopeElement interface { + ScopeType() string +} + +// PassportScope is the requested scopes of data. +type PassportScope struct { + V int `json:"v"` + Data []PassportScopeElement `json:"data"` +} + +// PassportScopeElementOneOfSeveral allows you to request any one of the +// requested documents. +type PassportScopeElementOneOfSeveral struct { +} + +// ScopeType is the scope type. +func (eo *PassportScopeElementOneOfSeveral) ScopeType() string { + return "one_of" +} + +// PassportScopeElementOne requires the specified element be provided. +type PassportScopeElementOne struct { + Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email” + Selfie bool `json:"selfie"` + Translation bool `json:"translation"` + NativeNames bool `json:"native_name"` +} + +// ScopeType is the scope type. +func (eo *PassportScopeElementOne) ScopeType() string { + return "one" +} + +type ( + // PassportData contains information about Telegram Passport data shared with + // the bot by the user. + PassportData struct { + // Array with information about documents and other Telegram Passport + // elements that was shared with the bot + Data []EncryptedPassportElement `json:"data"` + + // Encrypted credentials required to decrypt the data + Credentials *EncryptedCredentials `json:"credentials"` + } + + // PassportFile represents a file uploaded to Telegram Passport. Currently all + // Telegram Passport files are in JPEG format when decrypted and don't exceed + // 10MB. + PassportFile struct { + // Unique identifier for this file + FileID string `json:"file_id"` + + // File size + FileSize int `json:"file_size"` + + // Unix time when the file was uploaded + FileDate int64 `json:"file_date"` + } + + // EncryptedPassportElement contains information about documents or other + // Telegram Passport elements shared with the bot by the user. + EncryptedPassportElement struct { + // Element type. + Type string `json:"type"` + + // Base64-encoded encrypted Telegram Passport element data provided by + // the user, available for "personal_details", "passport", + // "driver_license", "identity_card", "identity_passport" and "address" + // types. Can be decrypted and verified using the accompanying + // EncryptedCredentials. + Data string `json:"data,omitempty"` + + // User's verified phone number, available only for "phone_number" type + PhoneNumber string `json:"phone_number,omitempty"` + + // User's verified email address, available only for "email" type + Email string `json:"email,omitempty"` + + // Array of encrypted files with documents provided by the user, + // available for "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration" and "temporary_registration" types. Files can + // be decrypted and verified using the accompanying EncryptedCredentials. + Files []PassportFile `json:"files,omitempty"` + + // Encrypted file with the front side of the document, provided by the + // user. Available for "passport", "driver_license", "identity_card" and + // "internal_passport". The file can be decrypted and verified using the + // accompanying EncryptedCredentials. + FrontSide *PassportFile `json:"front_side,omitempty"` + + // Encrypted file with the reverse side of the document, provided by the + // user. Available for "driver_license" and "identity_card". The file can + // be decrypted and verified using the accompanying EncryptedCredentials. + ReverseSide *PassportFile `json:"reverse_side,omitempty"` + + // Encrypted file with the selfie of the user holding a document, + // provided by the user; available for "passport", "driver_license", + // "identity_card" and "internal_passport". The file can be decrypted + // and verified using the accompanying EncryptedCredentials. + Selfie *PassportFile `json:"selfie,omitempty"` + } + + // EncryptedCredentials contains data required for decrypting and + // authenticating EncryptedPassportElement. See the Telegram Passport + // Documentation for a complete description of the data decryption and + // authentication processes. + EncryptedCredentials struct { + // Base64-encoded encrypted JSON-serialized data with unique user's + // payload, data hashes and secrets required for EncryptedPassportElement + // decryption and authentication + Data string `json:"data"` + + // Base64-encoded data hash for data authentication + Hash string `json:"hash"` + + // Base64-encoded secret, encrypted with the bot's public RSA key, + // required for data decryption + Secret string `json:"secret"` + } + + // PassportElementError represents an error in the Telegram Passport element + // which was submitted that should be resolved by the user. + PassportElementError interface{} + + // PassportElementErrorDataField represents an issue in one of the data + // fields that was provided by the user. The error is considered resolved + // when the field's value changes. + PassportElementErrorDataField struct { + // Error source, must be data + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the error, one + // of "personal_details", "passport", "driver_license", "identity_card", + // "internal_passport", "address" + Type string `json:"type"` + + // Name of the data field which has the error + FieldName string `json:"field_name"` + + // Base64-encoded data hash + DataHash string `json:"data_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFrontSide represents an issue with the front side of + // a document. The error is considered resolved when the file with the front + // side of the document changes. + PassportElementErrorFrontSide struct { + // Error source, must be front_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the front side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorReverseSide represents an issue with the reverse side + // of a document. The error is considered resolved when the file with reverse + // side of the document changes. + PassportElementErrorReverseSide struct { + // Error source, must be reverse_side + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "driver_license", "identity_card" + Type string `json:"type"` + + // Base64-encoded hash of the file with the reverse side of the document + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorSelfie represents an issue with the selfie with a + // document. The error is considered resolved when the file with the selfie + // changes. + PassportElementErrorSelfie struct { + // Error source, must be selfie + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "passport", "driver_license", "identity_card", "internal_passport" + Type string `json:"type"` + + // Base64-encoded hash of the file with the selfie + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFile represents an issue with a document scan. The + // error is considered resolved when the file with the document scan changes. + PassportElementErrorFile struct { + // Error source, must be file + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // Base64-encoded file hash + FileHash string `json:"file_hash"` + + // Error message + Message string `json:"message"` + } + + // PassportElementErrorFiles represents an issue with a list of scans. The + // error is considered resolved when the list of files containing the scans + // changes. + PassportElementErrorFiles struct { + // Error source, must be files + Source string `json:"source"` + + // The section of the user's Telegram Passport which has the issue, one + // of "utility_bill", "bank_statement", "rental_agreement", + // "passport_registration", "temporary_registration" + Type string `json:"type"` + + // List of base64-encoded file hashes + FileHashes []string `json:"file_hashes"` + + // Error message + Message string `json:"message"` + } + + // Credentials contains encrypted data. + Credentials struct { + Data SecureData `json:"secure_data"` + // Nonce the same nonce given in the request + Nonce string `json:"nonce"` + } + + // SecureData is a map of the fields and their encrypted values. + SecureData map[string]*SecureValue + // PersonalDetails *SecureValue `json:"personal_details"` + // Passport *SecureValue `json:"passport"` + // InternalPassport *SecureValue `json:"internal_passport"` + // DriverLicense *SecureValue `json:"driver_license"` + // IdentityCard *SecureValue `json:"identity_card"` + // Address *SecureValue `json:"address"` + // UtilityBill *SecureValue `json:"utility_bill"` + // BankStatement *SecureValue `json:"bank_statement"` + // RentalAgreement *SecureValue `json:"rental_agreement"` + // PassportRegistration *SecureValue `json:"passport_registration"` + // TemporaryRegistration *SecureValue `json:"temporary_registration"` + + // SecureValue contains encrypted values for a SecureData item. + SecureValue struct { + Data *DataCredentials `json:"data"` + FrontSide *FileCredentials `json:"front_side"` + ReverseSide *FileCredentials `json:"reverse_side"` + Selfie *FileCredentials `json:"selfie"` + Translation []*FileCredentials `json:"translation"` + Files []*FileCredentials `json:"files"` + } + + // DataCredentials contains information required to decrypt data. + DataCredentials struct { + // DataHash checksum of encrypted data + DataHash string `json:"data_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + // FileCredentials contains information required to decrypt files. + FileCredentials struct { + // FileHash checksum of encrypted data + FileHash string `json:"file_hash"` + // Secret of encrypted data + Secret string `json:"secret"` + } + + // PersonalDetails https://core.telegram.org/passport#personaldetails + PersonalDetails struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + MiddleName string `json:"middle_name"` + BirthDate string `json:"birth_date"` + Gender string `json:"gender"` + CountryCode string `json:"country_code"` + ResidenceCountryCode string `json:"residence_country_code"` + FirstNameNative string `json:"first_name_native"` + LastNameNative string `json:"last_name_native"` + MiddleNameNative string `json:"middle_name_native"` + } + + // IDDocumentData https://core.telegram.org/passport#iddocumentdata + IDDocumentData struct { + DocumentNumber string `json:"document_no"` + ExpiryDate string `json:"expiry_date"` + } +) diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go index 0843ab94..d3c433fb 100644 --- a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go +++ b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go @@ -144,6 +144,7 @@ type Message struct { Entities *[]MessageEntity `json:"entities"` // optional Audio *Audio `json:"audio"` // optional Document *Document `json:"document"` // optional + Animation *ChatAnimation `json:"animation"` // optional Game *Game `json:"game"` // optional Photo *[]PhotoSize `json:"photo"` // optional Sticker *Sticker `json:"sticker"` // optional @@ -167,6 +168,7 @@ type Message struct { PinnedMessage *Message `json:"pinned_message"` // optional Invoice *Invoice `json:"invoice"` // optional SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional + PassportData *PassportData `json:"passport_data,omitempty"` // optional } // Time converts the message timestamp into a Time. @@ -293,6 +295,18 @@ type Sticker struct { SetName string `json:"set_name"` // optional } +// ChatAnimation contains information about an animation. +type ChatAnimation struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + Thumbnail *PhotoSize `json:"thumb"` // optional + FileName string `json:"file_name"` // optional + MimeType string `json:"mime_type"` // optional + FileSize int `json:"file_size"` // optional +} + // Video contains information about a video. type Video struct { FileID string `json:"file_id"` @@ -511,6 +525,27 @@ func (info WebhookInfo) IsSet() bool { return info.URL != "" } +// InputMediaPhoto contains a photo for displaying as part of a media group. +type InputMediaPhoto struct { + Type string `json:"type"` + Media string `json:"media"` + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` +} + +// InputMediaVideo contains a video for displaying as part of a media group. +type InputMediaVideo struct { + Type string `json:"type"` + Media string `json:"media"` + // thumb intentionally missing as it is not currently compatible + Caption string `json:"caption"` + ParseMode string `json:"parse_mode"` + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration"` + SupportsStreaming bool `json:"supports_streaming"` +} + // InlineQuery is a Query from Telegram for an inline request. type InlineQuery struct { ID string `json:"id"` diff --git a/vendor/github.com/google/gops/agent/agent.go b/vendor/github.com/google/gops/agent/agent.go index 57083918..24c0b896 100644 --- a/vendor/github.com/google/gops/agent/agent.go +++ b/vendor/github.com/google/gops/agent/agent.go @@ -7,6 +7,8 @@ package agent import ( + "bufio" + "encoding/binary" "fmt" "io" "io/ioutil" @@ -14,14 +16,13 @@ import ( "os" gosignal "os/signal" "runtime" + "runtime/debug" "runtime/pprof" "runtime/trace" "strconv" "sync" "time" - "bufio" - "github.com/google/gops/internal" "github.com/google/gops/signal" "github.com/kardianos/osext" @@ -43,10 +44,16 @@ type Options struct { // Optional. Addr string - // NoShutdownCleanup tells the agent not to automatically cleanup - // resources if the running process receives an interrupt. + // ConfigDir is the directory to store the configuration file, + // PID of the gops process, filename, port as well as content. + // Optional. + ConfigDir string + + // ShutdownCleanup automatically cleans up resources if the + // running process receives an interrupt. Otherwise, users + // can call Close before shutting down. // Optional. - NoShutdownCleanup bool + ShutdownCleanup bool } // Listen starts the gops agent on a host process. Once agent started, users @@ -58,26 +65,29 @@ type Options struct { // Note: The agent exposes an endpoint via a TCP connection that can be used by // any program on the system. Review your security requirements before starting // the agent. -func Listen(opts *Options) error { +func Listen(opts Options) error { mu.Lock() defer mu.Unlock() - if opts == nil { - opts = &Options{} - } if portfile != "" { return fmt.Errorf("gops: agent already listening at: %v", listener.Addr()) } - gopsdir, err := internal.ConfigDir() - if err != nil { - return err + // new + gopsdir := opts.ConfigDir + if gopsdir == "" { + cfgDir, err := internal.ConfigDir() + if err != nil { + return err + } + gopsdir = cfgDir } - err = os.MkdirAll(gopsdir, os.ModePerm) + + err := os.MkdirAll(gopsdir, os.ModePerm) if err != nil { return err } - if !opts.NoShutdownCleanup { + if opts.ShutdownCleanup { gracefulShutdown() } @@ -165,7 +175,7 @@ func formatBytes(val uint64) string { return fmt.Sprintf("%d bytes", val) } -func handle(conn io.Writer, msg []byte) error { +func handle(conn io.ReadWriter, msg []byte) error { switch msg[0] { case signal.StackTrace: return pprof.Lookup("goroutine").WriteTo(conn, 2) @@ -190,13 +200,20 @@ func handle(conn io.Writer, msg []byte) error { fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects) fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse)) fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys)) + fmt.Fprintf(conn, "stack-mspan-inuse: %v\n", formatBytes(s.MSpanInuse)) + fmt.Fprintf(conn, "stack-mspan-sys: %v\n", formatBytes(s.MSpanSys)) + fmt.Fprintf(conn, "stack-mcache-inuse: %v\n", formatBytes(s.MCacheInuse)) + fmt.Fprintf(conn, "stack-mcache-sys: %v\n", formatBytes(s.MCacheSys)) + fmt.Fprintf(conn, "other-sys: %v\n", formatBytes(s.OtherSys)) + fmt.Fprintf(conn, "gc-sys: %v\n", formatBytes(s.GCSys)) fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC)) lastGC := "-" if s.LastGC != 0 { lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC))) } fmt.Fprintf(conn, "last-gc: %v\n", lastGC) - fmt.Fprintf(conn, "gc-pause: %v\n", time.Duration(s.PauseTotalNs)) + fmt.Fprintf(conn, "gc-pause-total: %v\n", time.Duration(s.PauseTotalNs)) + fmt.Fprintf(conn, "gc-pause: %v\n", s.PauseNs[(s.NumGC+255)%256]) fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC) fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC) fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC) @@ -232,6 +249,12 @@ func handle(conn io.Writer, msg []byte) error { trace.Start(conn) time.Sleep(5 * time.Second) trace.Stop() + case signal.SetGCPercent: + perc, err := binary.ReadVarint(bufio.NewReader(conn)) + if err != nil { + return err + } + fmt.Fprintf(conn, "New GC percent set to %v. Previous value was %v.\n", perc, debug.SetGCPercent(int(perc))) } return nil } diff --git a/vendor/github.com/google/gops/internal/internal.go b/vendor/github.com/google/gops/internal/internal.go index 13828220..80eac63f 100644 --- a/vendor/github.com/google/gops/internal/internal.go +++ b/vendor/github.com/google/gops/internal/internal.go @@ -1,3 +1,7 @@ +// Copyright 2017 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 internal import ( @@ -11,7 +15,13 @@ import ( "strings" ) +const gopsConfigDirEnvKey = "GOPS_CONFIG_DIR" + func ConfigDir() (string, error) { + if configDir := os.Getenv(gopsConfigDirEnvKey); configDir != "" { + return configDir, nil + } + if runtime.GOOS == "windows" { return filepath.Join(os.Getenv("APPDATA"), "gops"), nil } diff --git a/vendor/github.com/google/gops/signal/signal.go b/vendor/github.com/google/gops/signal/signal.go index b2bfbe15..c70764a0 100644 --- a/vendor/github.com/google/gops/signal/signal.go +++ b/vendor/github.com/google/gops/signal/signal.go @@ -32,4 +32,7 @@ const ( // BinaryDump returns running binary file. BinaryDump = byte(0x9) + + // SetGCPercent sets the garbage collection target percentage. + SetGCPercent = byte(0x10) ) diff --git a/vendor/github.com/gorilla/schema/cache.go b/vendor/github.com/gorilla/schema/cache.go index b2c5995d..73b75f48 100644 --- a/vendor/github.com/gorilla/schema/cache.go +++ b/vendor/github.com/gorilla/schema/cache.go @@ -18,13 +18,9 @@ var invalidPath = errors.New("schema: invalid path") 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 } @@ -32,11 +28,15 @@ func newCache() *cache { type cache struct { l sync.RWMutex m map[reflect.Type]*structInfo - conv map[reflect.Kind]Converter regconv map[reflect.Type]Converter tag string } +// registerConverter registers a converter function for a custom type. +func (c *cache) registerConverter(value interface{}, converterFunc Converter) { + c.regconv[reflect.TypeOf(value)] = converterFunc +} + // parsePath parses a path in dotted notation verifying that it is a valid // path to a struct field. // @@ -63,7 +63,7 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) { } // Valid field. Append index. path = append(path, field.name) - if field.ss { + if field.isSliceOfStructs && (!field.unmarshalerInfo.IsValid || (field.unmarshalerInfo.IsValid && field.unmarshalerInfo.IsSliceElement)) { // Parse a special case: slices of structs. // i+1 must be the slice index. // @@ -142,7 +142,7 @@ func (c *cache) create(t reflect.Type, info *structInfo) *structInfo { c.create(ft, info) for _, fi := range info.fields[bef:len(info.fields)] { // exclude required check because duplicated to embedded field - fi.required = false + fi.isRequired = false } } } @@ -162,6 +162,7 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) { // First let's get the basic type. isSlice, isStruct := false, false ft := field.Type + m := isTextUnmarshaler(reflect.Zero(ft)) if ft.Kind() == reflect.Ptr { ft = ft.Elem() } @@ -178,29 +179,26 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) { } } if isStruct = ft.Kind() == reflect.Struct; !isStruct { - if conv := c.converter(ft); conv == nil { + if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil { // Type is not supported. return } } info.fields = append(info.fields, &fieldInfo{ - typ: field.Type, - name: field.Name, - ss: isSlice && isStruct, - alias: alias, - anon: field.Anonymous, - required: options.Contains("required"), + typ: field.Type, + name: field.Name, + alias: alias, + unmarshalerInfo: m, + isSliceOfStructs: isSlice && isStruct, + isAnonymous: field.Anonymous, + isRequired: options.Contains("required"), }) } // 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 + return c.regconv[t] } // ---------------------------------------------------------------------------- @@ -219,12 +217,18 @@ func (i *structInfo) get(alias string) *fieldInfo { } 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 - anon bool // is an embedded field - required bool // tag option + typ reflect.Type + // name is the field name in the struct. + name string + alias string + // unmarshalerInfo contains information regarding the + // encoding.TextUnmarshaler implementation of the field type. + unmarshalerInfo unmarshaler + // isSliceOfStructs indicates if the field type is a slice of structs. + isSliceOfStructs bool + // isAnonymous indicates whether the field is embedded in the struct. + isAnonymous bool + isRequired bool } type pathPart struct { diff --git a/vendor/github.com/gorilla/schema/converter.go b/vendor/github.com/gorilla/schema/converter.go index b33e9423..4f2116a1 100644 --- a/vendor/github.com/gorilla/schema/converter.go +++ b/vendor/github.com/gorilla/schema/converter.go @@ -30,7 +30,7 @@ var ( ) // Default converters for basic types. -var converters = map[reflect.Kind]Converter{ +var builtinConverters = map[reflect.Kind]Converter{ boolType: convertBool, float32Type: convertFloat32, float64Type: convertFloat64, diff --git a/vendor/github.com/gorilla/schema/decoder.go b/vendor/github.com/gorilla/schema/decoder.go index b0f8cb00..5352a908 100644 --- a/vendor/github.com/gorilla/schema/decoder.go +++ b/vendor/github.com/gorilla/schema/decoder.go @@ -56,7 +56,7 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) { // RegisterConverter registers a converter function for a custom type. func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) { - d.cache.regconv[reflect.TypeOf(value)] = converterFunc + d.cache.registerConverter(value, converterFunc) } // Decode decodes a map[string][]string to a struct. @@ -90,7 +90,7 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { return d.checkRequired(t, src, "") } -// checkRequired checks whether requred field empty +// checkRequired checks whether required fields are empty // // check type t recursively if t has struct fields, and prefix is same as parsePath: in dotted notation // @@ -106,7 +106,7 @@ func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string, prefix if f.typ.Kind() == reflect.Struct { err := d.checkRequired(f.typ, src, prefix+f.alias+".") if err != nil { - if !f.anon { + if !f.isAnonymous { return err } // check embedded parent field. @@ -116,7 +116,7 @@ func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string, prefix } } } - if f.required { + if f.isRequired { key := f.alias if prefix != "" { key = prefix + key @@ -153,7 +153,6 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values } v = v.FieldByName(name) } - // Don't even bother for unexported fields. if !v.CanSet() { return nil @@ -185,7 +184,8 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, 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 { + m := isTextUnmarshaler(v) + if conv == nil && t.Kind() == reflect.Slice && m.IsSliceElement { var items []reflect.Value elemT := t.Elem() isPtrElem := elemT.Kind() == reflect.Ptr @@ -196,9 +196,12 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values // 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) + conv = builtinConverters[elemT.Kind()] + 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 { @@ -206,6 +209,26 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values if d.zeroEmpty { items = append(items, reflect.Zero(elemT)) } + } else if m.IsValid { + u := reflect.New(elemT) + if m.IsSliceElementPtr { + u = reflect.New(reflect.PtrTo(elemT).Elem()) + } + if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)); err != nil { + return ConversionError{ + Key: path, + Type: t, + Index: key, + Err: err, + } + } + if m.IsSliceElementPtr { + items = append(items, u.Elem().Addr()) + } else if u.Kind() == reflect.Ptr { + items = append(items, u.Elem()) + } else { + items = append(items, u) + } } else if item := conv(value); item.IsValid() { if isPtrElem { ptr := reflect.New(elemT) @@ -260,11 +283,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values val = values[len(values)-1] } - if val == "" { - if d.zeroEmpty { - v.Set(reflect.Zero(t)) - } - } else if conv != nil { + if conv != nil { if value := conv(val); value.IsValid() { v.Set(value.Convert(t)) } else { @@ -274,16 +293,10 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values 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 { + } else if m.IsValid { + if m.IsPtr { + u := reflect.New(v.Type()) + if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(val)); err != nil { return ConversionError{ Key: path, Type: t, @@ -291,15 +304,99 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values Err: err, } } - + v.Set(reflect.Indirect(u)) + } else { + // If the value implements the encoding.TextUnmarshaler interface + // apply UnmarshalText as the converter + if err := m.Unmarshaler.UnmarshalText([]byte(val)); err != nil { + return ConversionError{ + Key: path, + Type: t, + Index: -1, + Err: err, + } + } + } + } else if val == "" { + if d.zeroEmpty { + v.Set(reflect.Zero(t)) + } + } else if conv := builtinConverters[t.Kind()]; conv != nil { + if value := conv(val); value.IsValid() { + v.Set(value.Convert(t)) } else { - return fmt.Errorf("schema: converter not found for %v", t) + return ConversionError{ + Key: path, + Type: t, + Index: -1, + } } + } else { + return fmt.Errorf("schema: converter not found for %v", t) } } return nil } +func isTextUnmarshaler(v reflect.Value) unmarshaler { + // Create a new unmarshaller instance + m := unmarshaler{} + if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid { + return m + } + // As the UnmarshalText function should be applied to the pointer of the + // type, we check that type to see if it implements the necessary + // method. + if m.Unmarshaler, m.IsValid = reflect.New(v.Type()).Interface().(encoding.TextUnmarshaler); m.IsValid { + m.IsPtr = true + return m + } + + // if v is []T or *[]T create new T + t := v.Type() + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Slice { + // Check if the slice implements encoding.TextUnmarshaller + if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid { + return m + } + // If t is a pointer slice, check if its elements implement + // encoding.TextUnmarshaler + m.IsSliceElement = true + if t = t.Elem(); t.Kind() == reflect.Ptr { + t = reflect.PtrTo(t.Elem()) + v = reflect.Zero(t) + m.IsSliceElementPtr = true + m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler) + return m + } + } + + v = reflect.New(t) + m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler) + return m +} + +// TextUnmarshaler helpers ---------------------------------------------------- +// unmarshaller contains information about a TextUnmarshaler type +type unmarshaler struct { + Unmarshaler encoding.TextUnmarshaler + // IsValid indicates whether the resolved type indicated by the other + // flags implements the encoding.TextUnmarshaler interface. + IsValid bool + // IsPtr indicates that the resolved type is the pointer of the original + // type. + IsPtr bool + // IsSliceElement indicates that the resolved type is a slice element of + // the original type. + IsSliceElement bool + // IsSliceElementPtr indicates that the resolved type is a pointer to a + // slice element of the original type. + IsSliceElementPtr bool +} + // Errors --------------------------------------------------------------------- // ConversionError stores information about a failed conversion. diff --git a/vendor/github.com/gorilla/schema/doc.go b/vendor/github.com/gorilla/schema/doc.go index a95e87bd..aae9f33f 100644 --- a/vendor/github.com/gorilla/schema/doc.go +++ b/vendor/github.com/gorilla/schema/doc.go @@ -24,7 +24,7 @@ The basic usage is really simple. Given this struct: 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: +will be of type url.Values, http.Request.Form, or http.Request.MultipartForm: func MyHandler(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() @@ -45,7 +45,7 @@ will be of type url.Values: http.Request.Form or http.Request.MultipartForm: } 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: +because it caches meta-data about structs, and an instance can be shared safely: var decoder = schema.NewDecoder() @@ -121,7 +121,7 @@ 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 +TextUnmarshaler interface, and in this case there's no need to register a converter, like: type Person struct { diff --git a/vendor/github.com/gorilla/schema/encoder.go b/vendor/github.com/gorilla/schema/encoder.go index aa43a5c9..bf1d511e 100644 --- a/vendor/github.com/gorilla/schema/encoder.go +++ b/vendor/github.com/gorilla/schema/encoder.go @@ -40,6 +40,34 @@ func (e *Encoder) SetAliasTag(tag string) { e.cache.tag = tag } +// isValidStructPointer test if input value is a valid struct pointer. +func isValidStructPointer(v reflect.Value) bool { + return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct +} + +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Func: + case reflect.Map, reflect.Slice: + return v.IsNil() || v.Len() == 0 + case reflect.Array: + z := true + for i := 0; i < v.Len(); i++ { + z = z && isZero(v.Index(i)) + } + return z + case reflect.Struct: + z := true + for i := 0; i < v.NumField(); i++ { + z = z && isZero(v.Field(i)) + } + return z + } + // Compare other types directly: + z := reflect.Zero(v.Type()) + return v.Interface() == z.Interface() +} + func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { if v.Kind() == reflect.Ptr { v = v.Elem() @@ -57,8 +85,9 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { continue } - if v.Field(i).Type().Kind() == reflect.Struct { - e.encode(v.Field(i), dst) + // Encode struct pointer types if the field is a valid pointer and a struct. + if isValidStructPointer(v.Field(i)) { + e.encode(v.Field(i).Elem(), dst) continue } @@ -67,7 +96,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { // Encode non-slice types and custom implementations immediately. if encFunc != nil { value := encFunc(v.Field(i)) - if value == "" && opts.Contains("omitempty") { + if opts.Contains("omitempty") && isZero(v.Field(i)) { continue } @@ -75,6 +104,11 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { continue } + if v.Field(i).Type().Kind() == reflect.Struct { + e.encode(v.Field(i), dst) + continue + } + if v.Field(i).Type().Kind() == reflect.Slice { encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc) } diff --git a/vendor/github.com/hashicorp/golang-lru/2q.go b/vendor/github.com/hashicorp/golang-lru/2q.go index 337d9632..e474cd07 100644 --- a/vendor/github.com/hashicorp/golang-lru/2q.go +++ b/vendor/github.com/hashicorp/golang-lru/2q.go @@ -30,9 +30,9 @@ type TwoQueueCache struct { size int recentSize int - recent *simplelru.LRU - frequent *simplelru.LRU - recentEvict *simplelru.LRU + recent simplelru.LRUCache + frequent simplelru.LRUCache + recentEvict simplelru.LRUCache lock sync.RWMutex } @@ -84,7 +84,8 @@ func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCa return c, nil } -func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { +// Get looks up a key's value from the cache. +func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) { c.lock.Lock() defer c.lock.Unlock() @@ -105,6 +106,7 @@ func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { return nil, false } +// Add adds a value to the cache. func (c *TwoQueueCache) Add(key, value interface{}) { c.lock.Lock() defer c.lock.Unlock() @@ -160,12 +162,15 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) { c.frequent.RemoveOldest() } +// Len returns the number of items in the cache. func (c *TwoQueueCache) Len() int { c.lock.RLock() defer c.lock.RUnlock() return c.recent.Len() + c.frequent.Len() } +// Keys returns a slice of the keys in the cache. +// The frequently used keys are first in the returned slice. func (c *TwoQueueCache) Keys() []interface{} { c.lock.RLock() defer c.lock.RUnlock() @@ -174,6 +179,7 @@ func (c *TwoQueueCache) Keys() []interface{} { return append(k1, k2...) } +// Remove removes the provided key from the cache. func (c *TwoQueueCache) Remove(key interface{}) { c.lock.Lock() defer c.lock.Unlock() @@ -188,6 +194,7 @@ func (c *TwoQueueCache) Remove(key interface{}) { } } +// Purge is used to completely clear the cache. func (c *TwoQueueCache) Purge() { c.lock.Lock() defer c.lock.Unlock() @@ -196,13 +203,17 @@ func (c *TwoQueueCache) Purge() { c.recentEvict.Purge() } +// Contains is used to check if the cache contains a key +// without updating recency or frequency. func (c *TwoQueueCache) Contains(key interface{}) bool { c.lock.RLock() defer c.lock.RUnlock() return c.frequent.Contains(key) || c.recent.Contains(key) } -func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) { +// Peek is used to inspect the cache value of a key +// without updating recency or frequency. +func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) { c.lock.RLock() defer c.lock.RUnlock() if val, ok := c.frequent.Peek(key); ok { diff --git a/vendor/github.com/hashicorp/golang-lru/arc.go b/vendor/github.com/hashicorp/golang-lru/arc.go index a2a25281..555225a2 100644 --- a/vendor/github.com/hashicorp/golang-lru/arc.go +++ b/vendor/github.com/hashicorp/golang-lru/arc.go @@ -18,11 +18,11 @@ type ARCCache struct { size int // Size is the total capacity of the cache p int // P is the dynamic preference towards T1 or T2 - t1 *simplelru.LRU // T1 is the LRU for recently accessed items - b1 *simplelru.LRU // B1 is the LRU for evictions from t1 + t1 simplelru.LRUCache // T1 is the LRU for recently accessed items + b1 simplelru.LRUCache // B1 is the LRU for evictions from t1 - t2 *simplelru.LRU // T2 is the LRU for frequently accessed items - b2 *simplelru.LRU // B2 is the LRU for evictions from t2 + t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items + b2 simplelru.LRUCache // B2 is the LRU for evictions from t2 lock sync.RWMutex } @@ -60,11 +60,11 @@ func NewARC(size int) (*ARCCache, error) { } // Get looks up a key's value from the cache. -func (c *ARCCache) Get(key interface{}) (interface{}, bool) { +func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) { c.lock.Lock() defer c.lock.Unlock() - // Ff the value is contained in T1 (recent), then + // If the value is contained in T1 (recent), then // promote it to T2 (frequent) if val, ok := c.t1.Peek(key); ok { c.t1.Remove(key) @@ -153,7 +153,7 @@ func (c *ARCCache) Add(key, value interface{}) { // Remove from B2 c.b2.Remove(key) - // Add the key to the frequntly used list + // Add the key to the frequently used list c.t2.Add(key, value) return } @@ -247,7 +247,7 @@ func (c *ARCCache) Contains(key interface{}) bool { // Peek is used to inspect the cache value of a key // without updating recency or frequency. -func (c *ARCCache) Peek(key interface{}) (interface{}, bool) { +func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) { c.lock.RLock() defer c.lock.RUnlock() if val, ok := c.t1.Peek(key); ok { diff --git a/vendor/github.com/hashicorp/golang-lru/doc.go b/vendor/github.com/hashicorp/golang-lru/doc.go new file mode 100644 index 00000000..2547df97 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/doc.go @@ -0,0 +1,21 @@ +// Package lru provides three different LRU caches of varying sophistication. +// +// Cache is a simple LRU cache. It is based on the +// LRU implementation in groupcache: +// https://github.com/golang/groupcache/tree/master/lru +// +// TwoQueueCache tracks frequently used and recently used entries separately. +// This avoids a burst of accesses from taking out frequently used entries, +// at the cost of about 2x computational overhead and some extra bookkeeping. +// +// ARCCache is an adaptive replacement cache. It tracks recent evictions as +// well as recent usage in both the frequent and recent caches. Its +// computational overhead is comparable to TwoQueueCache, but the memory +// overhead is linear with the size of the cache. +// +// ARC has been patented by IBM, so do not use it if that is problematic for +// your program. +// +// All caches in this package take locks while operating, and are therefore +// thread-safe for consumers. +package lru diff --git a/vendor/github.com/hashicorp/golang-lru/go.mod b/vendor/github.com/hashicorp/golang-lru/go.mod new file mode 100644 index 00000000..824cb97e --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/go.mod @@ -0,0 +1 @@ +module github.com/hashicorp/golang-lru diff --git a/vendor/github.com/hashicorp/golang-lru/lru.go b/vendor/github.com/hashicorp/golang-lru/lru.go index a6285f98..c8d9b0a2 100644 --- a/vendor/github.com/hashicorp/golang-lru/lru.go +++ b/vendor/github.com/hashicorp/golang-lru/lru.go @@ -1,6 +1,3 @@ -// This package provides a simple LRU cache. It is based on the -// LRU implementation in groupcache: -// https://github.com/golang/groupcache/tree/master/lru package lru import ( @@ -11,11 +8,11 @@ import ( // Cache is a thread-safe fixed size LRU cache. type Cache struct { - lru *simplelru.LRU + lru simplelru.LRUCache lock sync.RWMutex } -// New creates an LRU of the given size +// New creates an LRU of the given size. func New(size int) (*Cache, error) { return NewWithEvict(size, nil) } @@ -33,7 +30,7 @@ func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) return c, nil } -// Purge is used to completely clear the cache +// Purge is used to completely clear the cache. func (c *Cache) Purge() { c.lock.Lock() c.lru.Purge() @@ -41,30 +38,30 @@ func (c *Cache) Purge() { } // Add adds a value to the cache. Returns true if an eviction occurred. -func (c *Cache) Add(key, value interface{}) bool { +func (c *Cache) Add(key, value interface{}) (evicted bool) { c.lock.Lock() defer c.lock.Unlock() return c.lru.Add(key, value) } // Get looks up a key's value from the cache. -func (c *Cache) Get(key interface{}) (interface{}, bool) { +func (c *Cache) Get(key interface{}) (value interface{}, ok bool) { c.lock.Lock() defer c.lock.Unlock() return c.lru.Get(key) } -// Check if a key is in the cache, without updating the recent-ness -// or deleting it for being stale. +// Contains checks if a key is in the cache, without updating the +// recent-ness or deleting it for being stale. func (c *Cache) Contains(key interface{}) bool { c.lock.RLock() defer c.lock.RUnlock() return c.lru.Contains(key) } -// Returns the key value (or undefined if not found) without updating +// Peek returns the key value (or undefined if not found) without updating // the "recently used"-ness of the key. -func (c *Cache) Peek(key interface{}) (interface{}, bool) { +func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) { c.lock.RLock() defer c.lock.RUnlock() return c.lru.Peek(key) @@ -73,16 +70,15 @@ func (c *Cache) Peek(key interface{}) (interface{}, bool) { // ContainsOrAdd checks if a key is in the cache without updating the // recent-ness or deleting it for being stale, and if not, adds the value. // Returns whether found and whether an eviction occurred. -func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) { +func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) { c.lock.Lock() defer c.lock.Unlock() if c.lru.Contains(key) { return true, false - } else { - evict := c.lru.Add(key, value) - return false, evict } + evicted = c.lru.Add(key, value) + return false, evicted } // Remove removes the provided key from the cache. diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go index cb416b39..5673773b 100644 --- a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go @@ -36,7 +36,7 @@ func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { return c, nil } -// Purge is used to completely clear the cache +// Purge is used to completely clear the cache. func (c *LRU) Purge() { for k, v := range c.items { if c.onEvict != nil { @@ -48,7 +48,7 @@ func (c *LRU) Purge() { } // Add adds a value to the cache. Returns true if an eviction occurred. -func (c *LRU) Add(key, value interface{}) bool { +func (c *LRU) Add(key, value interface{}) (evicted bool) { // Check for existing item if ent, ok := c.items[key]; ok { c.evictList.MoveToFront(ent) @@ -78,17 +78,18 @@ func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { return } -// Check if a key is in the cache, without updating the recent-ness +// Contains checks if a key is in the cache, without updating the recent-ness // or deleting it for being stale. func (c *LRU) Contains(key interface{}) (ok bool) { _, ok = c.items[key] return ok } -// Returns the key value (or undefined if not found) without updating +// Peek returns the key value (or undefined if not found) without updating // the "recently used"-ness of the key. func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { - if ent, ok := c.items[key]; ok { + var ent *list.Element + if ent, ok = c.items[key]; ok { return ent.Value.(*entry).value, true } return nil, ok @@ -96,7 +97,7 @@ func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { // Remove removes the provided key from the cache, returning if the // key was contained. -func (c *LRU) Remove(key interface{}) bool { +func (c *LRU) Remove(key interface{}) (present bool) { if ent, ok := c.items[key]; ok { c.removeElement(ent) return true @@ -105,7 +106,7 @@ func (c *LRU) Remove(key interface{}) bool { } // RemoveOldest removes the oldest item from the cache. -func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { +func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { ent := c.evictList.Back() if ent != nil { c.removeElement(ent) @@ -116,7 +117,7 @@ func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { } // GetOldest returns the oldest entry -func (c *LRU) GetOldest() (interface{}, interface{}, bool) { +func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { ent := c.evictList.Back() if ent != nil { kv := ent.Value.(*entry) diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go new file mode 100644 index 00000000..74c70774 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go @@ -0,0 +1,36 @@ +package simplelru + +// LRUCache is the interface for simple LRU cache. +type LRUCache interface { + // Adds a value to the cache, returns true if an eviction occurred and + // updates the "recently used"-ness of the key. + Add(key, value interface{}) bool + + // Returns key's value from the cache and + // updates the "recently used"-ness of the key. #value, isFound + Get(key interface{}) (value interface{}, ok bool) + + // Check if a key exsists in cache without updating the recent-ness. + Contains(key interface{}) (ok bool) + + // Returns key's value without updating the "recently used"-ness of the key. + Peek(key interface{}) (value interface{}, ok bool) + + // Removes a key from the cache. + Remove(key interface{}) bool + + // Removes the oldest entry from cache. + RemoveOldest() (interface{}, interface{}, bool) + + // Returns the oldest entry from the cache. #key, value, isFound + GetOldest() (interface{}, interface{}, bool) + + // Returns a slice of the keys in the cache, from oldest to newest. + Keys() []interface{} + + // Returns the number of items in the cache. + Len() int + + // Clear all cache entries + Purge() +} diff --git a/vendor/github.com/hashicorp/hcl/go.mod b/vendor/github.com/hashicorp/hcl/go.mod new file mode 100644 index 00000000..4debbbe3 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/go.mod @@ -0,0 +1,3 @@ +module github.com/hashicorp/hcl + +require github.com/davecgh/go-spew v1.1.1 diff --git a/vendor/github.com/hashicorp/hcl/go.sum b/vendor/github.com/hashicorp/hcl/go.sum new file mode 100644 index 00000000..b5e2922e --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/go.sum @@ -0,0 +1,2 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go index 098e1bc4..64c83bcf 100644 --- a/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go +++ b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go @@ -205,6 +205,12 @@ func (p *Parser) objectItem() (*ast.ObjectItem, error) { } } + // key=#comment + // val + if p.lineComment != nil { + o.LineComment, p.lineComment = p.lineComment, nil + } + // do a look-ahead for line comment p.scan() if len(keys) > 0 && o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil { diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go index c896d584..7c038d12 100644 --- a/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go +++ b/vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go @@ -252,6 +252,14 @@ func (p *printer) objectItem(o *ast.ObjectItem) []byte { } } + // If key and val are on different lines, treat line comments like lead comments. + if o.LineComment != nil && o.Val.Pos().Line != o.Keys[0].Pos().Line { + for _, comment := range o.LineComment.List { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + } + } + for i, k := range o.Keys { buf.WriteString(k.Token.Text) buf.WriteByte(blank) @@ -265,7 +273,7 @@ func (p *printer) objectItem(o *ast.ObjectItem) []byte { buf.Write(p.output(o.Val)) - if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil { + if o.LineComment != nil && o.Val.Pos().Line == o.Keys[0].Pos().Line { buf.WriteByte(blank) for _, comment := range o.LineComment.List { buf.WriteString(comment.Text) @@ -509,8 +517,13 @@ func (p *printer) alignedItems(items []*ast.ObjectItem) []byte { // list returns the printable HCL form of an list type. func (p *printer) list(l *ast.ListType) []byte { + if p.isSingleLineList(l) { + return p.singleLineList(l) + } + var buf bytes.Buffer buf.WriteString("[") + buf.WriteByte(newline) var longestLine int for _, item := range l.List { @@ -523,115 +536,112 @@ func (p *printer) list(l *ast.ListType) []byte { } } - insertSpaceBeforeItem := false - lastHadLeadComment := false + haveEmptyLine := false for i, item := range l.List { - // Keep track of whether this item is a heredoc since that has - // unique behavior. - heredoc := false - if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC { - heredoc = true - } - - if item.Pos().Line != l.Lbrack.Line { - // multiline list, add newline before we add each item - buf.WriteByte(newline) - insertSpaceBeforeItem = false - - // If we have a lead comment, then we want to write that first - leadComment := false - if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil { - leadComment = true - - // If this isn't the first item and the previous element - // didn't have a lead comment, then we need to add an extra - // newline to properly space things out. If it did have a - // lead comment previously then this would be done - // automatically. - if i > 0 && !lastHadLeadComment { - buf.WriteByte(newline) - } - - for _, comment := range lit.LeadComment.List { - buf.Write(p.indent([]byte(comment.Text))) - buf.WriteByte(newline) - } + // If we have a lead comment, then we want to write that first + leadComment := false + if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil { + leadComment = true + + // Ensure an empty line before every element with a + // lead comment (except the first item in a list). + if !haveEmptyLine && i != 0 { + buf.WriteByte(newline) } - // also indent each line - val := p.output(item) - curLen := len(val) - buf.Write(p.indent(val)) - - // if this item is a heredoc, then we output the comma on - // the next line. This is the only case this happens. - comma := []byte{','} - if heredoc { + for _, comment := range lit.LeadComment.List { + buf.Write(p.indent([]byte(comment.Text))) buf.WriteByte(newline) - comma = p.indent(comma) } + } - buf.Write(comma) + // also indent each line + val := p.output(item) + curLen := len(val) + buf.Write(p.indent(val)) - if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil { - // if the next item doesn't have any comments, do not align - buf.WriteByte(blank) // align one space - for i := 0; i < longestLine-curLen; i++ { - buf.WriteByte(blank) - } + // if this item is a heredoc, then we output the comma on + // the next line. This is the only case this happens. + comma := []byte{','} + if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC { + buf.WriteByte(newline) + comma = p.indent(comma) + } - for _, comment := range lit.LineComment.List { - buf.WriteString(comment.Text) - } - } + buf.Write(comma) - lastItem := i == len(l.List)-1 - if lastItem { - buf.WriteByte(newline) + if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil { + // if the next item doesn't have any comments, do not align + buf.WriteByte(blank) // align one space + for i := 0; i < longestLine-curLen; i++ { + buf.WriteByte(blank) } - if leadComment && !lastItem { - buf.WriteByte(newline) + for _, comment := range lit.LineComment.List { + buf.WriteString(comment.Text) } + } - lastHadLeadComment = leadComment - } else { - if insertSpaceBeforeItem { - buf.WriteByte(blank) - insertSpaceBeforeItem = false - } + buf.WriteByte(newline) - // Output the item itself - // also indent each line - val := p.output(item) - curLen := len(val) - buf.Write(val) + // Ensure an empty line after every element with a + // lead comment (except the first item in a list). + haveEmptyLine = leadComment && i != len(l.List)-1 + if haveEmptyLine { + buf.WriteByte(newline) + } + } - // If this is a heredoc item we always have to output a newline - // so that it parses properly. - if heredoc { - buf.WriteByte(newline) - } + buf.WriteString("]") + return buf.Bytes() +} - // If this isn't the last element, write a comma. - if i != len(l.List)-1 { - buf.WriteString(",") - insertSpaceBeforeItem = true - } +// isSingleLineList returns true if: +// * they were previously formatted entirely on one line +// * they consist entirely of literals +// * there are either no heredoc strings or the list has exactly one element +// * there are no line comments +func (printer) isSingleLineList(l *ast.ListType) bool { + for _, item := range l.List { + if item.Pos().Line != l.Lbrack.Line { + return false + } - if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil { - // if the next item doesn't have any comments, do not align - buf.WriteByte(blank) // align one space - for i := 0; i < longestLine-curLen; i++ { - buf.WriteByte(blank) - } + lit, ok := item.(*ast.LiteralType) + if !ok { + return false + } - for _, comment := range lit.LineComment.List { - buf.WriteString(comment.Text) - } - } + if lit.Token.Type == token.HEREDOC && len(l.List) != 1 { + return false + } + + if lit.LineComment != nil { + return false + } + } + + return true +} + +// singleLineList prints a simple single line list. +// For a definition of "simple", see isSingleLineList above. +func (p *printer) singleLineList(l *ast.ListType) []byte { + buf := &bytes.Buffer{} + + buf.WriteString("[") + for i, item := range l.List { + if i != 0 { + buf.WriteString(", ") } + // Output the item itself + buf.Write(p.output(item)) + + // The heredoc marker needs to be at the end of line. + if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC { + buf.WriteByte(newline) + } } buf.WriteString("]") diff --git a/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go b/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go index 6601ef76..624a18fe 100644 --- a/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go +++ b/vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go @@ -74,14 +74,6 @@ func (s *Scanner) next() rune { return eof } - if ch == utf8.RuneError && size == 1 { - s.srcPos.Column++ - s.srcPos.Offset += size - s.lastCharLen = size - s.err("illegal UTF-8 encoding") - return ch - } - // remember last position s.prevPos = s.srcPos @@ -89,18 +81,27 @@ func (s *Scanner) next() rune { s.lastCharLen = size s.srcPos.Offset += size + if ch == utf8.RuneError && size == 1 { + s.err("illegal UTF-8 encoding") + return ch + } + if ch == '\n' { s.srcPos.Line++ s.lastLineLen = s.srcPos.Column s.srcPos.Column = 0 } - // If we see a null character with data left, then that is an error - if ch == '\x00' && s.buf.Len() > 0 { + if ch == '\x00' { s.err("unexpected null character (0x00)") return eof } + if ch == '\uE123' { + s.err("unicode code point U+E123 reserved for internal use") + return utf8.RuneError + } + // debug // fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column) return ch @@ -432,16 +433,16 @@ func (s *Scanner) scanHeredoc() { // Read the identifier identBytes := s.src[offs : s.srcPos.Offset-s.lastCharLen] - if len(identBytes) == 0 { + if len(identBytes) == 0 || (len(identBytes) == 1 && identBytes[0] == '-') { s.err("zero-length heredoc anchor") return } var identRegexp *regexp.Regexp if identBytes[0] == '-' { - identRegexp = regexp.MustCompile(fmt.Sprintf(`[[:space:]]*%s\z`, identBytes[1:])) + identRegexp = regexp.MustCompile(fmt.Sprintf(`^[[:space:]]*%s\r*\z`, identBytes[1:])) } else { - identRegexp = regexp.MustCompile(fmt.Sprintf(`[[:space:]]*%s\z`, identBytes)) + identRegexp = regexp.MustCompile(fmt.Sprintf(`^[[:space:]]*%s\r*\z`, identBytes)) } // Read the actual string value @@ -551,7 +552,7 @@ func (s *Scanner) scanDigits(ch rune, base, n int) rune { s.err("illegal char escape") } - if n != start { + if n != start && ch != eof { // we scanned all digits, put the last non digit char back, // only if we read anything at all s.unread() diff --git a/vendor/github.com/jpillora/backoff/README.md b/vendor/github.com/jpillora/backoff/README.md index 81e77cd7..ee4d6230 100644 --- a/vendor/github.com/jpillora/backoff/README.md +++ b/vendor/github.com/jpillora/backoff/README.md @@ -116,4 +116,4 @@ https://godoc.org/github.com/jpillora/backoff #### Credits -Forked from some JavaScript written by [@tj](https://github.com/tj) \ No newline at end of file +Forked from [some JavaScript](https://github.com/segmentio/backo) written by [@tj](https://github.com/tj) diff --git a/vendor/github.com/jpillora/backoff/backoff.go b/vendor/github.com/jpillora/backoff/backoff.go index a50d0e95..b4941b6e 100644 --- a/vendor/github.com/jpillora/backoff/backoff.go +++ b/vendor/github.com/jpillora/backoff/backoff.go @@ -71,7 +71,8 @@ func (b *Backoff) ForAttempt(attempt float64) time.Duration { //keep within bounds if dur < min { return min - } else if dur > max { + } + if dur > max { return max } return dur @@ -86,3 +87,13 @@ func (b *Backoff) Reset() { func (b *Backoff) Attempt() float64 { return b.attempt } + +// Copy returns a backoff with equals constraints as the original +func (b *Backoff) Copy() *Backoff { + return &Backoff{ + Factor: b.Factor, + Jitter: b.Jitter, + Min: b.Min, + Max: b.Max, + } +} diff --git a/vendor/github.com/labstack/echo/.travis.yml b/vendor/github.com/labstack/echo/.travis.yml index 951c2d39..05e53b16 100644 --- a/vendor/github.com/labstack/echo/.travis.yml +++ b/vendor/github.com/labstack/echo/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.8.x - 1.9.x + - 1.10.x - tip install: - make dependency diff --git a/vendor/github.com/labstack/echo/Gopkg.lock b/vendor/github.com/labstack/echo/Gopkg.lock index 272aaa52..f3c3b8d2 100644 --- a/vendor/github.com/labstack/echo/Gopkg.lock +++ b/vendor/github.com/labstack/echo/Gopkg.lock @@ -10,26 +10,26 @@ [[projects]] name = "github.com/dgrijalva/jwt-go" packages = ["."] - revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c" - version = "v3.0.0" + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" [[projects]] name = "github.com/labstack/gommon" packages = ["bytes","color","log","random"] - revision = "1121fd3e243c202482226a7afe4dcd07ffc4139a" - version = "v0.2.1" + revision = "6fe1405d73ec4bd4cd8a4ac8e2a2b2bf95d03954" + version = "0.2.4" [[projects]] name = "github.com/mattn/go-colorable" packages = ["."] - revision = "d228849504861217f796da67fae4f6e347643f15" - version = "v0.0.7" + revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" + version = "v0.0.9" [[projects]] name = "github.com/mattn/go-isatty" packages = ["."] - revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe" - version = "v0.0.2" + revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" + version = "v0.0.3" [[projects]] name = "github.com/pmezard/go-difflib" @@ -40,8 +40,8 @@ [[projects]] name = "github.com/stretchr/testify" packages = ["assert"] - revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" - version = "v1.1.4" + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" [[projects]] branch = "master" @@ -59,17 +59,17 @@ branch = "master" name = "golang.org/x/crypto" packages = ["acme","acme/autocert"] - revision = "e1a4589e7d3ea14a3352255d04b6f1a418845e5e" + revision = "182114d582623c1caa54f73de9c7224e23a48487" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "b90f89a1e7a9c1f6b918820b3daa7f08488c8594" + revision = "c28acc882ebcbfbe8ce9f0f14b9ac26ee138dd51" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "5f74a2a2ba5b07475ad0faa1b4c021b973ad40b2ae749e3d94e15fe839bb440e" + inputs-digest = "9c7b45e80fe353405800cf01f429b3a203cfb8d4468a04c64a908e11a98ea764" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/labstack/echo/Gopkg.toml b/vendor/github.com/labstack/echo/Gopkg.toml index a24f61b9..61de60cb 100644 --- a/vendor/github.com/labstack/echo/Gopkg.toml +++ b/vendor/github.com/labstack/echo/Gopkg.toml @@ -1,82 +1,37 @@ -## Gopkg.toml example (these lines may be deleted) - -## "metadata" defines metadata about the project that could be used by other independent -## systems. The metadata defined here will be ignored by dep. -# [metadata] -# key1 = "value that convey data to other systems" -# system1-data = "value that is used by a system" -# system2-data = "value that is used by another system" - -## "required" lists a set of packages (not projects) that must be included in -## Gopkg.lock. This list is merged with the set of packages imported by the current -## project. Use it when your project needs a package it doesn't explicitly import - -## including "main" packages. -# required = ["github.com/user/thing/cmd/thing"] - -## "ignored" lists a set of packages (not projects) that are ignored when -## dep statically analyzes source code. Ignored packages can be in this project, -## or in a dependency. -# ignored = ["github.com/user/project/badpkg"] - -## Constraints are rules for how directly imported projects -## may be incorporated into the depgraph. They are respected by -## dep whether coming from the Gopkg.toml of the current project or a dependency. -# [[constraint]] -## Required: the root import path of the project being constrained. -# name = "github.com/user/project" +# Gopkg.toml example # -## Recommended: the version constraint to enforce for the project. -## Only one of "branch", "version" or "revision" can be specified. -# version = "1.0.0" -# branch = "master" -# revision = "abc123" +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. # -## Optional: an alternate location (URL or import path) for the project's source. -# source = "https://github.com/myfork/package.git" +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # -## "metadata" defines metadata about the dependency or override that could be used -## by other independent systems. The metadata defined here will be ignored by dep. -# [metadata] -# key1 = "value that convey data to other systems" -# system1-data = "value that is used by a system" -# system2-data = "value that is used by another system" - -## Overrides have the same structure as [[constraint]], but supersede all -## [[constraint]] declarations from all projects. Only [[override]] from -## the current project's are applied. -## -## Overrides are a sledgehammer. Use them only as a last resort. -# [[override]] -## Required: the root import path of the project being constrained. -# name = "github.com/user/project" +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" # -## Optional: specifying a version constraint override will cause all other -## constraints on this project to be ignored; only the overridden constraint -## need be satisfied. -## Again, only one of "branch", "version" or "revision" can be specified. -# version = "1.0.0" -# branch = "master" -# revision = "abc123" +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" # -## Optional: specifying an alternate source location as an override will -## enforce that the alternate location is used for that project, regardless of -## what source location any dependent projects specify. -# source = "https://github.com/myfork/package.git" - +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" [[constraint]] name = "github.com/dgrijalva/jwt-go" - version = "3.0.0" + version = "3.2.0" [[constraint]] name = "github.com/labstack/gommon" - version = "0.2.1" + version = "0.2.4" [[constraint]] name = "github.com/stretchr/testify" - version = "1.1.4" + version = "1.2.1" [[constraint]] branch = "master" @@ -84,4 +39,4 @@ [[constraint]] branch = "master" - name = "golang.org/x/crypto" + name = "golang.org/x/crypto" \ No newline at end of file diff --git a/vendor/github.com/labstack/echo/Makefile b/vendor/github.com/labstack/echo/Makefile index 3952d7bd..494667d8 100644 --- a/vendor/github.com/labstack/echo/Makefile +++ b/vendor/github.com/labstack/echo/Makefile @@ -11,3 +11,7 @@ test: go test -race -coverprofile=profile.out -covermode=atomic $$d || exit 1; \ [ -f profile.out ] && cat profile.out >> coverage.txt && rm profile.out; \ done + +tag: + @git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'` + @git tag|grep -v ^v diff --git a/vendor/github.com/labstack/echo/README.md b/vendor/github.com/labstack/echo/README.md index d840f343..0f609df4 100644 --- a/vendor/github.com/labstack/echo/README.md +++ b/vendor/github.com/labstack/echo/README.md @@ -26,9 +26,13 @@ - Automatic TLS via Let’s Encrypt - HTTP/2 support -## Performance +## Benchmarks - +Date: 2018/03/15
+Source: https://github.com/vishr/web-framework-benchmark
+Lower is better! + + ## [Guide](https://echo.labstack.com/guide) diff --git a/vendor/github.com/labstack/echo/bind.go b/vendor/github.com/labstack/echo/bind.go index 186bd83d..38e07150 100644 --- a/vendor/github.com/labstack/echo/bind.go +++ b/vendor/github.com/labstack/echo/bind.go @@ -80,7 +80,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag val := reflect.ValueOf(ptr).Elem() if typ.Kind() != reflect.Struct { - return errors.New("Binding element must be a struct") + return errors.New("binding element must be a struct") } for i := 0; i < typ.NumField(); i++ { diff --git a/vendor/github.com/labstack/echo/context.go b/vendor/github.com/labstack/echo/context.go index 39801f00..cf780c51 100644 --- a/vendor/github.com/labstack/echo/context.go +++ b/vendor/github.com/labstack/echo/context.go @@ -206,6 +206,13 @@ const ( indexPage = "index.html" ) +func (c *context) writeContentType(value string) { + header := c.Response().Header() + if header.Get(HeaderContentType) == "" { + header.Set(HeaderContentType, value) + } +} + func (c *context) Request() *http.Request { return c.request } @@ -430,7 +437,7 @@ func (c *context) JSONP(code int, callback string, i interface{}) (err error) { } func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) { - c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8) + c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8) c.response.WriteHeader(code) if _, err = c.response.Write([]byte(callback + "(")); err != nil { return @@ -463,7 +470,7 @@ func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) } func (c *context) XMLBlob(code int, b []byte) (err error) { - c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) + c.writeContentType(MIMEApplicationXMLCharsetUTF8) c.response.WriteHeader(code) if _, err = c.response.Write([]byte(xml.Header)); err != nil { return @@ -473,14 +480,14 @@ func (c *context) XMLBlob(code int, b []byte) (err error) { } func (c *context) Blob(code int, contentType string, b []byte) (err error) { - c.response.Header().Set(HeaderContentType, contentType) + c.writeContentType(contentType) c.response.WriteHeader(code) _, err = c.response.Write(b) return } func (c *context) Stream(code int, contentType string, r io.Reader) (err error) { - c.response.Header().Set(HeaderContentType, contentType) + c.writeContentType(contentType) c.response.WriteHeader(code) _, err = io.Copy(c.response, r) return @@ -509,18 +516,17 @@ func (c *context) File(file string) (err error) { return } -func (c *context) Attachment(file, name string) (err error) { +func (c *context) Attachment(file, name string) error { return c.contentDisposition(file, name, "attachment") } -func (c *context) Inline(file, name string) (err error) { +func (c *context) Inline(file, name string) error { return c.contentDisposition(file, name, "inline") } -func (c *context) contentDisposition(file, name, dispositionType string) (err error) { +func (c *context) contentDisposition(file, name, dispositionType string) error { c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name)) - c.File(file) - return + return c.File(file) } func (c *context) NoContent(code int) error { diff --git a/vendor/github.com/labstack/echo/echo.go b/vendor/github.com/labstack/echo/echo.go index 4a54b31a..41ac6b5e 100644 --- a/vendor/github.com/labstack/echo/echo.go +++ b/vendor/github.com/labstack/echo/echo.go @@ -38,6 +38,7 @@ package echo import ( "bytes" + stdContext "context" "crypto/tls" "errors" "fmt" @@ -45,6 +46,7 @@ import ( stdLog "log" "net" "net/http" + "net/url" "path" "path/filepath" "reflect" @@ -81,8 +83,7 @@ type ( Binder Binder Validator Validator Renderer Renderer - // Mutex sync.RWMutex - Logger Logger + Logger Logger } // Route contains a handler and information for matching against requests. @@ -94,9 +95,9 @@ type ( // HTTPError represents an error that occurred while handling a request. HTTPError struct { - Code int - Message interface{} - Inner error // Stores the error returned by an external dependency + Code int + Message interface{} + Internal error // Stores the error returned by an external dependency } // MiddlewareFunc defines a function to process middleware. @@ -129,15 +130,16 @@ type ( // HTTP methods const ( - CONNECT = "CONNECT" - DELETE = "DELETE" - GET = "GET" - HEAD = "HEAD" - OPTIONS = "OPTIONS" - PATCH = "PATCH" - POST = "POST" - PUT = "PUT" - TRACE = "TRACE" + CONNECT = "CONNECT" + DELETE = "DELETE" + GET = "GET" + HEAD = "HEAD" + OPTIONS = "OPTIONS" + PATCH = "PATCH" + POST = "POST" + PROPFIND = "PROPFIND" + PUT = "PUT" + TRACE = "TRACE" ) // MIME types @@ -191,6 +193,7 @@ const ( HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" HeaderXRealIP = "X-Real-IP" HeaderXRequestID = "X-Request-ID" + HeaderXRequestedWith = "X-Requested-With" HeaderServer = "Server" HeaderOrigin = "Origin" @@ -214,7 +217,7 @@ const ( ) const ( - version = "3.2.6" + Version = "3.3.5" website = "https://echo.labstack.com" // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo banner = ` @@ -238,6 +241,7 @@ var ( OPTIONS, PATCH, POST, + PROPFIND, PUT, TRACE, } @@ -251,10 +255,10 @@ var ( ErrForbidden = NewHTTPError(http.StatusForbidden) ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) - ErrValidatorNotRegistered = errors.New("Validator not registered") - ErrRendererNotRegistered = errors.New("Renderer not registered") - ErrInvalidRedirectCode = errors.New("Invalid redirect status code") - ErrCookieNotFound = errors.New("Cookie not found") + ErrValidatorNotRegistered = errors.New("validator not registered") + ErrRendererNotRegistered = errors.New("renderer not registered") + ErrInvalidRedirectCode = errors.New("invalid redirect status code") + ErrCookieNotFound = errors.New("cookie not found") ) // Error handlers @@ -321,8 +325,8 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { if he, ok := err.(*HTTPError); ok { code = he.Code msg = he.Message - if he.Inner != nil { - msg = fmt.Sprintf("%v, %v", err, he.Inner) + if he.Internal != nil { + msg = fmt.Sprintf("%v, %v", err, he.Internal) } } else if e.Debug { msg = err.Error() @@ -443,7 +447,7 @@ func (e *Echo) Static(prefix, root string) *Route { func static(i i, prefix, root string) *Route { h := func(c Context) error { - p, err := PathUnescape(c.Param("*")) + p, err := url.PathUnescape(c.Param("*")) if err != nil { return err } @@ -530,7 +534,7 @@ func (e *Echo) Reverse(name string, params ...interface{}) string { // Routes returns the registered routes. func (e *Echo) Routes() []*Route { - routes := []*Route{} + routes := make([]*Route, 0, len(e.router.routes)) for _, v := range e.router.routes { routes = append(routes, v) } @@ -551,39 +555,48 @@ func (e *Echo) ReleaseContext(c Context) { // ServeHTTP implements `http.Handler` interface, which serves HTTP requests. func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Acquire lock - // e.Mutex.RLock() - // defer e.Mutex.RUnlock() - // Acquire context c := e.pool.Get().(*context) - defer e.pool.Put(c) c.Reset(r, w) - // Middleware - h := func(c Context) error { - method := r.Method + m := r.Method + h := NotFoundHandler + + if e.premiddleware == nil { path := r.URL.RawPath if path == "" { path = r.URL.Path } - e.router.Find(method, path, c) - h := c.Handler() + e.router.Find(m, getPath(r), c) + h = c.Handler() for i := len(e.middleware) - 1; i >= 0; i-- { h = e.middleware[i](h) } - return h(c) - } - - // Premiddleware - for i := len(e.premiddleware) - 1; i >= 0; i-- { - h = e.premiddleware[i](h) + } else { + h = func(c Context) error { + path := r.URL.RawPath + if path == "" { + path = r.URL.Path + } + e.router.Find(m, getPath(r), c) + h := c.Handler() + for i := len(e.middleware) - 1; i >= 0; i-- { + h = e.middleware[i](h) + } + return h(c) + } + for i := len(e.premiddleware) - 1; i >= 0; i-- { + h = e.premiddleware[i](h) + } } // Execute chain if err := h(c); err != nil { e.HTTPErrorHandler(err, c) } + + // Release context + e.pool.Put(c) } // Start starts an HTTP server. @@ -609,6 +622,10 @@ func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) { // StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org. func (e *Echo) StartAutoTLS(address string) error { + if e.Listener == nil { + go http.ListenAndServe(":http", e.AutoTLSManager.HTTPHandler(nil)) + } + s := e.TLSServer s.TLSConfig = new(tls.Config) s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate @@ -635,7 +652,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) { } if !e.HideBanner { - e.colorer.Printf(banner, e.colorer.Red("v"+version), e.colorer.Blue(website)) + e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website)) } if s.TLSConfig == nil { @@ -663,6 +680,24 @@ func (e *Echo) StartServer(s *http.Server) (err error) { return s.Serve(e.TLSListener) } +// Close immediately stops the server. +// It internally calls `http.Server#Close()`. +func (e *Echo) Close() error { + if err := e.TLSServer.Close(); err != nil { + return err + } + return e.Server.Close() +} + +// Shutdown stops server the gracefully. +// It internally calls `http.Server#Shutdown()`. +func (e *Echo) Shutdown(ctx stdContext.Context) error { + if err := e.TLSServer.Shutdown(ctx); err != nil { + return err + } + return e.Server.Shutdown(ctx) +} + // NewHTTPError creates a new HTTPError instance. func NewHTTPError(code int, message ...interface{}) *HTTPError { he := &HTTPError{Code: code, Message: http.StatusText(code)} @@ -698,6 +733,14 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc { } } +func getPath(r *http.Request) string { + path := r.URL.RawPath + if path == "" { + path = r.URL.Path + } + return path +} + func handlerName(h HandlerFunc) string { t := reflect.ValueOf(h).Type() if t.Kind() == reflect.Func { @@ -706,6 +749,11 @@ func handlerName(h HandlerFunc) string { return t.String() } +// // PathUnescape is wraps `url.PathUnescape` +// func PathUnescape(s string) (string, error) { +// return url.PathUnescape(s) +// } + // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. It's used by ListenAndServe and ListenAndServeTLS so // dead TCP connections (e.g. closing laptop mid-download) eventually diff --git a/vendor/github.com/labstack/echo/echo_go1.8.go b/vendor/github.com/labstack/echo/echo_go1.8.go deleted file mode 100644 index 340bed70..00000000 --- a/vendor/github.com/labstack/echo/echo_go1.8.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build go1.8 - -package echo - -import ( - stdContext "context" -) - -// Close immediately stops the server. -// It internally calls `http.Server#Close()`. -func (e *Echo) Close() error { - if err := e.TLSServer.Close(); err != nil { - return err - } - return e.Server.Close() -} - -// Shutdown stops server the gracefully. -// It internally calls `http.Server#Shutdown()`. -func (e *Echo) Shutdown(ctx stdContext.Context) error { - if err := e.TLSServer.Shutdown(ctx); err != nil { - return err - } - return e.Server.Shutdown(ctx) -} diff --git a/vendor/github.com/labstack/echo/group.go b/vendor/github.com/labstack/echo/group.go index f7e61a2e..5257e83c 100644 --- a/vendor/github.com/labstack/echo/group.go +++ b/vendor/github.com/labstack/echo/group.go @@ -92,7 +92,7 @@ func (g *Group) Match(methods []string, path string, handler HandlerFunc, middle // Group creates a new sub-group with prefix and optional sub-group-level middleware. func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group { - m := []MiddlewareFunc{} + m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware)) m = append(m, g.middleware...) m = append(m, middleware...) return g.echo.Group(g.prefix+prefix, m...) @@ -113,7 +113,7 @@ func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...Midd // Combine into a new slice to avoid accidentally passing the same slice for // multiple routes, which would lead to later add() calls overwriting the // middleware from earlier calls. - m := []MiddlewareFunc{} + m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware)) m = append(m, g.middleware...) m = append(m, middleware...) return g.echo.Add(method, g.prefix+path, handler, m...) diff --git a/vendor/github.com/labstack/echo/middleware/basic_auth.go b/vendor/github.com/labstack/echo/middleware/basic_auth.go index 6d6a37b4..e6c96324 100644 --- a/vendor/github.com/labstack/echo/middleware/basic_auth.go +++ b/vendor/github.com/labstack/echo/middleware/basic_auth.go @@ -93,10 +93,8 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc { } } - realm := "" - if config.Realm == defaultRealm { - realm = defaultRealm - } else { + realm := defaultRealm + if config.Realm != defaultRealm { realm = strconv.Quote(config.Realm) } diff --git a/vendor/github.com/labstack/echo/middleware/body_dump.go b/vendor/github.com/labstack/echo/middleware/body_dump.go index 14cf33d1..e64e5e11 100644 --- a/vendor/github.com/labstack/echo/middleware/body_dump.go +++ b/vendor/github.com/labstack/echo/middleware/body_dump.go @@ -3,12 +3,11 @@ package middleware import ( "bufio" "bytes" + "io" "io/ioutil" "net" "net/http" - "io" - "github.com/labstack/echo" ) diff --git a/vendor/github.com/labstack/echo/middleware/body_limit.go b/vendor/github.com/labstack/echo/middleware/body_limit.go index 8d8281f4..c83f57e1 100644 --- a/vendor/github.com/labstack/echo/middleware/body_limit.go +++ b/vendor/github.com/labstack/echo/middleware/body_limit.go @@ -105,6 +105,7 @@ func (r *limitedReader) Close() error { func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) { r.reader = reader r.context = context + r.read = 0 } func limitedReaderPool(c BodyLimitConfig) sync.Pool { diff --git a/vendor/github.com/labstack/echo/middleware/csrf.go b/vendor/github.com/labstack/echo/middleware/csrf.go index 0d2b7fd6..5d1f4671 100644 --- a/vendor/github.com/labstack/echo/middleware/csrf.go +++ b/vendor/github.com/labstack/echo/middleware/csrf.go @@ -126,8 +126,8 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc { k, err := c.Cookie(config.CookieName) token := "" + // Generate token if err != nil { - // Generate token token = random.String(config.TokenLength) } else { // Reuse token @@ -143,7 +143,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if !validateCSRFToken(token, clientToken) { - return echo.NewHTTPError(http.StatusForbidden, "Invalid csrf token") + return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token") } } @@ -187,7 +187,7 @@ func csrfTokenFromForm(param string) csrfTokenExtractor { return func(c echo.Context) (string, error) { token := c.FormValue(param) if token == "" { - return "", errors.New("Missing csrf token in the form parameter") + return "", errors.New("missing csrf token in the form parameter") } return token, nil } @@ -199,7 +199,7 @@ func csrfTokenFromQuery(param string) csrfTokenExtractor { return func(c echo.Context) (string, error) { token := c.QueryParam(param) if token == "" { - return "", errors.New("Missing csrf token in the query string") + return "", errors.New("missing csrf token in the query string") } return token, nil } diff --git a/vendor/github.com/labstack/echo/middleware/jwt.go b/vendor/github.com/labstack/echo/middleware/jwt.go index 47d885b0..e98040ae 100644 --- a/vendor/github.com/labstack/echo/middleware/jwt.go +++ b/vendor/github.com/labstack/echo/middleware/jwt.go @@ -58,8 +58,8 @@ const ( // Errors var ( - ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "Missing or malformed jwt") - ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired jwt") + ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt") + ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt") ) var ( @@ -116,7 +116,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { config.keyFunc = func(t *jwt.Token) (interface{}, error) { // Check the signing method if t.Method.Alg() != config.SigningMethod { - return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"]) + return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"]) } return config.SigningKey, nil } @@ -156,9 +156,9 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { return next(c) } return &echo.HTTPError{ - Code: ErrJWTInvalid.Code, - Message: ErrJWTInvalid.Message, - Inner: err, + Code: ErrJWTInvalid.Code, + Message: ErrJWTInvalid.Message, + Internal: err, } } } diff --git a/vendor/github.com/labstack/echo/middleware/key_auth.go b/vendor/github.com/labstack/echo/middleware/key_auth.go index 4990afd9..c12f4ca9 100644 --- a/vendor/github.com/labstack/echo/middleware/key_auth.go +++ b/vendor/github.com/labstack/echo/middleware/key_auth.go @@ -114,14 +114,14 @@ func keyFromHeader(header string, authScheme string) keyExtractor { return func(c echo.Context) (string, error) { auth := c.Request().Header.Get(header) if auth == "" { - return "", errors.New("Missing key in request header") + return "", errors.New("missing key in request header") } if header == echo.HeaderAuthorization { l := len(authScheme) if len(auth) > l+1 && auth[:l] == authScheme { return auth[l+1:], nil } - return "", errors.New("Invalid key in the request header") + return "", errors.New("invalid key in the request header") } return auth, nil } @@ -132,7 +132,7 @@ func keyFromQuery(param string) keyExtractor { return func(c echo.Context) (string, error) { key := c.QueryParam(param) if key == "" { - return "", errors.New("Missing key in the query string") + return "", errors.New("missing key in the query string") } return key, nil } @@ -143,7 +143,7 @@ func keyFromForm(param string) keyExtractor { return func(c echo.Context) (string, error) { key := c.FormValue(param) if key == "" { - return "", errors.New("Missing key in the form") + return "", errors.New("missing key in the form") } return key, nil } diff --git a/vendor/github.com/labstack/echo/middleware/logger.go b/vendor/github.com/labstack/echo/middleware/logger.go index c7b80f8c..87af575f 100644 --- a/vendor/github.com/labstack/echo/middleware/logger.go +++ b/vendor/github.com/labstack/echo/middleware/logger.go @@ -47,7 +47,7 @@ type ( // Example "${remote_ip} ${status}" // // Optional. Default value DefaultLoggerConfig.Format. - Format string `yaml:"format"` + Format string `yaml:"format"` // Optional. Default value DefaultLoggerConfig.CustomTimeFormat. CustomTimeFormat string `yaml:"custom_time_format"` @@ -70,9 +70,9 @@ var ( `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` + `"latency_human":"${latency_human}","bytes_in":${bytes_in},` + `"bytes_out":${bytes_out}}` + "\n", - CustomTimeFormat:"2006-01-02 15:04:05.00000", - Output: os.Stdout, - colorer: color.New(), + CustomTimeFormat: "2006-01-02 15:04:05.00000", + Output: os.Stdout, + colorer: color.New(), } ) diff --git a/vendor/github.com/labstack/echo/middleware/proxy.go b/vendor/github.com/labstack/echo/middleware/proxy.go index ae3ff527..f6147737 100644 --- a/vendor/github.com/labstack/echo/middleware/proxy.go +++ b/vendor/github.com/labstack/echo/middleware/proxy.go @@ -108,15 +108,15 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { return } - errc := make(chan error, 2) + errCh := make(chan error, 2) cp := func(dst io.Writer, src io.Reader) { - _, err := io.Copy(dst, src) - errc <- err + _, err = io.Copy(dst, src) + errCh <- err } go cp(out, in) go cp(in, out) - err = <-errc + err = <-errCh if err != nil && err != io.EOF { c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err) } diff --git a/vendor/github.com/labstack/echo/middleware/static.go b/vendor/github.com/labstack/echo/middleware/static.go index 7208c3a2..55485f34 100644 --- a/vendor/github.com/labstack/echo/middleware/static.go +++ b/vendor/github.com/labstack/echo/middleware/static.go @@ -2,13 +2,16 @@ package middleware import ( "fmt" + "html/template" "net/http" + "net/url" "os" "path" "path/filepath" "strings" "github.com/labstack/echo" + "github.com/labstack/gommon/bytes" ) type ( @@ -36,6 +39,78 @@ type ( } ) +const html = ` + + + + + + + {{ .Name }} + + + +
+ {{ .Name }} +
+
    + {{ range .Files }} +
  • + {{ if .Dir }} + {{ $name := print .Name "/" }} + {{ $name }} + {{ else }} + {{ .Name }} + {{ .Size }} + {{ end }} +
  • + {{ end }} +
+ + +` + var ( // DefaultStaticConfig is the default Static middleware config. DefaultStaticConfig = StaticConfig{ @@ -66,6 +141,12 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { config.Index = DefaultStaticConfig.Index } + // Index template + t, err := template.New("index").Parse(html) + if err != nil { + panic(fmt.Sprintf("echo: %v", err)) + } + return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) (err error) { if config.Skipper(c) { @@ -76,7 +157,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`. p = c.Param("*") } - p, err = echo.PathUnescape(p) + p, err = url.PathUnescape(p) if err != nil { return } @@ -103,7 +184,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { if err != nil { if config.Browse { - return listDir(name, c.Response()) + return listDir(t, name, c.Response()) } if os.IsNotExist(err) { return next(c) @@ -119,32 +200,30 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { } } -func listDir(name string, res *echo.Response) (err error) { - dir, err := os.Open(name) +func listDir(t *template.Template, name string, res *echo.Response) (err error) { + file, err := os.Open(name) if err != nil { return } - dirs, err := dir.Readdir(-1) + files, err := file.Readdir(-1) if err != nil { return } - // Create a directory index + // Create directory index res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) - if _, err = fmt.Fprintf(res, "
\n"); err != nil {
-		return
+	data := struct {
+		Name  string
+		Files []interface{}
+	}{
+		Name: name,
 	}
-	for _, d := range dirs {
-		name := d.Name()
-		color := "#212121"
-		if d.IsDir() {
-			color = "#e91e63"
-			name += "/"
-		}
-		if _, err = fmt.Fprintf(res, "%s\n", name, color, name); err != nil {
-			return
-		}
+	for _, f := range files {
+		data.Files = append(data.Files, struct {
+			Name string
+			Dir  bool
+			Size string
+		}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
 	}
-	_, err = fmt.Fprintf(res, "
\n") - return + return t.Execute(res, data) } diff --git a/vendor/github.com/labstack/echo/response.go b/vendor/github.com/labstack/echo/response.go index 93204098..6244783b 100644 --- a/vendor/github.com/labstack/echo/response.go +++ b/vendor/github.com/labstack/echo/response.go @@ -4,7 +4,6 @@ import ( "bufio" "net" "net/http" - "strconv" ) type ( @@ -12,14 +11,13 @@ type ( // by an HTTP handler to construct an HTTP response. // See: https://golang.org/pkg/net/http/#ResponseWriter Response struct { - echo *Echo - contentLength int64 - beforeFuncs []func() - afterFuncs []func() - Writer http.ResponseWriter - Status int - Size int64 - Committed bool + echo *Echo + beforeFuncs []func() + afterFuncs []func() + Writer http.ResponseWriter + Status int + Size int64 + Committed bool } ) @@ -64,7 +62,6 @@ func (r *Response) WriteHeader(code int) { r.Status = code r.Writer.WriteHeader(code) r.Committed = true - r.contentLength, _ = strconv.ParseInt(r.Header().Get(HeaderContentLength), 10, 0) } // Write writes the data to the connection as part of an HTTP reply. @@ -74,10 +71,8 @@ func (r *Response) Write(b []byte) (n int, err error) { } n, err = r.Writer.Write(b) r.Size += int64(n) - if r.Size == r.contentLength { - for _, fn := range r.afterFuncs { - fn() - } + for _, fn := range r.afterFuncs { + fn() } return } @@ -106,7 +101,6 @@ func (r *Response) CloseNotify() <-chan bool { } func (r *Response) reset(w http.ResponseWriter) { - r.contentLength = 0 r.beforeFuncs = nil r.afterFuncs = nil r.Writer = w diff --git a/vendor/github.com/labstack/echo/router.go b/vendor/github.com/labstack/echo/router.go index 3af4be0b..ff53da87 100644 --- a/vendor/github.com/labstack/echo/router.go +++ b/vendor/github.com/labstack/echo/router.go @@ -21,15 +21,16 @@ type ( kind uint8 children []*node methodHandler struct { - connect HandlerFunc - delete HandlerFunc - get HandlerFunc - head HandlerFunc - options HandlerFunc - patch HandlerFunc - post HandlerFunc - put HandlerFunc - trace HandlerFunc + connect HandlerFunc + delete HandlerFunc + get HandlerFunc + head HandlerFunc + options HandlerFunc + patch HandlerFunc + post HandlerFunc + propfind HandlerFunc + put HandlerFunc + trace HandlerFunc } ) @@ -59,8 +60,8 @@ func (r *Router) Add(method, path string, h HandlerFunc) { if path[0] != '/' { path = "/" + path } - ppath := path // Pristine path pnames := []string{} // Param names + ppath := path // Pristine path for i, l := 0, len(path); i < l; i++ { if path[i] == ':' { @@ -225,22 +226,24 @@ func (n *node) findChildByKind(t kind) *node { func (n *node) addHandler(method string, h HandlerFunc) { switch method { + case CONNECT: + n.methodHandler.connect = h + case DELETE: + n.methodHandler.delete = h case GET: n.methodHandler.get = h + case HEAD: + n.methodHandler.head = h + case OPTIONS: + n.methodHandler.options = h + case PATCH: + n.methodHandler.patch = h case POST: n.methodHandler.post = h + case PROPFIND: + n.methodHandler.propfind = h case PUT: n.methodHandler.put = h - case DELETE: - n.methodHandler.delete = h - case PATCH: - n.methodHandler.patch = h - case OPTIONS: - n.methodHandler.options = h - case HEAD: - n.methodHandler.head = h - case CONNECT: - n.methodHandler.connect = h case TRACE: n.methodHandler.trace = h } @@ -248,22 +251,24 @@ func (n *node) addHandler(method string, h HandlerFunc) { func (n *node) findHandler(method string) HandlerFunc { switch method { + case CONNECT: + return n.methodHandler.connect + case DELETE: + return n.methodHandler.delete case GET: return n.methodHandler.get + case HEAD: + return n.methodHandler.head + case OPTIONS: + return n.methodHandler.options + case PATCH: + return n.methodHandler.patch case POST: return n.methodHandler.post + case PROPFIND: + return n.methodHandler.propfind case PUT: return n.methodHandler.put - case DELETE: - return n.methodHandler.delete - case PATCH: - return n.methodHandler.patch - case OPTIONS: - return n.methodHandler.options - case HEAD: - return n.methodHandler.head - case CONNECT: - return n.methodHandler.connect case TRACE: return n.methodHandler.trace default: diff --git a/vendor/github.com/labstack/echo/util_go17.go b/vendor/github.com/labstack/echo/util_go17.go deleted file mode 100644 index eaae17e3..00000000 --- a/vendor/github.com/labstack/echo/util_go17.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build go1.7, !go1.8 - -package echo - -import ( - "net/url" -) - -// PathUnescape is wraps `url.QueryUnescape` -func PathUnescape(s string) (string, error) { - return url.QueryUnescape(s) -} diff --git a/vendor/github.com/labstack/echo/util_go18.go b/vendor/github.com/labstack/echo/util_go18.go deleted file mode 100644 index 8a37785b..00000000 --- a/vendor/github.com/labstack/echo/util_go18.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build go1.8 - -package echo - -import "net/url" - -// PathUnescape is wraps `url.PathUnescape` -func PathUnescape(s string) (string, error) { - return url.PathUnescape(s) -} diff --git a/vendor/github.com/lrstanley/girc/.travis.yml b/vendor/github.com/lrstanley/girc/.travis.yml index 658e65d9..96a1bb8a 100644 --- a/vendor/github.com/lrstanley/girc/.travis.yml +++ b/vendor/github.com/lrstanley/girc/.travis.yml @@ -1,14 +1,13 @@ language: go go: -- 1.8 -- 1.9 +- 1.11.x - tip before_install: -- go get -v github.com/golang/lint/golint +- go get -v golang.org/x/lint/golint script: - $HOME/gopath/bin/golint -min_confidence 0.9 -set_exit_status - GORACE="exitcode=1 halt_on_error=1" go test -v -coverprofile=coverage.txt -race -timeout 3m -count 3 -cpu 1,4 -- go tool vet -v -all . +- go vet -v . after_success: - bash <(curl -s https://codecov.io/bash) branches: diff --git a/vendor/github.com/lrstanley/girc/commands.go b/vendor/github.com/lrstanley/girc/commands.go index d22f7616..300db9ed 100644 --- a/vendor/github.com/lrstanley/girc/commands.go +++ b/vendor/github.com/lrstanley/girc/commands.go @@ -70,7 +70,7 @@ func (cmd *Commands) PartMessage(channel, message string) { // PRIVMSG specifically. ctcpType is the CTCP command, e.g. "FINGER", "TIME", // "VERSION", etc. func (cmd *Commands) SendCTCP(target, ctcpType, message string) { - out := encodeCTCPRaw(ctcpType, message) + out := EncodeCTCPRaw(ctcpType, message) if out == "" { panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message)) } @@ -95,7 +95,7 @@ func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interf // SendCTCPReply sends a CTCP response to target. Note that this method uses // NOTICE specifically. func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) { - out := encodeCTCPRaw(ctcpType, message) + out := EncodeCTCPRaw(ctcpType, message) if out == "" { panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message)) } diff --git a/vendor/github.com/lrstanley/girc/constants.go b/vendor/github.com/lrstanley/girc/constants.go index 4d3c65bc..76ba482d 100644 --- a/vendor/github.com/lrstanley/girc/constants.go +++ b/vendor/github.com/lrstanley/girc/constants.go @@ -6,6 +6,7 @@ package girc // Standard CTCP based constants. const ( + CTCP_ACTION = "ACTION" CTCP_PING = "PING" CTCP_PONG = "PONG" CTCP_VERSION = "VERSION" diff --git a/vendor/github.com/lrstanley/girc/ctcp.go b/vendor/github.com/lrstanley/girc/ctcp.go index 6076ab10..45fc6be5 100644 --- a/vendor/github.com/lrstanley/girc/ctcp.go +++ b/vendor/github.com/lrstanley/girc/ctcp.go @@ -30,18 +30,22 @@ type CTCPEvent struct { Reply bool `json:"reply"` } -// decodeCTCP decodes an incoming CTCP event, if it is CTCP. nil is returned -// if the incoming event does not match a valid CTCP. -func decodeCTCP(e *Event) *CTCPEvent { +// DecodeCTCP decodes an incoming CTCP event, if it is CTCP. nil is returned +// if the incoming event does not have valid CTCP encoding. +func DecodeCTCP(e *Event) *CTCPEvent { // http://www.irchelp.org/protocol/ctcpspec.html + if e == nil { + return nil + } + // Must be targeting a user/channel, AND trailing must have // DELIM+TAG+DELIM minimum (at least 3 chars). if len(e.Params) != 1 || len(e.Trailing) < 3 { return nil } - if (e.Command != PRIVMSG && e.Command != NOTICE) || !IsValidNick(e.Params[0]) { + if e.Command != PRIVMSG && e.Command != NOTICE { return nil } @@ -88,18 +92,18 @@ func decodeCTCP(e *Event) *CTCPEvent { } } -// encodeCTCP encodes a CTCP event into a string, including delimiters. -func encodeCTCP(ctcp *CTCPEvent) (out string) { +// EncodeCTCP encodes a CTCP event into a string, including delimiters. +func EncodeCTCP(ctcp *CTCPEvent) (out string) { if ctcp == nil { return "" } - return encodeCTCPRaw(ctcp.Command, ctcp.Text) + return EncodeCTCPRaw(ctcp.Command, ctcp.Text) } -// encodeCTCPRaw is much like encodeCTCP, however accepts a raw command and +// EncodeCTCPRaw is much like EncodeCTCP, however accepts a raw command and // string as input. -func encodeCTCPRaw(cmd, text string) (out string) { +func EncodeCTCPRaw(cmd, text string) (out string) { if len(cmd) <= 0 { return "" } @@ -145,6 +149,11 @@ func (c *CTCP) call(client *Client, event *CTCPEvent) { } if _, ok := c.handlers[event.Command]; !ok { + // If ACTION, don't do anything. + if event.Command == CTCP_ACTION { + return + } + // Send a ERRMSG reply, if we know who sent it. if event.Source != nil && IsValidNick(event.Source.Name) { client.Cmd.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query") diff --git a/vendor/github.com/lrstanley/girc/event.go b/vendor/github.com/lrstanley/girc/event.go index 5e17b15b..0b40b40b 100644 --- a/vendor/github.com/lrstanley/girc/event.go +++ b/vendor/github.com/lrstanley/girc/event.go @@ -355,11 +355,15 @@ func (e *Event) Pretty() (out string, ok bool) { } if (e.Command == PRIVMSG || e.Command == NOTICE) && len(e.Params) > 0 { - if ctcp := decodeCTCP(e); ctcp != nil { + if ctcp := DecodeCTCP(e); ctcp != nil { if ctcp.Reply { return } + if ctcp.Command == CTCP_ACTION { + return fmt.Sprintf("[%s] **%s** %s", strings.Join(e.Params, ","), ctcp.Source.Name, ctcp.Text), true + } + return fmt.Sprintf("[*] CTCP query from %s: %s%s", ctcp.Source.Name, ctcp.Command, " "+ctcp.Text), true } return fmt.Sprintf("[%s] (%s) %s", strings.Join(e.Params, ","), e.Source.Name, e.Trailing), true @@ -448,23 +452,27 @@ func (e *Event) Pretty() (out string, ok bool) { return "", false } -// IsAction checks to see if the event is a PRIVMSG, and is an ACTION (/me). +// IsAction checks to see if the event is an ACTION (/me). func (e *Event) IsAction() bool { - if e.Source == nil || e.Command != PRIVMSG || len(e.Trailing) < 9 { + if e.Command != PRIVMSG { return false } - if !strings.HasPrefix(e.Trailing, "\001ACTION") || e.Trailing[len(e.Trailing)-1] != ctcpDelim { - return false - } + ok, ctcp := e.IsCTCP() + return ok && ctcp.Command == CTCP_ACTION +} - return true +// IsCTCP checks to see if the event is a CTCP event, and if so, returns the +// converted CTCP event. +func (e *Event) IsCTCP() (ok bool, ctcp *CTCPEvent) { + ctcp = DecodeCTCP(e) + return ctcp != nil, ctcp } // IsFromChannel checks to see if a message was from a channel (rather than // a private message). func (e *Event) IsFromChannel() bool { - if e.Source == nil || e.Command != PRIVMSG || len(e.Params) < 1 { + if e.Source == nil || (e.Command != PRIVMSG && e.Command != NOTICE) || len(e.Params) < 1 { return false } @@ -478,7 +486,7 @@ func (e *Event) IsFromChannel() bool { // IsFromUser checks to see if a message was from a user (rather than a // channel). func (e *Event) IsFromUser() bool { - if e.Source == nil || e.Command != PRIVMSG || len(e.Params) < 1 { + if e.Source == nil || (e.Command != PRIVMSG && e.Command != NOTICE) || len(e.Params) < 1 { return false } diff --git a/vendor/github.com/lrstanley/girc/format.go b/vendor/github.com/lrstanley/girc/format.go index b974c3bd..762f602b 100644 --- a/vendor/github.com/lrstanley/girc/format.go +++ b/vendor/github.com/lrstanley/girc/format.go @@ -113,7 +113,7 @@ func Fmt(text string) string { if last > -1 { // A-Z, a-z, and "," - if text[i] != ',' && (text[i] <= 'A' || text[i] >= 'Z') && (text[i] <= 'a' || text[i] >= 'z') { + if text[i] != ',' && (text[i] < 'A' || text[i] > 'Z') && (text[i] < 'a' || text[i] > 'z') { last = -1 continue } diff --git a/vendor/github.com/lrstanley/girc/go.mod b/vendor/github.com/lrstanley/girc/go.mod new file mode 100644 index 00000000..57b39ae8 --- /dev/null +++ b/vendor/github.com/lrstanley/girc/go.mod @@ -0,0 +1 @@ +module github.com/lrstanley/girc diff --git a/vendor/github.com/lrstanley/girc/handler.go b/vendor/github.com/lrstanley/girc/handler.go index bde08976..ec717de6 100644 --- a/vendor/github.com/lrstanley/girc/handler.go +++ b/vendor/github.com/lrstanley/girc/handler.go @@ -46,7 +46,7 @@ func (c *Client) RunHandlers(event *Event) { } // Check if it's a CTCP. - if ctcp := decodeCTCP(event.Copy()); ctcp != nil { + if ctcp := DecodeCTCP(event.Copy()); ctcp != nil { // Execute it. c.CTCP.call(c, ctcp) } diff --git a/vendor/github.com/magiconair/properties/.travis.yml b/vendor/github.com/magiconair/properties/.travis.yml index 923e8664..3e7c3d2c 100644 --- a/vendor/github.com/magiconair/properties/.travis.yml +++ b/vendor/github.com/magiconair/properties/.travis.yml @@ -6,5 +6,5 @@ go: - 1.7.x - 1.8.x - 1.9.x - - "1.10" + - "1.10.x" - tip diff --git a/vendor/github.com/magiconair/properties/CHANGELOG.md b/vendor/github.com/magiconair/properties/CHANGELOG.md index adefa17e..f83adc20 100644 --- a/vendor/github.com/magiconair/properties/CHANGELOG.md +++ b/vendor/github.com/magiconair/properties/CHANGELOG.md @@ -1,5 +1,13 @@ ## Changelog +### [1.8](https://github.com/magiconair/properties/tree/v1.8) - 15 May 2018 + + * [PR #26](https://github.com/magiconair/properties/pull/26): Disable expansion during loading + + This adds the option to disable property expansion during loading. + + Thanks to [@kmala](https://github.com/kmala) for the patch. + ### [1.7.6](https://github.com/magiconair/properties/tree/v1.7.6) - 14 Feb 2018 * [PR #29](https://github.com/magiconair/properties/pull/29): Reworked expansion logic to handle more complex cases. diff --git a/vendor/github.com/magiconair/properties/decode.go b/vendor/github.com/magiconair/properties/decode.go index 0a961bb0..3ebf8049 100644 --- a/vendor/github.com/magiconair/properties/decode.go +++ b/vendor/github.com/magiconair/properties/decode.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/vendor/github.com/magiconair/properties/doc.go b/vendor/github.com/magiconair/properties/doc.go index 36c83680..f8822da2 100644 --- a/vendor/github.com/magiconair/properties/doc.go +++ b/vendor/github.com/magiconair/properties/doc.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -73,7 +73,7 @@ // # refers to the users' home dir // home = ${HOME} // -// # local key takes precendence over env var: u = foo +// # local key takes precedence over env var: u = foo // USER = foo // u = ${USER} // @@ -102,7 +102,7 @@ // v = p.GetString("key", "def") // v = p.GetDuration("key", 999) // -// As an alterantive properties may be applied with the standard +// As an alternative properties may be applied with the standard // library's flag implementation at any time. // // # Standard configuration diff --git a/vendor/github.com/magiconair/properties/integrate.go b/vendor/github.com/magiconair/properties/integrate.go index 0d775e03..74d38dc6 100644 --- a/vendor/github.com/magiconair/properties/integrate.go +++ b/vendor/github.com/magiconair/properties/integrate.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/vendor/github.com/magiconair/properties/lex.go b/vendor/github.com/magiconair/properties/lex.go index c63fcc60..367166d5 100644 --- a/vendor/github.com/magiconair/properties/lex.go +++ b/vendor/github.com/magiconair/properties/lex.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // diff --git a/vendor/github.com/magiconair/properties/load.go b/vendor/github.com/magiconair/properties/load.go index 9c83fd63..c8e1b580 100644 --- a/vendor/github.com/magiconair/properties/load.go +++ b/vendor/github.com/magiconair/properties/load.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -16,21 +16,157 @@ import ( type Encoding uint const ( + // utf8Default is a private placeholder for the zero value of Encoding to + // ensure that it has the correct meaning. UTF8 is the default encoding but + // was assigned a non-zero value which cannot be changed without breaking + // existing code. Clients should continue to use the public constants. + utf8Default Encoding = iota + // UTF8 interprets the input data as UTF-8. - UTF8 Encoding = 1 << iota + UTF8 // ISO_8859_1 interprets the input data as ISO-8859-1. ISO_8859_1 ) +type Loader struct { + // Encoding determines how the data from files and byte buffers + // is interpreted. For URLs the Content-Type header is used + // to determine the encoding of the data. + Encoding Encoding + + // DisableExpansion configures the property expansion of the + // returned property object. When set to true, the property values + // will not be expanded and the Property object will not be checked + // for invalid expansion expressions. + DisableExpansion bool + + // IgnoreMissing configures whether missing files or URLs which return + // 404 are reported as errors. When set to true, missing files and 404 + // status codes are not reported as errors. + IgnoreMissing bool +} + +// Load reads a buffer into a Properties struct. +func (l *Loader) LoadBytes(buf []byte) (*Properties, error) { + return l.loadBytes(buf, l.Encoding) +} + +// LoadAll reads the content of multiple URLs or files in the given order into +// a Properties struct. If IgnoreMissing is true then a 404 status code or +// missing file will not be reported as error. Encoding sets the encoding for +// files. For the URLs see LoadURL for the Content-Type header and the +// encoding. +func (l *Loader) LoadAll(names []string) (*Properties, error) { + all := NewProperties() + for _, name := range names { + n, err := expandName(name) + if err != nil { + return nil, err + } + + var p *Properties + switch { + case strings.HasPrefix(n, "http://"): + p, err = l.LoadURL(n) + case strings.HasPrefix(n, "https://"): + p, err = l.LoadURL(n) + default: + p, err = l.LoadFile(n) + } + if err != nil { + return nil, err + } + all.Merge(p) + } + + all.DisableExpansion = l.DisableExpansion + if all.DisableExpansion { + return all, nil + } + return all, all.check() +} + +// LoadFile reads a file into a Properties struct. +// If IgnoreMissing is true then a missing file will not be +// reported as error. +func (l *Loader) LoadFile(filename string) (*Properties, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + if l.IgnoreMissing && os.IsNotExist(err) { + LogPrintf("properties: %s not found. skipping", filename) + return NewProperties(), nil + } + return nil, err + } + return l.loadBytes(data, l.Encoding) +} + +// LoadURL reads the content of the URL into a Properties struct. +// +// The encoding is determined via the Content-Type header which +// should be set to 'text/plain'. If the 'charset' parameter is +// missing, 'iso-8859-1' or 'latin1' the encoding is set to +// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the +// encoding is set to UTF-8. A missing content type header is +// interpreted as 'text/plain; charset=utf-8'. +func (l *Loader) LoadURL(url string) (*Properties, error) { + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("properties: error fetching %q. %s", url, err) + } + + if resp.StatusCode == 404 && l.IgnoreMissing { + LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode) + return NewProperties(), nil + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("properties: %s error reading response. %s", url, err) + } + defer resp.Body.Close() + + ct := resp.Header.Get("Content-Type") + var enc Encoding + switch strings.ToLower(ct) { + case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1": + enc = ISO_8859_1 + case "", "text/plain; charset=utf-8": + enc = UTF8 + default: + return nil, fmt.Errorf("properties: invalid content type %s", ct) + } + + return l.loadBytes(body, enc) +} + +func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) { + p, err := parse(convert(buf, enc)) + if err != nil { + return nil, err + } + p.DisableExpansion = l.DisableExpansion + if p.DisableExpansion { + return p, nil + } + return p, p.check() +} + // Load reads a buffer into a Properties struct. func Load(buf []byte, enc Encoding) (*Properties, error) { - return loadBuf(buf, enc) + l := &Loader{Encoding: enc} + return l.LoadBytes(buf) } // LoadString reads an UTF8 string into a properties struct. func LoadString(s string) (*Properties, error) { - return loadBuf([]byte(s), UTF8) + l := &Loader{Encoding: UTF8} + return l.LoadBytes([]byte(s)) } // LoadMap creates a new Properties struct from a string map. @@ -44,34 +180,32 @@ func LoadMap(m map[string]string) *Properties { // LoadFile reads a file into a Properties struct. func LoadFile(filename string, enc Encoding) (*Properties, error) { - return loadAll([]string{filename}, enc, false) + l := &Loader{Encoding: enc} + return l.LoadAll([]string{filename}) } // LoadFiles reads multiple files in the given order into // a Properties struct. If 'ignoreMissing' is true then // non-existent files will not be reported as error. func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) { - return loadAll(filenames, enc, ignoreMissing) + l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing} + return l.LoadAll(filenames) } // LoadURL reads the content of the URL into a Properties struct. -// -// The encoding is determined via the Content-Type header which -// should be set to 'text/plain'. If the 'charset' parameter is -// missing, 'iso-8859-1' or 'latin1' the encoding is set to -// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the -// encoding is set to UTF-8. A missing content type header is -// interpreted as 'text/plain; charset=utf-8'. +// See Loader#LoadURL for details. func LoadURL(url string) (*Properties, error) { - return loadAll([]string{url}, UTF8, false) + l := &Loader{Encoding: UTF8} + return l.LoadAll([]string{url}) } // LoadURLs reads the content of multiple URLs in the given order into a -// Properties struct. If 'ignoreMissing' is true then a 404 status code will -// not be reported as error. See LoadURL for the Content-Type header +// Properties struct. If IgnoreMissing is true then a 404 status code will +// not be reported as error. See Loader#LoadURL for the Content-Type header // and the encoding. func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) { - return loadAll(urls, UTF8, ignoreMissing) + l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing} + return l.LoadAll(urls) } // LoadAll reads the content of multiple URLs or files in the given order into a @@ -79,7 +213,8 @@ func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) { // not be reported as error. Encoding sets the encoding for files. For the URLs please see // LoadURL for the Content-Type header and the encoding. func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) { - return loadAll(names, enc, ignoreMissing) + l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing} + return l.LoadAll(names) } // MustLoadString reads an UTF8 string into a Properties struct and @@ -122,90 +257,6 @@ func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties { return must(LoadAll(names, enc, ignoreMissing)) } -func loadBuf(buf []byte, enc Encoding) (*Properties, error) { - p, err := parse(convert(buf, enc)) - if err != nil { - return nil, err - } - return p, p.check() -} - -func loadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) { - result := NewProperties() - for _, name := range names { - n, err := expandName(name) - if err != nil { - return nil, err - } - var p *Properties - if strings.HasPrefix(n, "http://") || strings.HasPrefix(n, "https://") { - p, err = loadURL(n, ignoreMissing) - } else { - p, err = loadFile(n, enc, ignoreMissing) - } - if err != nil { - return nil, err - } - result.Merge(p) - - } - return result, result.check() -} - -func loadFile(filename string, enc Encoding, ignoreMissing bool) (*Properties, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - if ignoreMissing && os.IsNotExist(err) { - LogPrintf("properties: %s not found. skipping", filename) - return NewProperties(), nil - } - return nil, err - } - p, err := parse(convert(data, enc)) - if err != nil { - return nil, err - } - return p, nil -} - -func loadURL(url string, ignoreMissing bool) (*Properties, error) { - resp, err := http.Get(url) - if err != nil { - return nil, fmt.Errorf("properties: error fetching %q. %s", url, err) - } - if resp.StatusCode == 404 && ignoreMissing { - LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode) - return NewProperties(), nil - } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode) - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("properties: %s error reading response. %s", url, err) - } - if err = resp.Body.Close(); err != nil { - return nil, fmt.Errorf("properties: %s error reading response. %s", url, err) - } - - ct := resp.Header.Get("Content-Type") - var enc Encoding - switch strings.ToLower(ct) { - case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1": - enc = ISO_8859_1 - case "", "text/plain; charset=utf-8": - enc = UTF8 - default: - return nil, fmt.Errorf("properties: invalid content type %s", ct) - } - - p, err := parse(convert(body, enc)) - if err != nil { - return nil, err - } - return p, nil -} - func must(p *Properties, err error) *Properties { if err != nil { ErrorHandler(err) @@ -226,7 +277,7 @@ func expandName(name string) (string, error) { // first 256 unicode code points cover ISO-8859-1. func convert(buf []byte, enc Encoding) string { switch enc { - case UTF8: + case utf8Default, UTF8: return string(buf) case ISO_8859_1: runes := make([]rune, len(buf)) diff --git a/vendor/github.com/magiconair/properties/parser.go b/vendor/github.com/magiconair/properties/parser.go index 90f555cb..cdc4a803 100644 --- a/vendor/github.com/magiconair/properties/parser.go +++ b/vendor/github.com/magiconair/properties/parser.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/vendor/github.com/magiconair/properties/properties.go b/vendor/github.com/magiconair/properties/properties.go index 53f5b2ff..cb3d1a33 100644 --- a/vendor/github.com/magiconair/properties/properties.go +++ b/vendor/github.com/magiconair/properties/properties.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -83,6 +83,17 @@ func NewProperties() *Properties { } } +// Load reads a buffer into the given Properties struct. +func (p *Properties) Load(buf []byte, enc Encoding) error { + l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion} + newProperties, err := l.LoadBytes(buf) + if err != nil { + return err + } + p.Merge(newProperties) + return nil +} + // Get returns the expanded value for the given key if exists. // Otherwise, ok is false. func (p *Properties) Get(key string) (value string, ok bool) { diff --git a/vendor/github.com/magiconair/properties/rangecheck.go b/vendor/github.com/magiconair/properties/rangecheck.go index 2e907d54..b013a2e5 100644 --- a/vendor/github.com/magiconair/properties/rangecheck.go +++ b/vendor/github.com/magiconair/properties/rangecheck.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frank Schroeder. All rights reserved. +// Copyright 2018 Frank Schroeder. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/vendor/github.com/mattermost/mattermost-server/LICENSE.txt b/vendor/github.com/mattermost/mattermost-server/LICENSE.txt new file mode 100644 index 00000000..76e4c455 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/LICENSE.txt @@ -0,0 +1,897 @@ +Mattermost Licensing + +SOFTWARE LICENSING + +You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE + +- See MIT-COMPILED-LICENSE.md included in compiled versions for details + +You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways: + +1. Under the Free Software Foundation’s GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or +2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com + +You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/, +plugin/ and all subdirectories thereof) under the Apache License v2.0. + +We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not +link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and +(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of +a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license. + +MATTERMOST TRADEMARK GUIDELINES + +Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organization’s Trademark +Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions +you have about using these trademarks, please email trademark@mattermost.com + +------------------------------------------------------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +------------------------------------------------------------------------------ + +The software is released under the terms of the GNU Affero General Public +License, version 3. + + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/vendor/github.com/mattermost/mattermost-server/NOTICE.txt b/vendor/github.com/mattermost/mattermost-server/NOTICE.txt new file mode 100644 index 00000000..55141290 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/NOTICE.txt @@ -0,0 +1,3721 @@ +Mattermost Server +© 2015-present Mattermost, Inc. All Rights Reserved. See LICENSE.txt for license information. + +NOTICES: +-------- + +This document includes a list of open source components used in Mattermost Server, including those that have been modified. + +--- + +## NYTimes/gziphandler + +This product contains 'gziphandler' by The New York Times. + +Golang middleware to gzip HTTP responses + +* HOMEPAGE: + * https://github.com/NYTimes/gziphandler + +* LICENSE: Apache-2.0 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2017 The New York Times Company + + 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. + +--- + +## avct/uasurfer + +This product contains 'uasurfer' by Avocet. + +Go package for fast and reliable abstraction of browser user agent strings. + +* HOMEPAGE: + * https://github.com/avct/uasurfer + +* LICENSE: Apache-2.0 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + Copyright 2015 Avocet Systems Ltd. + http://avocet.io/opensource + + 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. + + Copyright 2015 Avocet Systems Ltd. + + 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. + +--- + +## dgryski/dgoogauth + +This product contains 'dgoogauth' by Damian Gryski. + +Google Authenticator for Go + +* HOMEPAGE: + * https://github.com/dgryski/dgoogauth + +* LICENSE: Apache-2.0 + +Note: An original license file for this dependency is not available. We determined the type of license based on the official project website. The following text has been prepared using a template from the SPDX Workgroup (https://spdx.org) for this type of license. + +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 2018 Damian Gryski + +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. + +--- + +## disintegration/imaging + +This product contains 'imaging' by Grigory Dryapak. + +Imaging is a simple image processing package for Go + +* HOMEPAGE: + * https://github.com/disintegration/imaging + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2012-2018 Grigory Dryapak + +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. + +--- + +## dyatlov/go-opengraph + +This product contains 'go-opengraph' by Vitaly Dyatlov. + +Golang package for parsing OpenGraph data from HTML into regular structures + +* HOMEPAGE: + * https://github.com/dyatlov/go-opengraph + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2015 Vitaly Dyatlov + +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. + +--- + +## fsnotify/fsnotify + +This product contains 'fsnotify' by GitHub user "fsnotify". + +Cross-platform file system notifications for Go. + +* HOMEPAGE: + * https://github.com/fsnotify/fsnotify + +* LICENSE: BSD-3-Clause + +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2012 fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## go-ldap/ldap + +This product contains 'ldap' by GitHub user "go-ldap". + +Basic LDAP v3 functionality for the GO programming language. + +* HOMEPAGE: + * https://github.com/go-ldap/ldap + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) +Portions copyright (c) 2015-2016 go-ldap Authors + +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. + +--- + +## go-redis/redis + +This product contains 'redis' by GitHub user "go-redis". + +Type-safe Redis client for Golang + +* HOMEPAGE: + * https://github.com/go-redis/redis + +* LICENSE: BSD-2-Clause + +Copyright (c) 2013 The github.com/go-redis/redis 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 +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. + +--- + +## go-sql-driver/mysql + +This product contains 'mysql' by Go SQL Drivers. + +Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package + +* HOMEPAGE: + * https://github.com/go-sql-driver/mysql + +* LICENSE: MPL-2.0 + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + +--- + +## golang/freetype + +This product contains 'freetype' by Go. + +The Freetype font rasterizer in the Go programming language. + +* HOMEPAGE: + * https://github.com/golang/freetype + +* LICENSE: (FTL OR GPL-2.0) + +Use of the Freetype-Go software is subject to your choice of exactly one of +the following two licenses: + * The FreeType License, which is similar to the original BSD license with + an advertising clause, or + * The GNU General Public License (GPL), version 2 or later. + +The text of these licenses are available in the licenses/ftl.txt and the +licenses/gpl.txt files respectively. They are also available at +http://freetype.sourceforge.net/license.html + +The Luxi fonts in the testdata directory are licensed separately. See the +testdata/COPYING file for details. + +--- + +## gorilla/handlers + +This product contains 'handlers' by Gorilla web toolkit. + +A collection of useful handlers for Go's net/http package. + +* HOMEPAGE: + * https://github.com/gorilla/handlers + +* LICENSE: BSD-2-Clause + +Copyright (c) 2013 The Gorilla Handlers 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. + +--- + +## gorilla/mux + +This product contains 'mux' by Gorilla web toolkit. + +A powerful URL router and dispatcher for golang. + +* HOMEPAGE: + * https://github.com/gorilla/mux + +* LICENSE: BSD-3-Clause + +Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## gorilla/schema + +This product contains 'schema' by Gorilla web toolkit. + +Package gorilla/schema fills a struct with form values. + +* HOMEPAGE: + * https://github.com/gorilla/schema + +* LICENSE: BSD-3-Clause + +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. + +--- + +## gorilla/websocket + +This product contains 'websocket' by Gorilla web toolkit. + +A WebSocket implementation for Go. + +* HOMEPAGE: + * https://github.com/gorilla/websocket + +* LICENSE: BSD-2-Clause + +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. + +--- + +## hako/durafmt + +This product contains 'durafmt' by Wesley Hill. + +:clock8: Better time duration formatting in Go! + +* HOMEPAGE: + * https://github.com/hako/durafmt + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2016 Wesley Hill + +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. + +--- + +## hashicorp/go-hclog + +This product contains 'go-hclog' by HashiCorp. + +A common logging package for HashiCorp tools + +* HOMEPAGE: + * https://github.com/hashicorp/go-hclog + +* LICENSE: MIT + +MIT License + +Copyright (c) 2017 HashiCorp + +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. + +--- + +## hashicorp/go-plugin + +This product contains 'go-plugin' by HashiCorp. + +Golang plugin system over RPC. + +* HOMEPAGE: + * https://github.com/hashicorp/go-plugin + +* LICENSE: MPL-2.0 + +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + +--- + +## hashicorp/memberlist + +This product contains 'memberlist' by HashiCorp. + +Golang package for gossip based membership and failure detection + +* HOMEPAGE: + * https://github.com/hashicorp/memberlist + +* LICENSE: MPL-2.0 + +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + +--- + +## icrowley/fake + +This product contains 'fake' by GitHub user "icrowley". + +Fake data generator for Go (Golang) + +* HOMEPAGE: + * https://github.com/icrowley/fake + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2014 Dmitry Afanasyev + +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. + +--- + +## jaytaylor/html2text + +This product contains 'html2text' by J. Elliot Taylor. + +Golang HTML to plaintext conversion library + +* HOMEPAGE: + * https://github.com/jaytaylor/html2text + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2015 Jay Taylor + +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. + +--- + +## lib/pq + +This product contains 'pq' by GitHub user "lib". + +Pure Go Postgres driver for database/sql + +* HOMEPAGE: + * https://github.com/lib/pq + +* LICENSE: MIT + +Copyright (c) 2011-2013, 'pq' Contributors +Portions Copyright (C) 2011 Blake Mizerany + +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. + +--- + +## mattermost/gorp + +This product contains 'gorp' by Mattermost (forked from original GitHub repo 'go-gorp/gorp' owned by GitHub user "go-gorp"). + +Go Relational Persistence - an ORM-ish library for Go + +* HOMEPAGE: + * https://github.com/mattermost/gorp + +* LICENSE: MIT + +(The MIT License) + +Copyright (c) 2012 James Cooper + +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. + +--- + +## mattermost/rsc + +This product contains 'rsc' by Mattermost (forked from original GitHub repo 'petar/rsc' owned by Petar Maymounkov). + +fork of Russ Cox's code.google.com/p/rsc + +* HOMEPAGE: + * https://github.com/mattermost/rsc + +* LICENSE: BSD-3-Clause + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## mattermost/viper + +This product contains 'viper' by Mattermost (forked from original GitHub repo 'spf13/viper' owned by Steve Francia). + +Go configuration with fangs + +* HOMEPAGE: + * https://github.com/mattermost/viper + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2014 Steve Francia + +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. + +--- + +## minio/minio-go + +This product contains 'minio-go' by Minio Cloud Storage. + +Minio Client SDK for Go + +* HOMEPAGE: + * https://github.com/minio/minio-go + +* LICENSE: Apache-2.0 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +* This package includes the following NOTICE: + +minio-go +Copyright 2015-2017 Minio, Inc. + +--- + +## nicksnyder/go-i18n + +This product contains 'go-i18n' by Nick Snyder. + +Translate your Go program into multiple languages. + +* HOMEPAGE: + * https://github.com/nicksnyder/go-i18n + +* LICENSE: MIT + +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. + +--- + +## pborman/uuid + +This product contains 'uuid' by GitHub user "pborman". + +Automatically exported from code.google.com/p/go-uuid + +* HOMEPAGE: + * https://github.com/pborman/uuid + +* LICENSE: BSD-3-Clause + +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. + +--- + +## pkg/errors + +This product contains 'errors' by GitHub user "pkg". + +Simple error handling primitives + +* HOMEPAGE: + * https://github.com/pkg/errors + +* LICENSE: BSD-2-Clause + +Copyright (c) 2015, Dave Cheney +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. + +--- + +## prometheus/client_golang + +This product contains 'client_golang' by Prometheus. + +Prometheus instrumentation library for Go applications + +* HOMEPAGE: + * https://github.com/prometheus/client_golang + +* LICENSE: Apache-2.0 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +* This package includes the following NOTICE: + +Prometheus instrumentation library for Go applications +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). + + +The following components are included in this product: + +perks - a fork of https://github.com/bmizerany/perks +https://github.com/beorn7/perks +Copyright 2013-2015 Blake Mizerany, Björn Rabenstein +See https://github.com/beorn7/perks/blob/master/README.md for license details. + +Go support for Protocol Buffers - Google's data interchange format +http://github.com/golang/protobuf/ +Copyright 2010 The Go Authors +See source code for license details. + +Support for streaming Protocol Buffer messages for the Go language (golang). +https://github.com/matttproud/golang_protobuf_extensions +Copyright 2013 Matt T. Proud +Licensed under the Apache License, Version 2.0 + +--- + +## rs/cors + +This product contains 'cors' by Olivier Poitrey. + +Go net/http configurable handler to handle CORS requests + +* HOMEPAGE: + * https://github.com/rs/cors + +* LICENSE: MIT + +Copyright (c) 2014 Olivier Poitrey + +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. + +--- + +## rwcarlsen/goexif + +This product contains 'goexif' by Robert Carlsen. + +Decode embedded EXIF meta data from image files. + +* HOMEPAGE: + * https://github.com/rwcarlsen/goexif + +* LICENSE: BSD-2-Clause + + +Copyright (c) 2012, Robert Carlsen & Contributors +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. + +--- + +## segmentio/analytics-go + +This product contains 'analytics-go' by Segment. + +Segment analytics client for Go + +* HOMEPAGE: + * https://github.com/segmentio/analytics-go + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2016 Segment, Inc. + +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. + +--- + +## spf13/cobra + +This product contains 'cobra' by Steve Francia. + +A Commander for modern Go CLI interactions + +* HOMEPAGE: + * https://github.com/spf13/cobra + +* LICENSE: Apache-2.0 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +--- + +## stretchr/testify + +This product contains 'testify' by Stretchr, Inc.. + +A toolkit with common assertions and mocks that plays nicely with the standard library + +* HOMEPAGE: + * https://github.com/stretchr/testify + +* LICENSE: MIT + +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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. + +--- + +## throttled/throttled + +This product contains 'throttled' by GitHub user "throttled". + +Package throttled implements rate limiting access to resources such as HTTP endpoints. + +* HOMEPAGE: + * https://github.com/throttled/throttled + +* LICENSE: BSD-3-Clause + +Copyright (c) 2018, Martin Angers and Contributors. +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 the author 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 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. + +--- + +## tylerb/graceful + +This product contains 'graceful' by Tyler Stillwater. + +Graceful is a Go package enabling graceful shutdown of an http.Handler server. + +* HOMEPAGE: + * https://github.com/tylerb/graceful + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2014 Tyler Bunnell + +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. + +--- + +## zap + +This product contains 'zap' by Uber Technologies, Inc.. + +Blazing fast, structured, leveled logging in Go. + +* HOMEPAGE: + * https://github.com/uber-go/zap + +* LICENSE: MIT + +Copyright (c) 2016-2017 Uber Technologies, Inc. + +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. + +--- + +## x/crypto + +This product contains 'crypto' by The Go Authors. + +[mirror] Go supplementary cryptography libraries + +* HOMEPAGE: + * https://github.com/golang/crypto + +* LICENSE: BSD-3-Clause + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## x/image + +This product contains 'image' by The Go Authors. + +[mirror] Go supplementary image libraries + +* HOMEPAGE: + * https://github.com/golang/image + +* LICENSE: BSD-3-Clause + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## x/net + +This product contains 'net' by The Go Authors. + +[mirror] Go supplementary network libraries + +* HOMEPAGE: + * https://github.com/golang/net + +* LICENSE: BSD-3-Clause + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +## go-gomail/gomail + +This product contains 'gomail' by Gomail. + +The best way to send emails in Go. + +* HOMEPAGE: + * https://github.com/go-gomail/gomail + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2014 Alexandre Cesaro + +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. + +--- + +## natefinch/lumberjack + +This product contains 'lumberjack' by Nate Finch. + +lumberjack is a log rolling package for Go + +* HOMEPAGE: + * https://github.com/natefinch/lumberjack + +* LICENSE: MIT + +The MIT License (MIT) + +Copyright (c) 2014 Nate Finch + +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. + +--- + +## olivere/elastic + +This product contains 'elastic' by Oliver Eilhard. + +Elasticsearch client for Go. + +* HOMEPAGE: + * https://github.com/olivere/elastic + +* LICENSE: MIT + +The MIT License (MIT) +Copyright © 2012-2015 Oliver Eilhard + +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. + +--- + +## go-yaml/yaml + +This product contains 'yaml' by GitHub user "go-yaml". + +YAML support for the Go language. + +* HOMEPAGE: + * https://github.com/go-yaml/yaml + +* LICENSE: Apache-2.0 + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +* This package includes the following NOTICE: + +Copyright 2011-2016 Canonical Ltd. + +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/mattermost/mattermost-server/mlog/default.go b/vendor/github.com/mattermost/mattermost-server/mlog/default.go new file mode 100644 index 00000000..366d22f8 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/mlog/default.go @@ -0,0 +1,50 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mlog + +import ( + "encoding/json" + "fmt" +) + +// defaultLog manually encodes the log to STDOUT, providing a basic, default logging implementation +// before mlog is fully configured. +func defaultLog(level, msg string, fields ...Field) { + log := struct { + Level string `json:"level"` + Message string `json:"msg"` + Fields []Field `json:"fields,omitempty"` + }{ + level, + msg, + fields, + } + + if b, err := json.Marshal(log); err != nil { + fmt.Printf(`{"level":"error","msg":"failed to encode log message"}%s`, "\n") + } else { + fmt.Printf("%s\n", b) + } +} + +func defaultDebugLog(msg string, fields ...Field) { + defaultLog("debug", msg, fields...) +} + +func defaultInfoLog(msg string, fields ...Field) { + defaultLog("info", msg, fields...) +} + +func defaultWarnLog(msg string, fields ...Field) { + defaultLog("warn", msg, fields...) +} + +func defaultErrorLog(msg string, fields ...Field) { + defaultLog("error", msg, fields...) +} + +func defaultCriticalLog(msg string, fields ...Field) { + // We map critical to error in zap, so be consistent. + defaultLog("error", msg, fields...) +} diff --git a/vendor/github.com/mattermost/mattermost-server/mlog/global.go b/vendor/github.com/mattermost/mattermost-server/mlog/global.go new file mode 100644 index 00000000..ba90ace2 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/mlog/global.go @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mlog + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var globalLogger *Logger + +func InitGlobalLogger(logger *Logger) { + glob := *logger + glob.zap = glob.zap.WithOptions(zap.AddCallerSkip(1)) + globalLogger = &glob + Debug = globalLogger.Debug + Info = globalLogger.Info + Warn = globalLogger.Warn + Error = globalLogger.Error + Critical = globalLogger.Critical +} + +func RedirectStdLog(logger *Logger) { + zap.RedirectStdLogAt(logger.zap.With(zap.String("source", "stdlog")).WithOptions(zap.AddCallerSkip(-2)), zapcore.ErrorLevel) +} + +type LogFunc func(string, ...Field) + +// DON'T USE THIS Modify the level on the app logger +func GloballyDisableDebugLogForTest() { + globalLogger.consoleLevel.SetLevel(zapcore.ErrorLevel) +} + +// DON'T USE THIS Modify the level on the app logger +func GloballyEnableDebugLogForTest() { + globalLogger.consoleLevel.SetLevel(zapcore.DebugLevel) +} + +var Debug LogFunc = defaultDebugLog +var Info LogFunc = defaultInfoLog +var Warn LogFunc = defaultWarnLog +var Error LogFunc = defaultErrorLog +var Critical LogFunc = defaultCriticalLog diff --git a/vendor/github.com/mattermost/mattermost-server/mlog/log.go b/vendor/github.com/mattermost/mattermost-server/mlog/log.go new file mode 100644 index 00000000..e3bc38d8 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/mlog/log.go @@ -0,0 +1,173 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mlog + +import ( + "io" + "log" + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +const ( + // Very verbose messages for debugging specific issues + LevelDebug = "debug" + // Default log level, informational + LevelInfo = "info" + // Warnings are messages about possible issues + LevelWarn = "warn" + // Errors are messages about things we know are problems + LevelError = "error" +) + +// Type and function aliases from zap to limit the libraries scope into MM code +type Field = zapcore.Field + +var Int64 = zap.Int64 +var Int = zap.Int +var Uint32 = zap.Uint32 +var String = zap.String +var Any = zap.Any +var Err = zap.Error +var Bool = zap.Bool + +type LoggerConfiguration struct { + EnableConsole bool + ConsoleJson bool + ConsoleLevel string + EnableFile bool + FileJson bool + FileLevel string + FileLocation string +} + +type Logger struct { + zap *zap.Logger + consoleLevel zap.AtomicLevel + fileLevel zap.AtomicLevel +} + +func getZapLevel(level string) zapcore.Level { + switch level { + case LevelInfo: + return zapcore.InfoLevel + case LevelWarn: + return zapcore.WarnLevel + case LevelDebug: + return zapcore.DebugLevel + case LevelError: + return zapcore.ErrorLevel + default: + return zapcore.InfoLevel + } +} + +func makeEncoder(json bool) zapcore.Encoder { + encoderConfig := zap.NewProductionEncoderConfig() + if json { + return zapcore.NewJSONEncoder(encoderConfig) + } + + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + return zapcore.NewConsoleEncoder(encoderConfig) +} + +func NewLogger(config *LoggerConfiguration) *Logger { + cores := []zapcore.Core{} + logger := &Logger{ + consoleLevel: zap.NewAtomicLevelAt(getZapLevel(config.ConsoleLevel)), + fileLevel: zap.NewAtomicLevelAt(getZapLevel(config.FileLevel)), + } + + if config.EnableConsole { + writer := zapcore.Lock(os.Stdout) + core := zapcore.NewCore(makeEncoder(config.ConsoleJson), writer, logger.consoleLevel) + cores = append(cores, core) + } + + if config.EnableFile { + writer := zapcore.AddSync(&lumberjack.Logger{ + Filename: config.FileLocation, + MaxSize: 100, + Compress: true, + }) + core := zapcore.NewCore(makeEncoder(config.FileJson), writer, logger.fileLevel) + cores = append(cores, core) + } + + combinedCore := zapcore.NewTee(cores...) + + logger.zap = zap.New(combinedCore, + zap.AddCallerSkip(1), + zap.AddCaller(), + ) + + return logger +} + +func (l *Logger) ChangeLevels(config *LoggerConfiguration) { + l.consoleLevel.SetLevel(getZapLevel(config.ConsoleLevel)) + l.fileLevel.SetLevel(getZapLevel(config.FileLevel)) +} + +func (l *Logger) SetConsoleLevel(level string) { + l.consoleLevel.SetLevel(getZapLevel(level)) +} + +func (l *Logger) With(fields ...Field) *Logger { + newlogger := *l + newlogger.zap = newlogger.zap.With(fields...) + return &newlogger +} + +func (l *Logger) StdLog(fields ...Field) *log.Logger { + return zap.NewStdLog(l.With(fields...).zap.WithOptions(getStdLogOption())) +} + +// StdLogWriter returns a writer that can be hooked up to the output of a golang standard logger +// anything written will be interpreted as log entries accordingly +func (l *Logger) StdLogWriter() io.Writer { + newLogger := *l + newLogger.zap = newLogger.zap.WithOptions(zap.AddCallerSkip(4), getStdLogOption()) + f := newLogger.Info + return &loggerWriter{f} +} + +func (l *Logger) WithCallerSkip(skip int) *Logger { + newlogger := *l + newlogger.zap = newlogger.zap.WithOptions(zap.AddCallerSkip(skip)) + return &newlogger +} + +// Made for the plugin interface, wraps mlog in a simpler interface +// at the cost of performance +func (l *Logger) Sugar() *SugarLogger { + return &SugarLogger{ + wrappedLogger: l, + zapSugar: l.zap.Sugar(), + } +} + +func (l *Logger) Debug(message string, fields ...Field) { + l.zap.Debug(message, fields...) +} + +func (l *Logger) Info(message string, fields ...Field) { + l.zap.Info(message, fields...) +} + +func (l *Logger) Warn(message string, fields ...Field) { + l.zap.Warn(message, fields...) +} + +func (l *Logger) Error(message string, fields ...Field) { + l.zap.Error(message, fields...) +} + +func (l *Logger) Critical(message string, fields ...Field) { + l.zap.Error(message, fields...) +} diff --git a/vendor/github.com/mattermost/mattermost-server/mlog/stdlog.go b/vendor/github.com/mattermost/mattermost-server/mlog/stdlog.go new file mode 100644 index 00000000..7839ddfa --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/mlog/stdlog.go @@ -0,0 +1,87 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package mlog + +import ( + "bytes" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Implementation of zapcore.Core to interpret log messages from a standard logger +// and translate the levels to zapcore levels. +type stdLogLevelInterpreterCore struct { + wrappedCore zapcore.Core +} + +func stdLogInterpretZapEntry(entry zapcore.Entry) zapcore.Entry { + message := entry.Message + if strings.Index(message, "[DEBUG]") == 0 { + entry.Level = zapcore.DebugLevel + entry.Message = message[7:] + } else if strings.Index(message, "[DEBG]") == 0 { + entry.Level = zapcore.DebugLevel + entry.Message = message[6:] + } else if strings.Index(message, "[WARN]") == 0 { + entry.Level = zapcore.WarnLevel + entry.Message = message[6:] + } else if strings.Index(message, "[ERROR]") == 0 { + entry.Level = zapcore.ErrorLevel + entry.Message = message[7:] + } else if strings.Index(message, "[EROR]") == 0 { + entry.Level = zapcore.ErrorLevel + entry.Message = message[6:] + } else if strings.Index(message, "[ERR]") == 0 { + entry.Level = zapcore.ErrorLevel + entry.Message = message[5:] + } else if strings.Index(message, "[INFO]") == 0 { + entry.Level = zapcore.InfoLevel + entry.Message = message[6:] + } + return entry +} + +func (s *stdLogLevelInterpreterCore) Enabled(lvl zapcore.Level) bool { + return s.wrappedCore.Enabled(lvl) +} + +func (s *stdLogLevelInterpreterCore) With(fields []zapcore.Field) zapcore.Core { + return s.wrappedCore.With(fields) +} + +func (s *stdLogLevelInterpreterCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry { + entry = stdLogInterpretZapEntry(entry) + return s.wrappedCore.Check(entry, checkedEntry) +} + +func (s *stdLogLevelInterpreterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { + entry = stdLogInterpretZapEntry(entry) + return s.wrappedCore.Write(entry, fields) +} + +func (s *stdLogLevelInterpreterCore) Sync() error { + return s.wrappedCore.Sync() +} + +func getStdLogOption() zap.Option { + return zap.WrapCore( + func(core zapcore.Core) zapcore.Core { + return &stdLogLevelInterpreterCore{core} + }, + ) +} + +type loggerWriter struct { + logFunc func(msg string, fields ...Field) +} + +func (l *loggerWriter) Write(p []byte) (int, error) { + trimmed := string(bytes.TrimSpace(p)) + for _, line := range strings.Split(trimmed, "\n") { + l.logFunc(string(line)) + } + return len(p), nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/mlog/sugar.go b/vendor/github.com/mattermost/mattermost-server/mlog/sugar.go new file mode 100644 index 00000000..c00a8bbf --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/mlog/sugar.go @@ -0,0 +1,28 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package mlog + +import "go.uber.org/zap" + +// Made for the plugin interface, use the regular logger for other uses +type SugarLogger struct { + wrappedLogger *Logger + zapSugar *zap.SugaredLogger +} + +func (l *SugarLogger) Debug(msg string, keyValuePairs ...interface{}) { + l.zapSugar.Debugw(msg, keyValuePairs...) +} + +func (l *SugarLogger) Info(msg string, keyValuePairs ...interface{}) { + l.zapSugar.Infow(msg, keyValuePairs...) +} + +func (l *SugarLogger) Error(msg string, keyValuePairs ...interface{}) { + l.zapSugar.Errorw(msg, keyValuePairs...) +} + +func (l *SugarLogger) Warn(msg string, keyValuePairs ...interface{}) { + l.zapSugar.Warnw(msg, keyValuePairs...) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/access.go b/vendor/github.com/mattermost/mattermost-server/model/access.go new file mode 100644 index 00000000..e9603c78 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/access.go @@ -0,0 +1,96 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" +) + +const ( + ACCESS_TOKEN_GRANT_TYPE = "authorization_code" + ACCESS_TOKEN_TYPE = "bearer" + REFRESH_TOKEN_GRANT_TYPE = "refresh_token" +) + +type AccessData struct { + ClientId string `json:"client_id"` + UserId string `json:"user_id"` + Token string `json:"token"` + RefreshToken string `json:"refresh_token"` + RedirectUri string `json:"redirect_uri"` + ExpiresAt int64 `json:"expires_at"` + Scope string `json:"scope"` +} + +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.ClientId) == 0 || len(ad.ClientId) > 26 { + return NewAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(ad.UserId) == 0 || len(ad.UserId) > 26 { + return NewAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(ad.Token) != 26 { + return NewAppError("AccessData.IsValid", "model.access.is_valid.access_token.app_error", nil, "", http.StatusBadRequest) + } + + if len(ad.RefreshToken) > 26 { + return NewAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "", http.StatusBadRequest) + } + + if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) { + return NewAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (me *AccessData) IsExpired() bool { + + if me.ExpiresAt <= 0 { + return false + } + + if GetMillis() > me.ExpiresAt { + return true + } + + return false +} + +func (ad *AccessData) ToJson() string { + b, _ := json.Marshal(ad) + return string(b) +} + +func AccessDataFromJson(data io.Reader) *AccessData { + var ad *AccessData + json.NewDecoder(data).Decode(&ad) + return ad +} + +func (ar *AccessResponse) ToJson() string { + b, _ := json.Marshal(ar) + return string(b) +} + +func AccessResponseFromJson(data io.Reader) *AccessResponse { + var ar *AccessResponse + json.NewDecoder(data).Decode(&ar) + return ar +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/analytics_row.go b/vendor/github.com/mattermost/mattermost-server/model/analytics_row.go new file mode 100644 index 00000000..4615bb79 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/analytics_row.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(me) + return string(b) +} + +func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow { + var me *AnalyticsRow + json.NewDecoder(data).Decode(&me) + return me +} + +func (me AnalyticsRows) ToJson() string { + if b, err := json.Marshal(me); err != nil { + return "[]" + } else { + return string(b) + } +} + +func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows { + var me AnalyticsRows + json.NewDecoder(data).Decode(&me) + return me +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/audit.go b/vendor/github.com/mattermost/mattermost-server/model/audit.go new file mode 100644 index 00000000..e3d1bdf9 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/audit.go @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(o) + return string(b) +} + +func AuditFromJson(data io.Reader) *Audit { + var o *Audit + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/audits.go b/vendor/github.com/mattermost/mattermost-server/model/audits.go new file mode 100644 index 00000000..3673eb61 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/audits.go @@ -0,0 +1,34 @@ +// Copyright (c) 2015-present 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 { + var o Audits + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/authorize.go b/vendor/github.com/mattermost/mattermost-server/model/authorize.go new file mode 100644 index 00000000..922adc07 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/authorize.go @@ -0,0 +1,142 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" +) + +const ( + AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes + AUTHCODE_RESPONSE_TYPE = "code" + IMPLICIT_RESPONSE_TYPE = "token" + DEFAULT_SCOPE = "user" +) + +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"` +} + +type AuthorizeRequest struct { + ResponseType string `json:"response_type"` + ClientId string `json:"client_id"` + RedirectUri string `json:"redirect_uri"` + Scope string `json:"scope"` + State string `json:"state"` +} + +// 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 NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(ad.UserId) != 26 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(ad.Code) == 0 || len(ad.Code) > 128 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.auth_code.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) + } + + if ad.ExpiresIn == 0 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.expires.app_error", nil, "", http.StatusBadRequest) + } + + if ad.CreateAt <= 0 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) + } + + if len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) + } + + if len(ad.State) > 1024 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) + } + + if len(ad.Scope) > 128 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) + } + + return nil +} + +// IsValid validates the AuthorizeRequest and returns an error if it isn't configured +// correctly. +func (ar *AuthorizeRequest) IsValid() *AppError { + + if len(ar.ClientId) != 26 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(ar.ResponseType) == 0 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest) + } + + if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest) + } + + if len(ar.State) > 1024 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest) + } + + if len(ar.Scope) > 128 { + return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest) + } + + return nil +} + +func (ad *AuthData) PreSave() { + if ad.ExpiresIn == 0 { + ad.ExpiresIn = AUTHCODE_EXPIRE_TIME + } + + if ad.CreateAt == 0 { + ad.CreateAt = GetMillis() + } + + if len(ad.Scope) == 0 { + ad.Scope = DEFAULT_SCOPE + } +} + +func (ad *AuthData) ToJson() string { + b, _ := json.Marshal(ad) + return string(b) +} + +func AuthDataFromJson(data io.Reader) *AuthData { + var ad *AuthData + json.NewDecoder(data).Decode(&ad) + return ad +} + +func (ar *AuthorizeRequest) ToJson() string { + b, _ := json.Marshal(ar) + return string(b) +} + +func AuthorizeRequestFromJson(data io.Reader) *AuthorizeRequest { + var ar *AuthorizeRequest + json.NewDecoder(data).Decode(&ar) + return ar +} + +func (ad *AuthData) IsExpired() bool { + return GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/builtin.go b/vendor/github.com/mattermost/mattermost-server/model/builtin.go new file mode 100644 index 00000000..5dd00a96 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/builtin.go @@ -0,0 +1,9 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +func NewBool(b bool) *bool { return &b } +func NewInt(n int) *int { return &n } +func NewInt64(n int64) *int64 { return &n } +func NewString(s string) *string { return &s } diff --git a/vendor/github.com/mattermost/mattermost-server/model/bundle_info.go b/vendor/github.com/mattermost/mattermost-server/model/bundle_info.go new file mode 100644 index 00000000..7cb06701 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/bundle_info.go @@ -0,0 +1,32 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import "github.com/mattermost/mattermost-server/mlog" + +type BundleInfo struct { + Path string + + Manifest *Manifest + ManifestPath string + ManifestError error +} + +func (b *BundleInfo) WrapLogger(logger *mlog.Logger) *mlog.Logger { + if b.Manifest != nil { + return logger.With(mlog.String("plugin_id", b.Manifest.Id)) + } + return logger.With(mlog.String("plugin_path", b.Path)) +} + +// Returns bundle info for the given path. The return value is never nil. +func BundleInfoForPath(path string) *BundleInfo { + m, mpath, err := FindManifest(path) + return &BundleInfo{ + Path: path, + Manifest: m, + ManifestPath: mpath, + ManifestError: err, + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel.go b/vendor/github.com/mattermost/mattermost-server/model/channel.go new file mode 100644 index 00000000..529c49d3 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel.go @@ -0,0 +1,222 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "crypto/sha1" + "encoding/hex" + "encoding/json" + "io" + "net/http" + "sort" + "strings" + "unicode/utf8" +) + +const ( + CHANNEL_OPEN = "O" + CHANNEL_PRIVATE = "P" + CHANNEL_DIRECT = "D" + CHANNEL_GROUP = "G" + CHANNEL_GROUP_MAX_USERS = 8 + CHANNEL_GROUP_MIN_USERS = 3 + DEFAULT_CHANNEL = "town-square" + CHANNEL_DISPLAY_NAME_MAX_RUNES = 64 + CHANNEL_NAME_MIN_LENGTH = 2 + CHANNEL_NAME_MAX_LENGTH = 64 + CHANNEL_NAME_UI_MAX_LENGTH = 22 + CHANNEL_HEADER_MAX_RUNES = 1024 + CHANNEL_PURPOSE_MAX_RUNES = 250 + CHANNEL_CACHE_SIZE = 25000 +) + +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"` + SchemeId *string `json:"scheme_id"` + Props map[string]interface{} `json:"props" db:"-"` +} + +type ChannelPatch struct { + DisplayName *string `json:"display_name"` + Name *string `json:"name"` + Header *string `json:"header"` + Purpose *string `json:"purpose"` +} + +type ChannelForExport struct { + Channel + TeamName string + SchemeName *string +} + +func (o *Channel) DeepCopy() *Channel { + copy := *o + if copy.SchemeId != nil { + copy.SchemeId = NewString(*o.SchemeId) + } + return © +} + +func (o *Channel) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func (o *ChannelPatch) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ChannelFromJson(data io.Reader) *Channel { + var o *Channel + json.NewDecoder(data).Decode(&o) + return o +} + +func ChannelPatchFromJson(data io.Reader) *ChannelPatch { + var o *ChannelPatch + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *Channel) Etag() string { + return Etag(o.Id, o.UpdateAt) +} + +func (o *Channel) IsValid() *AppError { + if len(o.Id) != 26 { + return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.DisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES { + return NewAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if !IsValidChannelIdentifier(o.Name) { + return NewAppError("Channel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP) { + return NewAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.Header) > CHANNEL_HEADER_MAX_RUNES { + return NewAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.Purpose) > CHANNEL_PURPOSE_MAX_RUNES { + return NewAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.CreatorId) > 26 { + return NewAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (o *Channel) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + o.CreateAt = GetMillis() + o.UpdateAt = o.CreateAt + o.ExtraUpdateAt = 0 +} + +func (o *Channel) PreUpdate() { + o.UpdateAt = GetMillis() +} + +func (o *Channel) IsGroupOrDirect() bool { + return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP +} + +func (o *Channel) Patch(patch *ChannelPatch) { + if patch.DisplayName != nil { + o.DisplayName = *patch.DisplayName + } + + if patch.Name != nil { + o.Name = *patch.Name + } + + if patch.Header != nil { + o.Header = *patch.Header + } + + if patch.Purpose != nil { + o.Purpose = *patch.Purpose + } +} + +func (o *Channel) MakeNonNil() { + if o.Props == nil { + o.Props = make(map[string]interface{}) + } +} + +func (o *Channel) AddProp(key string, value interface{}) { + o.MakeNonNil() + + o.Props[key] = value +} + +func GetDMNameFromIds(userId1, userId2 string) string { + if userId1 > userId2 { + return userId2 + "__" + userId1 + } else { + return userId1 + "__" + userId2 + } +} + +func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string { + usernames := make([]string, len(users)) + for index, user := range users { + usernames[index] = user.Username + } + + sort.Strings(usernames) + + name := strings.Join(usernames, ", ") + + if truncate && len(name) > CHANNEL_NAME_MAX_LENGTH { + name = name[:CHANNEL_NAME_MAX_LENGTH] + } + + return name +} + +func GetGroupNameFromUserIds(userIds []string) string { + sort.Strings(userIds) + + h := sha1.New() + for _, id := range userIds { + io.WriteString(h, id) + } + + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_count.go b/vendor/github.com/mattermost/mattermost-server/model/channel_count.go new file mode 100644 index 00000000..8c6d8dd0 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_count.go @@ -0,0 +1,54 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(o) + return string(b) +} + +func ChannelCountsFromJson(data io.Reader) *ChannelCounts { + var o *ChannelCounts + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_data.go b/vendor/github.com/mattermost/mattermost-server/model/channel_data.go new file mode 100644 index 00000000..aae0a149 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_data.go @@ -0,0 +1,34 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(o) + return string(b) +} + +func ChannelDataFromJson(data io.Reader) *ChannelData { + var o *ChannelData + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_list.go b/vendor/github.com/mattermost/mattermost-server/model/channel_list.go new file mode 100644 index 00000000..1b3bda46 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_list.go @@ -0,0 +1,53 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ChannelList []*Channel + +func (o *ChannelList) ToJson() string { + if b, err := json.Marshal(o); 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 { + if v.LastPostAt > t { + t = v.LastPostAt + id = v.Id + } + + if v.UpdateAt > t { + t = v.UpdateAt + id = v.Id + } + + } + + return Etag(id, t, delta, len(*o)) +} + +func ChannelListFromJson(data io.Reader) *ChannelList { + var o *ChannelList + json.NewDecoder(data).Decode(&o) + return o +} + +func ChannelSliceFromJson(data io.Reader) []*Channel { + var o []*Channel + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_member.go b/vendor/github.com/mattermost/mattermost-server/model/channel_member.go new file mode 100644 index 00000000..941db62f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_member.go @@ -0,0 +1,156 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "strings" +) + +const ( + 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 ChannelUnread struct { + TeamId string `json:"team_id"` + ChannelId string `json:"channel_id"` + MsgCount int64 `json:"msg_count"` + MentionCount int64 `json:"mention_count"` + NotifyProps StringMap `json:"-"` +} + +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"` + SchemeUser bool `json:"scheme_user"` + SchemeAdmin bool `json:"scheme_admin"` + ExplicitRoles string `json:"explicit_roles"` +} + +type ChannelMembers []ChannelMember + +type ChannelMemberForExport struct { + ChannelMember + ChannelName string +} + +func (o *ChannelMembers) ToJson() string { + if b, err := json.Marshal(o); err != nil { + return "[]" + } else { + return string(b) + } +} + +func (o *ChannelUnread) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ChannelMembersFromJson(data io.Reader) *ChannelMembers { + var o *ChannelMembers + json.NewDecoder(data).Decode(&o) + return o +} + +func ChannelUnreadFromJson(data io.Reader) *ChannelUnread { + var o *ChannelUnread + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *ChannelMember) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ChannelMemberFromJson(data io.Reader) *ChannelMember { + var o *ChannelMember + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *ChannelMember) IsValid() *AppError { + + if len(o.ChannelId) != 26 { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.UserId) != 26 { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + notifyLevel := o.NotifyProps[DESKTOP_NOTIFY_PROP] + if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error", nil, "notify_level="+notifyLevel, http.StatusBadRequest) + } + + markUnreadLevel := o.NotifyProps[MARK_UNREAD_NOTIFY_PROP] + if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error", nil, "mark_unread_level="+markUnreadLevel, http.StatusBadRequest) + } + + if pushLevel, ok := o.NotifyProps[PUSH_NOTIFY_PROP]; ok { + if len(pushLevel) > 20 || !IsChannelNotifyLevelValid(pushLevel) { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.push_level.app_error", nil, "push_notification_level="+pushLevel, http.StatusBadRequest) + } + } + + if sendEmail, ok := o.NotifyProps[EMAIL_NOTIFY_PROP]; ok { + if len(sendEmail) > 20 || !IsSendEmailValid(sendEmail) { + return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.email_value.app_error", nil, "push_notification_level="+sendEmail, http.StatusBadRequest) + } + } + + return nil +} + +func (o *ChannelMember) PreSave() { + o.LastUpdateAt = GetMillis() +} + +func (o *ChannelMember) PreUpdate() { + o.LastUpdateAt = GetMillis() +} + +func (o *ChannelMember) GetRoles() []string { + return strings.Fields(o.Roles) +} + +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 IsSendEmailValid(sendEmail string) bool { + return sendEmail == CHANNEL_NOTIFY_DEFAULT || sendEmail == "true" || sendEmail == "false" +} + +func GetDefaultChannelNotifyProps() StringMap { + return StringMap{ + DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL, + PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_member_history.go b/vendor/github.com/mattermost/mattermost-server/model/channel_member_history.go new file mode 100644 index 00000000..55435c32 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_member_history.go @@ -0,0 +1,11 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +type ChannelMemberHistory struct { + ChannelId string + UserId string + JoinTime int64 + LeaveTime *int64 +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_member_history_result.go b/vendor/github.com/mattermost/mattermost-server/model/channel_member_history_result.go new file mode 100644 index 00000000..ed3e7963 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_member_history_result.go @@ -0,0 +1,15 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +type ChannelMemberHistoryResult struct { + ChannelId string + UserId string + JoinTime int64 + LeaveTime *int64 + + // these two fields are never set in the database - when we SELECT, we join on Users to get them + UserEmail string `db:"Email"` + Username string +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_mentions.go b/vendor/github.com/mattermost/mattermost-server/model/channel_mentions.go new file mode 100644 index 00000000..795ec379 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_mentions.go @@ -0,0 +1,28 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "regexp" + "strings" +) + +var channelMentionRegexp = regexp.MustCompile(`\B~[a-zA-Z0-9\-_]+`) + +func ChannelMentions(message string) []string { + var names []string + + if strings.Contains(message, "~") { + alreadyMentioned := make(map[string]bool) + for _, match := range channelMentionRegexp.FindAllString(message, -1) { + name := match[1:] + if !alreadyMentioned[name] { + names = append(names, name) + alreadyMentioned[name] = true + } + } + } + + return names +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_search.go b/vendor/github.com/mattermost/mattermost-server/model/channel_search.go new file mode 100644 index 00000000..593cf669 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_search.go @@ -0,0 +1,26 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ChannelSearch struct { + Term string `json:"term"` +} + +// ToJson convert a Channel to a json string +func (c *ChannelSearch) ToJson() string { + b, _ := json.Marshal(c) + return string(b) +} + +// ChannelSearchFromJson will decode the input and return a Channel +func ChannelSearchFromJson(data io.Reader) *ChannelSearch { + var cs *ChannelSearch + json.NewDecoder(data).Decode(&cs) + return cs +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_stats.go b/vendor/github.com/mattermost/mattermost-server/model/channel_stats.go new file mode 100644 index 00000000..21af920f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_stats.go @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ChannelStats struct { + ChannelId string `json:"channel_id"` + MemberCount int64 `json:"member_count"` +} + +func (o *ChannelStats) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ChannelStatsFromJson(data io.Reader) *ChannelStats { + var o *ChannelStats + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/channel_view.go b/vendor/github.com/mattermost/mattermost-server/model/channel_view.go new file mode 100644 index 00000000..650d14ce --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/channel_view.go @@ -0,0 +1,41 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ChannelView struct { + ChannelId string `json:"channel_id"` + PrevChannelId string `json:"prev_channel_id"` +} + +func (o *ChannelView) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ChannelViewFromJson(data io.Reader) *ChannelView { + var o *ChannelView + json.NewDecoder(data).Decode(&o) + return o +} + +type ChannelViewResponse struct { + Status string `json:"status"` + LastViewedAtTimes map[string]int64 `json:"last_viewed_at_times"` +} + +func (o *ChannelViewResponse) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ChannelViewResponseFromJson(data io.Reader) *ChannelViewResponse { + var o *ChannelViewResponse + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/client4.go b/vendor/github.com/mattermost/mattermost-server/model/client4.go new file mode 100644 index 00000000..57c4fb7c --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/client4.go @@ -0,0 +1,3839 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +const ( + HEADER_REQUEST_ID = "X-Request-ID" + HEADER_VERSION_ID = "X-Version-ID" + HEADER_CLUSTER_ID = "X-Cluster-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" + STATUS = "status" + STATUS_OK = "OK" + STATUS_FAIL = "FAIL" + STATUS_REMOVE = "REMOVE" + + CLIENT_DIR = "client" + + API_URL_SUFFIX_V1 = "/api/v1" + API_URL_SUFFIX_V4 = "/api/v4" + API_URL_SUFFIX = API_URL_SUFFIX_V4 +) + +type Response struct { + StatusCode int + Error *AppError + RequestId string + Etag string + ServerVersion string + Header http.Header +} + +type Client4 struct { + Url string // The location of the server, for example "http://localhost:8065" + ApiUrl string // The api location of the server, for example "http://localhost:8065/api/v4" + HttpClient *http.Client // The http client + AuthToken string + AuthType string + HttpHeader map[string]string // Headers to be copied over for each request +} + +func closeBody(r *http.Response) { + if r.Body != nil { + ioutil.ReadAll(r.Body) + r.Body.Close() + } +} + +// Must is a convenience function used for testing. +func (c *Client4) Must(result interface{}, resp *Response) interface{} { + if resp.Error != nil { + + time.Sleep(time.Second) + panic(resp.Error) + } + + return result +} + +func NewAPIv4Client(url string) *Client4 { + return &Client4{url, url + API_URL_SUFFIX, &http.Client{}, "", "", map[string]string{}} +} + +func BuildErrorResponse(r *http.Response, err *AppError) *Response { + var statusCode int + var header http.Header + if r != nil { + statusCode = r.StatusCode + header = r.Header + } else { + statusCode = 0 + header = make(http.Header) + } + + return &Response{ + StatusCode: statusCode, + Error: err, + Header: header, + } +} + +func BuildResponse(r *http.Response) *Response { + return &Response{ + StatusCode: r.StatusCode, + RequestId: r.Header.Get(HEADER_REQUEST_ID), + Etag: r.Header.Get(HEADER_ETAG_SERVER), + ServerVersion: r.Header.Get(HEADER_VERSION_ID), + Header: r.Header, + } +} + +func (c *Client4) MockSession(sessionToken string) { + c.AuthToken = sessionToken + c.AuthType = HEADER_BEARER +} + +func (c *Client4) SetOAuthToken(token string) { + c.AuthToken = token + c.AuthType = HEADER_TOKEN +} + +func (c *Client4) ClearOAuthToken() { + c.AuthToken = "" + c.AuthType = HEADER_BEARER +} + +func (c *Client4) GetUsersRoute() string { + return fmt.Sprintf("/users") +} + +func (c *Client4) GetUserRoute(userId string) string { + return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId) +} + +func (c *Client4) GetUserAccessTokensRoute() string { + return fmt.Sprintf(c.GetUsersRoute() + "/tokens") +} + +func (c *Client4) GetUserAccessTokenRoute(tokenId string) string { + return fmt.Sprintf(c.GetUsersRoute()+"/tokens/%v", tokenId) +} + +func (c *Client4) GetUserByUsernameRoute(userName string) string { + return fmt.Sprintf(c.GetUsersRoute()+"/username/%v", userName) +} + +func (c *Client4) GetUserByEmailRoute(email string) string { + return fmt.Sprintf(c.GetUsersRoute()+"/email/%v", email) +} + +func (c *Client4) GetTeamsRoute() string { + return fmt.Sprintf("/teams") +} + +func (c *Client4) GetTeamRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamsRoute()+"/%v", teamId) +} + +func (c *Client4) GetTeamAutoCompleteCommandsRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamsRoute()+"/%v/commands/autocomplete", teamId) +} + +func (c *Client4) GetTeamByNameRoute(teamName string) string { + return fmt.Sprintf(c.GetTeamsRoute()+"/name/%v", teamName) +} + +func (c *Client4) GetTeamMemberRoute(teamId, userId string) string { + return fmt.Sprintf(c.GetTeamRoute(teamId)+"/members/%v", userId) +} + +func (c *Client4) GetTeamMembersRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamRoute(teamId) + "/members") +} + +func (c *Client4) GetTeamStatsRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamRoute(teamId) + "/stats") +} + +func (c *Client4) GetTeamImportRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamRoute(teamId) + "/import") +} + +func (c *Client4) GetChannelsRoute() string { + return fmt.Sprintf("/channels") +} + +func (c *Client4) GetChannelsForTeamRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamRoute(teamId) + "/channels") +} + +func (c *Client4) GetChannelRoute(channelId string) string { + return fmt.Sprintf(c.GetChannelsRoute()+"/%v", channelId) +} + +func (c *Client4) GetChannelByNameRoute(channelName, teamId string) string { + return fmt.Sprintf(c.GetTeamRoute(teamId)+"/channels/name/%v", channelName) +} + +func (c *Client4) GetChannelByNameForTeamNameRoute(channelName, teamName string) string { + return fmt.Sprintf(c.GetTeamByNameRoute(teamName)+"/channels/name/%v", channelName) +} + +func (c *Client4) GetChannelMembersRoute(channelId string) string { + return fmt.Sprintf(c.GetChannelRoute(channelId) + "/members") +} + +func (c *Client4) GetChannelMemberRoute(channelId, userId string) string { + return fmt.Sprintf(c.GetChannelMembersRoute(channelId)+"/%v", userId) +} + +func (c *Client4) GetPostsRoute() string { + return fmt.Sprintf("/posts") +} + +func (c *Client4) GetPostsEphemeralRoute() string { + return fmt.Sprintf("/posts/ephemeral") +} + +func (c *Client4) GetConfigRoute() string { + return fmt.Sprintf("/config") +} + +func (c *Client4) GetLicenseRoute() string { + return fmt.Sprintf("/license") +} + +func (c *Client4) GetPostRoute(postId string) string { + return fmt.Sprintf(c.GetPostsRoute()+"/%v", postId) +} + +func (c *Client4) GetFilesRoute() string { + return fmt.Sprintf("/files") +} + +func (c *Client4) GetFileRoute(fileId string) string { + return fmt.Sprintf(c.GetFilesRoute()+"/%v", fileId) +} + +func (c *Client4) GetPluginsRoute() string { + return fmt.Sprintf("/plugins") +} + +func (c *Client4) GetPluginRoute(pluginId string) string { + return fmt.Sprintf(c.GetPluginsRoute()+"/%v", pluginId) +} + +func (c *Client4) GetSystemRoute() string { + return fmt.Sprintf("/system") +} + +func (c *Client4) GetTestEmailRoute() string { + return fmt.Sprintf("/email/test") +} + +func (c *Client4) GetTestS3Route() string { + return fmt.Sprintf("/file/s3_test") +} + +func (c *Client4) GetDatabaseRoute() string { + return fmt.Sprintf("/database") +} + +func (c *Client4) GetCacheRoute() string { + return fmt.Sprintf("/caches") +} + +func (c *Client4) GetClusterRoute() string { + return fmt.Sprintf("/cluster") +} + +func (c *Client4) GetIncomingWebhooksRoute() string { + return fmt.Sprintf("/hooks/incoming") +} + +func (c *Client4) GetIncomingWebhookRoute(hookID string) string { + return fmt.Sprintf(c.GetIncomingWebhooksRoute()+"/%v", hookID) +} + +func (c *Client4) GetComplianceReportsRoute() string { + return fmt.Sprintf("/compliance/reports") +} + +func (c *Client4) GetComplianceReportRoute(reportId string) string { + return fmt.Sprintf("/compliance/reports/%v", reportId) +} + +func (c *Client4) GetOutgoingWebhooksRoute() string { + return fmt.Sprintf("/hooks/outgoing") +} + +func (c *Client4) GetOutgoingWebhookRoute(hookID string) string { + return fmt.Sprintf(c.GetOutgoingWebhooksRoute()+"/%v", hookID) +} + +func (c *Client4) GetPreferencesRoute(userId string) string { + return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences") +} + +func (c *Client4) GetUserStatusRoute(userId string) string { + return fmt.Sprintf(c.GetUserRoute(userId) + "/status") +} + +func (c *Client4) GetUserStatusesRoute() string { + return fmt.Sprintf(c.GetUsersRoute() + "/status") +} + +func (c *Client4) GetSamlRoute() string { + return fmt.Sprintf("/saml") +} + +func (c *Client4) GetLdapRoute() string { + return fmt.Sprintf("/ldap") +} + +func (c *Client4) GetBrandRoute() string { + return fmt.Sprintf("/brand") +} + +func (c *Client4) GetDataRetentionRoute() string { + return fmt.Sprintf("/data_retention") +} + +func (c *Client4) GetElasticsearchRoute() string { + return fmt.Sprintf("/elasticsearch") +} + +func (c *Client4) GetCommandsRoute() string { + return fmt.Sprintf("/commands") +} + +func (c *Client4) GetCommandRoute(commandId string) string { + return fmt.Sprintf(c.GetCommandsRoute()+"/%v", commandId) +} + +func (c *Client4) GetEmojisRoute() string { + return fmt.Sprintf("/emoji") +} + +func (c *Client4) GetEmojiRoute(emojiId string) string { + return fmt.Sprintf(c.GetEmojisRoute()+"/%v", emojiId) +} + +func (c *Client4) GetEmojiByNameRoute(name string) string { + return fmt.Sprintf(c.GetEmojisRoute()+"/name/%v", name) +} + +func (c *Client4) GetReactionsRoute() string { + return fmt.Sprintf("/reactions") +} + +func (c *Client4) GetOAuthAppsRoute() string { + return fmt.Sprintf("/oauth/apps") +} + +func (c *Client4) GetOAuthAppRoute(appId string) string { + return fmt.Sprintf("/oauth/apps/%v", appId) +} + +func (c *Client4) GetOpenGraphRoute() string { + return fmt.Sprintf("/opengraph") +} + +func (c *Client4) GetJobsRoute() string { + return fmt.Sprintf("/jobs") +} + +func (c *Client4) GetRolesRoute() string { + return fmt.Sprintf("/roles") +} + +func (c *Client4) GetSchemesRoute() string { + return fmt.Sprintf("/schemes") +} + +func (c *Client4) GetSchemeRoute(id string) string { + return c.GetSchemesRoute() + fmt.Sprintf("/%v", id) +} + +func (c *Client4) GetAnalyticsRoute() string { + return fmt.Sprintf("/analytics") +} + +func (c *Client4) GetTimezonesRoute() string { + return fmt.Sprintf(c.GetSystemRoute() + "/timezones") +} + +func (c *Client4) GetChannelSchemeRoute(channelId string) string { + return fmt.Sprintf(c.GetChannelsRoute()+"/%v/scheme", channelId) +} + +func (c *Client4) GetTeamSchemeRoute(teamId string) string { + return fmt.Sprintf(c.GetTeamsRoute()+"/%v/scheme", teamId) +} + +func (c *Client4) GetTotalUsersStatsRoute() string { + return fmt.Sprintf(c.GetUsersRoute() + "/stats") +} + +func (c *Client4) GetRedirectLocationRoute() string { + return fmt.Sprintf("/redirect_location") +} + +func (c *Client4) GetRegisterTermsOfServiceRoute(userId string) string { + return c.GetUserRoute(userId) + "/terms_of_service" +} + +func (c *Client4) GetTermsOfServiceRoute() string { + return "/terms_of_service" +} + +func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) { + return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag) +} + +func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) { + return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "") +} + +func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) { + return c.DoApiRequest(http.MethodPut, c.ApiUrl+url, data, "") +} + +func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) { + return c.DoApiRequest(http.MethodDelete, c.ApiUrl+url, "", "") +} + +func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) { + rq, _ := http.NewRequest(method, 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 c.HttpHeader != nil && len(c.HttpHeader) > 0 { + + for k, v := range c.HttpHeader { + rq.Header.Set(k, v) + } + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) + } else if rp.StatusCode == 304 { + return rp, nil + } else if rp.StatusCode >= 300 { + defer closeBody(rp) + return rp, AppErrorFromJson(rp.Body) + } else { + return rp, nil + } +} + +func (c *Client4) DoUploadFile(url string, data []byte, contentType string) (*FileUploadResponse, *Response) { + rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data)) + rq.Header.Set("Content-Type", contentType) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, BuildErrorResponse(rp, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return FileUploadResponseFromJson(rp.Body), BuildResponse(rp) + } + } +} + +func (c *Client4) DoEmojiUploadFile(url string, data []byte, contentType string) (*Emoji, *Response) { + 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, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, BuildErrorResponse(rp, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return EmojiFromJson(rp.Body), BuildResponse(rp) + } + } +} + +func (c *Client4) DoUploadImportTeam(url string, data []byte, contentType string) (map[string]string, *Response) { + 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, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, BuildErrorResponse(rp, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return MapFromJson(rp.Body), BuildResponse(rp) + } + } +} + +// CheckStatusOK is a convenience function for checking the standard OK response +// from the web service. +func CheckStatusOK(r *http.Response) bool { + m := MapFromJson(r.Body) + defer closeBody(r) + + if m != nil && m[STATUS] == STATUS_OK { + return true + } + + return false +} + +// Authentication Section + +// LoginById authenticates a user by user id and password. +func (c *Client4) LoginById(id string, password string) (*User, *Response) { + m := make(map[string]string) + m["id"] = id + m["password"] = password + return c.login(m) +} + +// Login authenticates a user by login id, which can be username, email or some sort +// of SSO identifier based on server configuration, and a password. +func (c *Client4) Login(loginId string, password string) (*User, *Response) { + m := make(map[string]string) + m["login_id"] = loginId + m["password"] = password + return c.login(m) +} + +// LoginByLdap authenticates a user by LDAP id and password. +func (c *Client4) LoginByLdap(loginId string, password string) (*User, *Response) { + m := make(map[string]string) + m["login_id"] = loginId + m["password"] = password + m["ldap_only"] = "true" + return c.login(m) +} + +// LoginWithDevice authenticates a user by login id (username, email or some sort +// of SSO identifier based on configuration), password and attaches a device id to +// the session. +func (c *Client4) LoginWithDevice(loginId string, password string, deviceId string) (*User, *Response) { + m := make(map[string]string) + m["login_id"] = loginId + m["password"] = password + m["device_id"] = deviceId + return c.login(m) +} + +func (c *Client4) login(m map[string]string) (*User, *Response) { + if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + c.AuthToken = r.Header.Get(HEADER_TOKEN) + c.AuthType = HEADER_BEARER + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// Logout terminates the current user's session. +func (c *Client4) Logout() (bool, *Response) { + if r, err := c.DoApiPost("/users/logout", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + c.AuthToken = "" + c.AuthType = HEADER_BEARER + + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// SwitchAccountType changes a user's login type from one type to another. +func (c *Client4) SwitchAccountType(switchRequest *SwitchRequest) (string, *Response) { + if r, err := c.DoApiPost(c.GetUsersRoute()+"/login/switch", switchRequest.ToJson()); err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body)["follow_link"], BuildResponse(r) + } +} + +// User Section + +// CreateUser creates a user in the system based on the provided user struct. +func (c *Client4) CreateUser(user *User) (*User, *Response) { + if r, err := c.DoApiPost(c.GetUsersRoute(), user.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// CreateUserWithToken creates a user in the system based on the provided tokenId. +func (c *Client4) CreateUserWithToken(user *User, tokenId string) (*User, *Response) { + var query string + if tokenId != "" { + query = fmt.Sprintf("?t=%v", tokenId) + } else { + err := NewAppError("MissingHashOrData", "api.user.create_user.missing_token.app_error", nil, "", http.StatusBadRequest) + return nil, &Response{StatusCode: err.StatusCode, Error: err} + } + if r, err := c.DoApiPost(c.GetUsersRoute()+query, user.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// CreateUserWithInviteId creates a user in the system based on the provided invited id. +func (c *Client4) CreateUserWithInviteId(user *User, inviteId string) (*User, *Response) { + var query string + if inviteId != "" { + query = fmt.Sprintf("?iid=%v", url.QueryEscape(inviteId)) + } else { + err := NewAppError("MissingInviteId", "api.user.create_user.missing_invite_id.app_error", nil, "", http.StatusBadRequest) + return nil, &Response{StatusCode: err.StatusCode, Error: err} + } + if r, err := c.DoApiPost(c.GetUsersRoute()+query, user.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// GetMe returns the logged in user. +func (c *Client4) GetMe(etag string) (*User, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(ME), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// GetUser returns a user based on the provided user id string. +func (c *Client4) GetUser(userId, etag string) (*User, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// GetUserByUsername returns a user based on the provided user name string. +func (c *Client4) GetUserByUsername(userName, etag string) (*User, *Response) { + if r, err := c.DoApiGet(c.GetUserByUsernameRoute(userName), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// GetUserByEmail returns a user based on the provided user email string. +func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response) { + if r, err := c.DoApiGet(c.GetUserByEmailRoute(email), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// AutocompleteUsersInTeam returns the users on a team based on search term. +func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag string) (*UserAutocomplete, *Response) { + query := fmt.Sprintf("?in_team=%v&name=%v", teamId, username) + if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAutocompleteFromJson(r.Body), BuildResponse(r) + } +} + +// AutocompleteUsersInChannel returns the users in a channel based on search term. +func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, etag string) (*UserAutocomplete, *Response) { + query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v", teamId, channelId, username) + if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAutocompleteFromJson(r.Body), BuildResponse(r) + } +} + +// AutocompleteUsers returns the users in the system based on search term. +func (c *Client4) AutocompleteUsers(username string, etag string) (*UserAutocomplete, *Response) { + query := fmt.Sprintf("?name=%v", username) + if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAutocompleteFromJson(r.Body), BuildResponse(r) + } +} + +// GetProfileImage gets user's profile image. Must be logged in or be a system administrator. +func (c *Client4) GetProfileImage(userId, etag string) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/image", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetProfileImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// GetUsers returns a page of users on the system. Page counting starts at 0. +func (c *Client4) GetUsers(page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersInTeam returns a page of users on a team. Page counting starts at 0. +func (c *Client4) GetUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?in_team=%v&page=%v&per_page=%v", teamId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetNewUsersInTeam returns a page of users on a team. Page counting starts at 0. +func (c *Client4) GetNewUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?sort=create_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetRecentlyActiveUsersInTeam returns a page of users on a team. Page counting starts at 0. +func (c *Client4) GetRecentlyActiveUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?sort=last_activity_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersNotInTeam returns a page of users who are not in a team. Page counting starts at 0. +func (c *Client4) GetUsersNotInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?not_in_team=%v&page=%v&per_page=%v", teamId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersInChannel returns a page of users in a channel. Page counting starts at 0. +func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersInChannelStatus returns a page of users in a channel. Page counting starts at 0. Sorted by Status +func (c *Client4) GetUsersInChannelByStatus(channelId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v&sort=status", channelId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersNotInChannel returns a page of users not in a channel. Page counting starts at 0. +func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?in_team=%v¬_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersWithoutTeam returns a page of users on the system that aren't on any teams. Page counting starts at 0. +func (c *Client4) GetUsersWithoutTeam(page int, perPage int, etag string) ([]*User, *Response) { + query := fmt.Sprintf("?without_team=1&page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersByIds returns a list of users based on the provided user ids. +func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response) { + if r, err := c.DoApiPost(c.GetUsersRoute()+"/ids", ArrayToJson(userIds)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersByUsernames returns a list of users based on the provided usernames. +func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response) { + if r, err := c.DoApiPost(c.GetUsersRoute()+"/usernames", ArrayToJson(usernames)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// SearchUsers returns a list of users based on some search criteria. +func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response) { + if r, err := c.DoApiPost(c.GetUsersRoute()+"/search", search.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserListFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateUser updates a user in the system based on the provided user struct. +func (c *Client4) UpdateUser(user *User) (*User, *Response) { + if r, err := c.DoApiPut(c.GetUserRoute(user.Id), user.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// PatchUser partially updates a user in the system. Any missing fields are not updated. +func (c *Client4) PatchUser(userId string, patch *UserPatch) (*User, *Response) { + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/patch", patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateUserAuth updates a user AuthData (uthData, authService and password) in the system. +func (c *Client4) UpdateUserAuth(userId string, userAuth *UserAuth) (*UserAuth, *Response) { + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/auth", userAuth.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAuthFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateUserMfa activates multi-factor authentication for a user if activate +// is true and a valid code is provided. If activate is false, then code is not +// required and multi-factor authentication is disabled for the user. +func (c *Client4) UpdateUserMfa(userId, code string, activate bool) (bool, *Response) { + requestBody := make(map[string]interface{}) + requestBody["activate"] = activate + requestBody["code"] = code + + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/mfa", StringInterfaceToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// CheckUserMfa checks whether a user has MFA active on their account or not based on the +// provided login id. +func (c *Client4) CheckUserMfa(loginId string) (bool, *Response) { + requestBody := make(map[string]interface{}) + requestBody["login_id"] = loginId + + if r, err := c.DoApiPost(c.GetUsersRoute()+"/mfa", StringInterfaceToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + data := StringInterfaceFromJson(r.Body) + if mfaRequired, ok := data["mfa_required"].(bool); !ok { + return false, BuildResponse(r) + } else { + return mfaRequired, BuildResponse(r) + } + } +} + +// GenerateMfaSecret will generate a new MFA secret for a user and return it as a string and +// as a base64 encoded image QR code. +func (c *Client4) GenerateMfaSecret(userId string) (*MfaSecret, *Response) { + if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/mfa/generate", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MfaSecretFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateUserPassword updates a user's password. Must be logged in as the user or be a system administrator. +func (c *Client4) UpdateUserPassword(userId, currentPassword, newPassword string) (bool, *Response) { + requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword} + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/password", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateUserRoles updates a user's roles in the system. A user can have "system_user" and "system_admin" roles. +func (c *Client4) UpdateUserRoles(userId, roles string) (bool, *Response) { + requestBody := map[string]string{"roles": roles} + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/roles", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateUserActive updates status of a user whether active or not. +func (c *Client4) UpdateUserActive(userId string, active bool) (bool, *Response) { + requestBody := make(map[string]interface{}) + requestBody["active"] = active + + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/active", StringInterfaceToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// DeleteUser deactivates a user in the system based on the provided user id string. +func (c *Client4) DeleteUser(userId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetUserRoute(userId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// SendPasswordResetEmail will send a link for password resetting to a user with the +// provided email. +func (c *Client4) SendPasswordResetEmail(email string) (bool, *Response) { + requestBody := map[string]string{"email": email} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset/send", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// ResetPassword uses a recovery code to update reset a user's password. +func (c *Client4) ResetPassword(token, newPassword string) (bool, *Response) { + requestBody := map[string]string{"token": token, "new_password": newPassword} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetSessions returns a list of sessions based on the provided user id string. +func (c *Client4) GetSessions(userId, etag string) ([]*Session, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/sessions", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SessionsFromJson(r.Body), BuildResponse(r) + } +} + +// RevokeSession revokes a user session based on the provided user id and session id strings. +func (c *Client4) RevokeSession(userId, sessionId string) (bool, *Response) { + requestBody := map[string]string{"session_id": sessionId} + if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/sessions/revoke", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// RevokeAllSessions revokes all sessions for the provided user id string. +func (c *Client4) RevokeAllSessions(userId string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/sessions/revoke/all", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// AttachDeviceId attaches a mobile device ID to the current session. +func (c *Client4) AttachDeviceId(deviceId string) (bool, *Response) { + requestBody := map[string]string{"device_id": deviceId} + if r, err := c.DoApiPut(c.GetUsersRoute()+"/sessions/device", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetTeamsUnreadForUser will return an array with TeamUnread objects that contain the amount +// of unread messages and mentions the current user has for the teams it belongs to. +// An optional team ID can be set to exclude that team from the results. Must be authenticated. +func (c *Client4) GetTeamsUnreadForUser(userId, teamIdToExclude string) ([]*TeamUnread, *Response) { + optional := "" + if teamIdToExclude != "" { + optional += fmt.Sprintf("?exclude_team=%s", url.QueryEscape(teamIdToExclude)) + } + + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/unread"+optional, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamsUnreadFromJson(r.Body), BuildResponse(r) + } +} + +// GetUserAudits returns a list of audit based on the provided user id string. +func (c *Client4) GetUserAudits(userId string, page int, perPage int, etag string) (Audits, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/audits"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return AuditsFromJson(r.Body), BuildResponse(r) + } +} + +// VerifyUserEmail will verify a user's email using the supplied token. +func (c *Client4) VerifyUserEmail(token string) (bool, *Response) { + requestBody := map[string]string{"token": token} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// SendVerificationEmail will send an email to the user with the provided email address, if +// that user exists. The email will contain a link that can be used to verify the user's +// email address. +func (c *Client4) SendVerificationEmail(email string) (bool, *Response) { + requestBody := map[string]string{"email": email} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify/send", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// SetProfileImage sets profile image of the user +func (c *Client4) SetProfileImage(userId string, data []byte) (bool, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("image", "profile.png"); err != nil { + return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetUserRoute(userId)+"/image", bytes.NewReader(body.Bytes())) + rq.Header.Set("Content-Type", writer.FormDataContentType()) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + // set to http.StatusForbidden(403) + return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetUserRoute(userId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)} + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return CheckStatusOK(rp), BuildResponse(rp) + } + } +} + +// CreateUserAccessToken will generate a user access token that can be used in place +// of a session token to access the REST API. Must have the 'create_user_access_token' +// permission and if generating for another user, must have the 'edit_other_users' +// permission. A non-blank description is required. +func (c *Client4) CreateUserAccessToken(userId, description string) (*UserAccessToken, *Response) { + requestBody := map[string]string{"description": description} + if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/tokens", MapToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAccessTokenFromJson(r.Body), BuildResponse(r) + } +} + +// GetUserAccessTokens will get a page of access tokens' id, description, is_active +// and the user_id in the system. The actual token will not be returned. Must have +// the 'manage_system' permission. +func (c *Client4) GetUserAccessTokens(page int, perPage int) ([]*UserAccessToken, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUserAccessTokensRoute()+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAccessTokenListFromJson(r.Body), BuildResponse(r) + } +} + +// GetUserAccessToken will get a user access tokens' id, description, is_active +// and the user_id of the user it is for. The actual token will not be returned. +// Must have the 'read_user_access_token' permission and if getting for another +// user, must have the 'edit_other_users' permission. +func (c *Client4) GetUserAccessToken(tokenId string) (*UserAccessToken, *Response) { + if r, err := c.DoApiGet(c.GetUserAccessTokenRoute(tokenId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAccessTokenFromJson(r.Body), BuildResponse(r) + } +} + +// GetUserAccessTokensForUser will get a paged list of user access tokens showing id, +// description and user_id for each. The actual tokens will not be returned. Must have +// the 'read_user_access_token' permission and if getting for another user, must have the +// 'edit_other_users' permission. +func (c *Client4) GetUserAccessTokensForUser(userId string, page, perPage int) ([]*UserAccessToken, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/tokens"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAccessTokenListFromJson(r.Body), BuildResponse(r) + } +} + +// RevokeUserAccessToken will revoke a user access token by id. Must have the +// 'revoke_user_access_token' permission and if revoking for another user, must have the +// 'edit_other_users' permission. +func (c *Client4) RevokeUserAccessToken(tokenId string) (bool, *Response) { + requestBody := map[string]string{"token_id": tokenId} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/revoke", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// SearchUserAccessTokens returns user access tokens matching the provided search term. +func (c *Client4) SearchUserAccessTokens(search *UserAccessTokenSearch) ([]*UserAccessToken, *Response) { + if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/search", search.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UserAccessTokenListFromJson(r.Body), BuildResponse(r) + } +} + +// DisableUserAccessToken will disable a user access token by id. Must have the +// 'revoke_user_access_token' permission and if disabling for another user, must have the +// 'edit_other_users' permission. +func (c *Client4) DisableUserAccessToken(tokenId string) (bool, *Response) { + requestBody := map[string]string{"token_id": tokenId} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/disable", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// EnableUserAccessToken will enable a user access token by id. Must have the +// 'create_user_access_token' permission and if enabling for another user, must have the +// 'edit_other_users' permission. +func (c *Client4) EnableUserAccessToken(tokenId string) (bool, *Response) { + requestBody := map[string]string{"token_id": tokenId} + if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/enable", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Team Section + +// CreateTeam creates a team in the system based on the provided team struct. +func (c *Client4) CreateTeam(team *Team) (*Team, *Response) { + if r, err := c.DoApiPost(c.GetTeamsRoute(), team.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) + } +} + +// GetTeam returns a team based on the provided team id string. +func (c *Client4) GetTeam(teamId, etag string) (*Team, *Response) { + if r, err := c.DoApiGet(c.GetTeamRoute(teamId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) + } +} + +// GetAllTeams returns all teams based on permissions. +func (c *Client4) GetAllTeams(etag string, page int, perPage int) ([]*Team, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetTeamsRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamListFromJson(r.Body), BuildResponse(r) + } +} + +// GetTeamByName returns a team based on the provided team name string. +func (c *Client4) GetTeamByName(name, etag string) (*Team, *Response) { + if r, err := c.DoApiGet(c.GetTeamByNameRoute(name), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) + } +} + +// SearchTeams returns teams matching the provided search term. +func (c *Client4) SearchTeams(search *TeamSearch) ([]*Team, *Response) { + if r, err := c.DoApiPost(c.GetTeamsRoute()+"/search", search.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamListFromJson(r.Body), BuildResponse(r) + } +} + +// TeamExists returns true or false if the team exist or not. +func (c *Client4) TeamExists(name, etag string) (bool, *Response) { + if r, err := c.DoApiGet(c.GetTeamByNameRoute(name)+"/exists", etag); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapBoolFromJson(r.Body)["exists"], BuildResponse(r) + } +} + +// GetTeamsForUser returns a list of teams a user is on. Must be logged in as the user +// or be a system administrator. +func (c *Client4) GetTeamsForUser(userId, etag string) ([]*Team, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamListFromJson(r.Body), BuildResponse(r) + } +} + +// GetTeamMember returns a team member based on the provided team and user id strings. +func (c *Client4) GetTeamMember(teamId, userId, etag string) (*TeamMember, *Response) { + if r, err := c.DoApiGet(c.GetTeamMemberRoute(teamId, userId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMemberFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateTeamMemberRoles will update the roles on a team for a user. +func (c *Client4) UpdateTeamMemberRoles(teamId, userId, newRoles string) (bool, *Response) { + requestBody := map[string]string{"roles": newRoles} + if r, err := c.DoApiPut(c.GetTeamMemberRoute(teamId, userId)+"/roles", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateTeamMemberSchemeRoles will update the scheme-derived roles on a team for a user. +func (c *Client4) UpdateTeamMemberSchemeRoles(teamId string, userId string, schemeRoles *SchemeRoles) (bool, *Response) { + if r, err := c.DoApiPut(c.GetTeamMemberRoute(teamId, userId)+"/schemeRoles", schemeRoles.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateTeam will update a team. +func (c *Client4) UpdateTeam(team *Team) (*Team, *Response) { + if r, err := c.DoApiPut(c.GetTeamRoute(team.Id), team.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) + } +} + +// PatchTeam partially updates a team. Any missing fields are not updated. +func (c *Client4) PatchTeam(teamId string, patch *TeamPatch) (*Team, *Response) { + if r, err := c.DoApiPut(c.GetTeamRoute(teamId)+"/patch", patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) + } +} + +// SoftDeleteTeam deletes the team softly (archive only, not permanent delete). +func (c *Client4) SoftDeleteTeam(teamId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetTeamRoute(teamId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// PermanentDeleteTeam deletes the team, should only be used when needed for +// compliance and the like +func (c *Client4) PermanentDeleteTeam(teamId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetTeamRoute(teamId) + "?permanent=true"); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetTeamMembers returns team members based on the provided team id string. +func (c *Client4) GetTeamMembers(teamId string, page int, perPage int, etag string) ([]*TeamMember, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetTeamMembersRoute(teamId)+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMembersFromJson(r.Body), BuildResponse(r) + } +} + +// GetTeamMembersForUser returns the team members for a user. +func (c *Client4) GetTeamMembersForUser(userId string, etag string) ([]*TeamMember, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/members", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMembersFromJson(r.Body), BuildResponse(r) + } +} + +// GetTeamMembersByIds will return an array of team members based on the +// team id and a list of user ids provided. Must be authenticated. +func (c *Client4) GetTeamMembersByIds(teamId string, userIds []string) ([]*TeamMember, *Response) { + if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMembersFromJson(r.Body), BuildResponse(r) + } +} + +// AddTeamMember adds user to a team and return a team member. +func (c *Client4) AddTeamMember(teamId, userId string) (*TeamMember, *Response) { + member := &TeamMember{TeamId: teamId, UserId: userId} + + if r, err := c.DoApiPost(c.GetTeamMembersRoute(teamId), member.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMemberFromJson(r.Body), BuildResponse(r) + } +} + +// AddTeamMemberFromInvite adds a user to a team and return a team member using an invite id +// or an invite token/data pair. +func (c *Client4) AddTeamMemberFromInvite(token, inviteId string) (*TeamMember, *Response) { + var query string + + if inviteId != "" { + query += fmt.Sprintf("?invite_id=%v", inviteId) + } + + if token != "" { + query += fmt.Sprintf("?token=%v", token) + } + + if r, err := c.DoApiPost(c.GetTeamsRoute()+"/members/invite"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMemberFromJson(r.Body), BuildResponse(r) + } +} + +// AddTeamMembers adds a number of users to a team and returns the team members. +func (c *Client4) AddTeamMembers(teamId string, userIds []string) ([]*TeamMember, *Response) { + var members []*TeamMember + for _, userId := range userIds { + member := &TeamMember{TeamId: teamId, UserId: userId} + members = append(members, member) + } + + if r, err := c.DoApiPost(c.GetTeamMembersRoute(teamId)+"/batch", TeamMembersToJson(members)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamMembersFromJson(r.Body), BuildResponse(r) + } +} + +// RemoveTeamMember will remove a user from a team. +func (c *Client4) RemoveTeamMember(teamId, userId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetTeamMemberRoute(teamId, userId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetTeamStats returns a team stats based on the team id string. +// Must be authenticated. +func (c *Client4) GetTeamStats(teamId, etag string) (*TeamStats, *Response) { + if r, err := c.DoApiGet(c.GetTeamStatsRoute(teamId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamStatsFromJson(r.Body), BuildResponse(r) + } +} + +// GetTotalUsersStats returns a total system user stats. +// Must be authenticated. +func (c *Client4) GetTotalUsersStats(etag string) (*UsersStats, *Response) { + if r, err := c.DoApiGet(c.GetTotalUsersStatsRoute(), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return UsersStatsFromJson(r.Body), BuildResponse(r) + } +} + +// GetTeamUnread will return a TeamUnread object that contains the amount of +// unread messages and mentions the user has for the specified team. +// Must be authenticated. +func (c *Client4) GetTeamUnread(teamId, userId string) (*TeamUnread, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetTeamRoute(teamId)+"/unread", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamUnreadFromJson(r.Body), BuildResponse(r) + } +} + +// ImportTeam will import an exported team from other app into a existing team. +func (c *Client4) ImportTeam(data []byte, filesize int, importFrom, filename, teamId string) (map[string]string, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("file", filename); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if part, err := writer.CreateFormField("filesize"); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file_size.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, strings.NewReader(strconv.Itoa(filesize))); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file_size.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if part, err := writer.CreateFormField("importFrom"); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.import_from.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, strings.NewReader(importFrom)); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.import_from.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + return c.DoUploadImportTeam(c.GetTeamImportRoute(teamId), body.Bytes(), writer.FormDataContentType()) +} + +// InviteUsersToTeam invite users by email to the team. +func (c *Client4) InviteUsersToTeam(teamId string, userEmails []string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/invite/email", ArrayToJson(userEmails)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetTeamInviteInfo returns a team object from an invite id containing sanitized information. +func (c *Client4) GetTeamInviteInfo(inviteId string) (*Team, *Response) { + if r, err := c.DoApiGet(c.GetTeamsRoute()+"/invite/"+inviteId, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamFromJson(r.Body), BuildResponse(r) + } +} + +// SetTeamIcon sets team icon of the team +func (c *Client4) SetTeamIcon(teamId string, data []byte) (bool, *Response) { + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("image", "teamIcon.png"); err != nil { + return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetTeamRoute(teamId)+"/image", bytes.NewReader(body.Bytes())) + rq.Header.Set("Content-Type", writer.FormDataContentType()) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + // set to http.StatusForbidden(403) + return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetTeamRoute(teamId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)} + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return CheckStatusOK(rp), BuildResponse(rp) + } + } +} + +// GetTeamIcon gets the team icon of the team +func (c *Client4) GetTeamIcon(teamId, etag string) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetTeamRoute(teamId)+"/image", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetTeamIcon", "model.client.get_team_icon.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// RemoveTeamIcon updates LastTeamIconUpdate to 0 which indicates team icon is removed. +func (c *Client4) RemoveTeamIcon(teamId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetTeamRoute(teamId) + "/image"); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Channel Section + +// CreateChannel creates a channel based on the provided channel struct. +func (c *Client4) CreateChannel(channel *Channel) (*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelsRoute(), channel.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateChannel update a channel based on the provided channel struct. +func (c *Client4) UpdateChannel(channel *Channel) (*Channel, *Response) { + if r, err := c.DoApiPut(c.GetChannelRoute(channel.Id), channel.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// PatchChannel partially updates a channel. Any missing fields are not updated. +func (c *Client4) PatchChannel(channelId string, patch *ChannelPatch) (*Channel, *Response) { + if r, err := c.DoApiPut(c.GetChannelRoute(channelId)+"/patch", patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// ConvertChannelToPrivate converts public to private channel. +func (c *Client4) ConvertChannelToPrivate(channelId string) (*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/convert", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// RestoreChannel restores a previously deleted channel. Any missing fields are not updated. +func (c *Client4) RestoreChannel(channelId string) (*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/restore", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// CreateDirectChannel creates a direct message channel based on the two user +// ids provided. +func (c *Client4) CreateDirectChannel(userId1, userId2 string) (*Channel, *Response) { + requestBody := []string{userId1, userId2} + if r, err := c.DoApiPost(c.GetChannelsRoute()+"/direct", ArrayToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// CreateGroupChannel creates a group message channel based on userIds provided +func (c *Client4) CreateGroupChannel(userIds []string) (*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelsRoute()+"/group", ArrayToJson(userIds)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannel returns a channel based on the provided channel id string. +func (c *Client4) GetChannel(channelId, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelRoute(channelId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelStats returns statistics for a channel. +func (c *Client4) GetChannelStats(channelId string, etag string) (*ChannelStats, *Response) { + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/stats", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelStatsFromJson(r.Body), BuildResponse(r) + } +} + +// GetPinnedPosts gets a list of pinned posts. +func (c *Client4) GetPinnedPosts(channelId string, etag string) (*PostList, *Response) { + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetPublicChannelsForTeam returns a list of public channels based on the provided team id string. +func (c *Client4) GetPublicChannelsForTeam(teamId string, page int, perPage int, etag string) ([]*Channel, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetChannelsForTeamRoute(teamId)+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelSliceFromJson(r.Body), BuildResponse(r) + } +} + +// GetDeletedChannelsForTeam returns a list of public channels based on the provided team id string. +func (c *Client4) GetDeletedChannelsForTeam(teamId string, page int, perPage int, etag string) ([]*Channel, *Response) { + query := fmt.Sprintf("/deleted?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetChannelsForTeamRoute(teamId)+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelSliceFromJson(r.Body), BuildResponse(r) + } +} + +// GetPublicChannelsByIdsForTeam returns a list of public channels based on provided team id string +func (c *Client4) GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) ([]*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelsForTeamRoute(teamId)+"/ids", ArrayToJson(channelIds)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelSliceFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelsForTeamForUser returns a list channels of on a team for a user. +func (c *Client4) GetChannelsForTeamForUser(teamId, userId, etag string) ([]*Channel, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetTeamRoute(teamId)+"/channels", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelSliceFromJson(r.Body), BuildResponse(r) + } +} + +// SearchChannels returns the channels on a team matching the provided search term. +func (c *Client4) SearchChannels(teamId string, search *ChannelSearch) ([]*Channel, *Response) { + if r, err := c.DoApiPost(c.GetChannelsForTeamRoute(teamId)+"/search", search.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelSliceFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteChannel deletes channel based on the provided channel id string. +func (c *Client4) DeleteChannel(channelId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetChannelRoute(channelId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetChannelByName returns a channel based on the provided channel name and team id strings. +func (c *Client4) GetChannelByName(channelName, teamId string, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelByNameRoute(channelName, teamId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +func (c *Client4) GetChannelByNameIncludeDeleted(channelName, teamId string, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelByNameRoute(channelName, teamId)+"?include_deleted=true", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelByNameForTeamName returns a channel based on the provided channel name and team name strings. +func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +func (c *Client4) GetChannelByNameForTeamNameIncludeDeleted(channelName, teamName string, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName)+"?include_deleted=true", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelMembers gets a page of channel members. +func (c *Client4) GetChannelMembers(channelId string, page, perPage int, etag string) (*ChannelMembers, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetChannelMembersRoute(channelId)+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelMembersFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelMembersByIds gets the channel members in a channel for a list of user ids. +func (c *Client4) GetChannelMembersByIds(channelId string, userIds []string) (*ChannelMembers, *Response) { + if r, err := c.DoApiPost(c.GetChannelMembersRoute(channelId)+"/ids", ArrayToJson(userIds)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelMembersFromJson(r.Body), BuildResponse(r) + + } +} + +// GetChannelMember gets a channel member. +func (c *Client4) GetChannelMember(channelId, userId, etag string) (*ChannelMember, *Response) { + if r, err := c.DoApiGet(c.GetChannelMemberRoute(channelId, userId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelMemberFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelMembersForUser gets all the channel members for a user on a team. +func (c *Client4) GetChannelMembersForUser(userId, teamId, etag string) (*ChannelMembers, *Response) { + if r, err := c.DoApiGet(fmt.Sprintf(c.GetUserRoute(userId)+"/teams/%v/channels/members", teamId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelMembersFromJson(r.Body), BuildResponse(r) + } +} + +// ViewChannel performs a view action for a user. Synonymous with switching channels or marking channels as read by a user. +func (c *Client4) ViewChannel(userId string, view *ChannelView) (*ChannelViewResponse, *Response) { + url := fmt.Sprintf(c.GetChannelsRoute()+"/members/%v/view", userId) + if r, err := c.DoApiPost(url, view.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelViewResponseFromJson(r.Body), BuildResponse(r) + } +} + +// GetChannelUnread will return a ChannelUnread object that contains the number of +// unread messages and mentions for a user. +func (c *Client4) GetChannelUnread(channelId, userId string) (*ChannelUnread, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetChannelRoute(channelId)+"/unread", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelUnreadFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateChannelRoles will update the roles on a channel for a user. +func (c *Client4) UpdateChannelRoles(channelId, userId, roles string) (bool, *Response) { + requestBody := map[string]string{"roles": roles} + if r, err := c.DoApiPut(c.GetChannelMemberRoute(channelId, userId)+"/roles", MapToJson(requestBody)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateChannelMemberSchemeRoles will update the scheme-derived roles on a channel for a user. +func (c *Client4) UpdateChannelMemberSchemeRoles(channelId string, userId string, schemeRoles *SchemeRoles) (bool, *Response) { + if r, err := c.DoApiPut(c.GetChannelMemberRoute(channelId, userId)+"/schemeRoles", schemeRoles.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateChannelNotifyProps will update the notification properties on a channel for a user. +func (c *Client4) UpdateChannelNotifyProps(channelId, userId string, props map[string]string) (bool, *Response) { + if r, err := c.DoApiPut(c.GetChannelMemberRoute(channelId, userId)+"/notify_props", MapToJson(props)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// AddChannelMember adds user to channel and return a channel member. +func (c *Client4) AddChannelMember(channelId, userId string) (*ChannelMember, *Response) { + requestBody := map[string]string{"user_id": userId} + if r, err := c.DoApiPost(c.GetChannelMembersRoute(channelId)+"", MapToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelMemberFromJson(r.Body), BuildResponse(r) + } +} + +// AddChannelMemberWithRootId adds user to channel and return a channel member. Post add to channel message has the postRootId. +func (c *Client4) AddChannelMemberWithRootId(channelId, userId, postRootId string) (*ChannelMember, *Response) { + requestBody := map[string]string{"user_id": userId, "post_root_id": postRootId} + if r, err := c.DoApiPost(c.GetChannelMembersRoute(channelId)+"", MapToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelMemberFromJson(r.Body), BuildResponse(r) + } +} + +// RemoveUserFromChannel will delete the channel member object for a user, effectively removing the user from a channel. +func (c *Client4) RemoveUserFromChannel(channelId, userId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetChannelMemberRoute(channelId, userId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// AutocompleteChannelsForTeam will return an ordered list of channels autocomplete suggestions +func (c *Client4) AutocompleteChannelsForTeam(teamId, name string) (*ChannelList, *Response) { + query := fmt.Sprintf("?name=%v", name) + if r, err := c.DoApiGet(c.GetChannelsForTeamRoute(teamId)+"/autocomplete"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelListFromJson(r.Body), BuildResponse(r) + } +} + +// AutocompleteChannelsForTeamForSearch will return an ordered list of your channels autocomplete suggestions +func (c *Client4) AutocompleteChannelsForTeamForSearch(teamId, name string) (*ChannelList, *Response) { + query := fmt.Sprintf("?name=%v", name) + if r, err := c.DoApiGet(c.GetChannelsForTeamRoute(teamId)+"/search_autocomplete"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelListFromJson(r.Body), BuildResponse(r) + } +} + +// Post Section + +// CreatePost creates a post based on the provided post struct. +func (c *Client4) CreatePost(post *Post) (*Post, *Response) { + if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToUnsanitizedJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostFromJson(r.Body), BuildResponse(r) + } +} + +// CreatePostEphemeral creates a ephemeral post based on the provided post struct which is send to the given user id +func (c *Client4) CreatePostEphemeral(post *PostEphemeral) (*Post, *Response) { + if r, err := c.DoApiPost(c.GetPostsEphemeralRoute(), post.ToUnsanitizedJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostFromJson(r.Body), BuildResponse(r) + } +} + +// UpdatePost updates a post based on the provided post struct. +func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response) { + if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToUnsanitizedJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostFromJson(r.Body), BuildResponse(r) + } +} + +// PatchPost partially updates a post. Any missing fields are not updated. +func (c *Client4) PatchPost(postId string, patch *PostPatch) (*Post, *Response) { + if r, err := c.DoApiPut(c.GetPostRoute(postId)+"/patch", patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostFromJson(r.Body), BuildResponse(r) + } +} + +// PinPost pin a post based on provided post id string. +func (c *Client4) PinPost(postId string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/pin", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UnpinPost unpin a post based on provided post id string. +func (c *Client4) UnpinPost(postId string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/unpin", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetPost gets a single post. +func (c *Client4) GetPost(postId string, etag string) (*Post, *Response) { + if r, err := c.DoApiGet(c.GetPostRoute(postId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostFromJson(r.Body), BuildResponse(r) + } +} + +// DeletePost deletes a post from the provided post id string. +func (c *Client4) DeletePost(postId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetPostRoute(postId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetPostThread gets a post with all the other posts in the same thread. +func (c *Client4) GetPostThread(postId string, etag string) (*PostList, *Response) { + if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/thread", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetPostsForChannel gets a page of posts with an array for ordering for a channel. +func (c *Client4) GetPostsForChannel(channelId string, page, perPage int, etag string) (*PostList, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetFlaggedPostsForUser returns flagged posts of a user based on user id string. +func (c *Client4) GetFlaggedPostsForUser(userId string, page int, perPage int) (*PostList, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/posts/flagged"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetFlaggedPostsForUserInTeam returns flagged posts in team of a user based on user id string. +func (c *Client4) GetFlaggedPostsForUserInTeam(userId string, teamId string, page int, perPage int) (*PostList, *Response) { + if len(teamId) == 0 || len(teamId) != 26 { + return nil, &Response{StatusCode: http.StatusBadRequest, Error: NewAppError("GetFlaggedPostsForUserInTeam", "model.client.get_flagged_posts_in_team.missing_parameter.app_error", nil, "", http.StatusBadRequest)} + } + + query := fmt.Sprintf("?team_id=%v&page=%v&per_page=%v", teamId, page, perPage) + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/posts/flagged"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetFlaggedPostsForUserInChannel returns flagged posts in channel of a user based on user id string. +func (c *Client4) GetFlaggedPostsForUserInChannel(userId string, channelId string, page int, perPage int) (*PostList, *Response) { + if len(channelId) == 0 || len(channelId) != 26 { + return nil, &Response{StatusCode: http.StatusBadRequest, Error: NewAppError("GetFlaggedPostsForUserInChannel", "model.client.get_flagged_posts_in_channel.missing_parameter.app_error", nil, "", http.StatusBadRequest)} + } + + query := fmt.Sprintf("?channel_id=%v&page=%v&per_page=%v", channelId, page, perPage) + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/posts/flagged"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetPostsSince gets posts created after a specified time as Unix time in milliseconds. +func (c *Client4) GetPostsSince(channelId string, time int64) (*PostList, *Response) { + query := fmt.Sprintf("?since=%v", time) + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetPostsAfter gets a page of posts that were posted after the post provided. +func (c *Client4) GetPostsAfter(channelId, postId string, page, perPage int, etag string) (*PostList, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&after=%v", page, perPage, postId) + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// GetPostsBefore gets a page of posts that were posted before the post provided. +func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, etag string) (*PostList, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&before=%v", page, perPage, postId) + if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// SearchPosts returns any posts with matching terms string. +func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*PostList, *Response) { + params := SearchParameter{ + Terms: &terms, + IsOrSearch: &isOrSearch, + } + return c.SearchPostsWithParams(teamId, ¶ms) +} + +// SearchPosts returns any posts with matching terms string. +func (c *Client4) SearchPostsWithParams(teamId string, params *SearchParameter) (*PostList, *Response) { + if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", params.SearchParameterToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + +// SearchPosts returns any posts with matching terms string, including . +func (c *Client4) SearchPostsWithMatches(teamId string, terms string, isOrSearch bool) (*PostSearchResults, *Response) { + requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch} + if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", StringInterfaceToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostSearchResultsFromJson(r.Body), BuildResponse(r) + } +} + +// DoPostAction performs a post action. +func (c *Client4) DoPostAction(postId, actionId string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/actions/"+actionId, ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// File Section + +// UploadFile will upload a file to a channel using a multipart request, to be later attached to a post. +// This method is functionally equivalent to Client4.UploadFileAsRequestBody. +func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("files", filename); err != nil { + return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if part, err := writer.CreateFormField("channel_id"); err != nil { + return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil { + return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + return c.DoUploadFile(c.GetFilesRoute(), body.Bytes(), writer.FormDataContentType()) +} + +// UploadFileAsRequestBody will upload a file to a channel as the body of a request, to be later attached +// to a post. This method is functionally equivalent to Client4.UploadFile. +func (c *Client4) UploadFileAsRequestBody(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) { + return c.DoUploadFile(c.GetFilesRoute()+fmt.Sprintf("?channel_id=%v&filename=%v", url.QueryEscape(channelId), url.QueryEscape(filename)), data, http.DetectContentType(data)) +} + +// GetFile gets the bytes for a file by id. +func (c *Client4) GetFile(fileId string) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// DownloadFile gets the bytes for a file by id, optionally adding headers to force the browser to download it +func (c *Client4) DownloadFile(fileId string, download bool) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+fmt.Sprintf("?download=%v", download), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("DownloadFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// GetFileThumbnail gets the bytes for a file by id. +func (c *Client4) GetFileThumbnail(fileId string) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/thumbnail", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// DownloadFileThumbnail gets the bytes for a file by id, optionally adding headers to force the browser to download it. +func (c *Client4) DownloadFileThumbnail(fileId string, download bool) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+fmt.Sprintf("/thumbnail?download=%v", download), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("DownloadFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// GetFileLink gets the public link of a file by id. +func (c *Client4) GetFileLink(fileId string) (string, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/link", ""); err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + return MapFromJson(r.Body)["link"], BuildResponse(r) + } +} + +// GetFilePreview gets the bytes for a file by id. +func (c *Client4) GetFilePreview(fileId string) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/preview", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetFilePreview", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// DownloadFilePreview gets the bytes for a file by id. +func (c *Client4) DownloadFilePreview(fileId string, download bool) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+fmt.Sprintf("/preview?download=%v", download), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("DownloadFilePreview", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// GetFileInfo gets all the file info objects. +func (c *Client4) GetFileInfo(fileId string) (*FileInfo, *Response) { + if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/info", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return FileInfoFromJson(r.Body), BuildResponse(r) + } +} + +// GetFileInfosForPost gets all the file info objects attached to a post. +func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo, *Response) { + if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/files/info", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return FileInfosFromJson(r.Body), BuildResponse(r) + } +} + +// General/System Section + +// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above. +func (c *Client4) GetPing() (string, *Response) { + if r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", ""); r != nil && r.StatusCode == 500 { + defer r.Body.Close() + return "unhealthy", BuildErrorResponse(r, err) + } else if err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body)["status"], BuildResponse(r) + } +} + +// TestEmail will attempt to connect to the configured SMTP server. +func (c *Client4) TestEmail(config *Config) (bool, *Response) { + if r, err := c.DoApiPost(c.GetTestEmailRoute(), config.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// TestS3Connection will attempt to connect to the AWS S3. +func (c *Client4) TestS3Connection(config *Config) (bool, *Response) { + if r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetConfig will retrieve the server config with some sanitized items. +func (c *Client4) GetConfig() (*Config, *Response) { + if r, err := c.DoApiGet(c.GetConfigRoute(), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ConfigFromJson(r.Body), BuildResponse(r) + } +} + +// ReloadConfig will reload the server configuration. +func (c *Client4) ReloadConfig() (bool, *Response) { + if r, err := c.DoApiPost(c.GetConfigRoute()+"/reload", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetOldClientConfig will retrieve the parts of the server configuration needed by the +// client, formatted in the old format. +func (c *Client4) GetOldClientConfig(etag string) (map[string]string, *Response) { + if r, err := c.DoApiGet(c.GetConfigRoute()+"/client?format=old", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body), BuildResponse(r) + } +} + +// GetEnvironmentConfig will retrieve a map mirroring the server configuration where fields +// are set to true if the corresponding config setting is set through an environment variable. +// Settings that haven't been set through environment variables will be missing from the map. +func (c *Client4) GetEnvironmentConfig() (map[string]interface{}, *Response) { + if r, err := c.DoApiGet(c.GetConfigRoute()+"/environment", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return StringInterfaceFromJson(r.Body), BuildResponse(r) + } +} + +// GetOldClientLicense will retrieve the parts of the server license needed by the +// client, formatted in the old format. +func (c *Client4) GetOldClientLicense(etag string) (map[string]string, *Response) { + if r, err := c.DoApiGet(c.GetLicenseRoute()+"/client?format=old", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body), BuildResponse(r) + } +} + +// DatabaseRecycle will recycle the connections. Discard current connection and get new one. +func (c *Client4) DatabaseRecycle() (bool, *Response) { + if r, err := c.DoApiPost(c.GetDatabaseRoute()+"/recycle", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// InvalidateCaches will purge the cache and can affect the performance while is cleaning. +func (c *Client4) InvalidateCaches() (bool, *Response) { + if r, err := c.DoApiPost(c.GetCacheRoute()+"/invalidate", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateConfig will update the server configuration. +func (c *Client4) UpdateConfig(config *Config) (*Config, *Response) { + if r, err := c.DoApiPut(c.GetConfigRoute(), config.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ConfigFromJson(r.Body), BuildResponse(r) + } +} + +// UploadLicenseFile will add a license file to the system. +func (c *Client4) UploadLicenseFile(data []byte) (bool, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("license", "test-license.mattermost-license"); err != nil { + return false, &Response{Error: NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return false, &Response{Error: NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return false, &Response{Error: NewAppError("UploadLicenseFile", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetLicenseRoute(), bytes.NewReader(body.Bytes())) + rq.Header.Set("Content-Type", writer.FormDataContentType()) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetLicenseRoute(), "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)} + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return CheckStatusOK(rp), BuildResponse(rp) + } + } +} + +// RemoveLicenseFile will remove the server license it exists. Note that this will +// disable all enterprise features. +func (c *Client4) RemoveLicenseFile() (bool, *Response) { + if r, err := c.DoApiDelete(c.GetLicenseRoute()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetAnalyticsOld will retrieve analytics using the old format. New format is not +// available but the "/analytics" endpoint is reserved for it. The "name" argument is optional +// and defaults to "standard". The "teamId" argument is optional and will limit results +// to a specific team. +func (c *Client4) GetAnalyticsOld(name, teamId string) (AnalyticsRows, *Response) { + query := fmt.Sprintf("?name=%v&team_id=%v", name, teamId) + if r, err := c.DoApiGet(c.GetAnalyticsRoute()+"/old"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return AnalyticsRowsFromJson(r.Body), BuildResponse(r) + } +} + +// Webhooks Section + +// CreateIncomingWebhook creates an incoming webhook for a channel. +func (c *Client4) CreateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response) { + if r, err := c.DoApiPost(c.GetIncomingWebhooksRoute(), hook.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return IncomingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateIncomingWebhook updates an incoming webhook for a channel. +func (c *Client4) UpdateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response) { + if r, err := c.DoApiPut(c.GetIncomingWebhookRoute(hook.Id), hook.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return IncomingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// GetIncomingWebhooks returns a page of incoming webhooks on the system. Page counting starts at 0. +func (c *Client4) GetIncomingWebhooks(page int, perPage int, etag string) ([]*IncomingWebhook, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetIncomingWebhooksRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return IncomingWebhookListFromJson(r.Body), BuildResponse(r) + } +} + +// GetIncomingWebhooksForTeam returns a page of incoming webhooks for a team. Page counting starts at 0. +func (c *Client4) GetIncomingWebhooksForTeam(teamId string, page int, perPage int, etag string) ([]*IncomingWebhook, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId) + if r, err := c.DoApiGet(c.GetIncomingWebhooksRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return IncomingWebhookListFromJson(r.Body), BuildResponse(r) + } +} + +// GetIncomingWebhook returns an Incoming webhook given the hook ID +func (c *Client4) GetIncomingWebhook(hookID string, etag string) (*IncomingWebhook, *Response) { + if r, err := c.DoApiGet(c.GetIncomingWebhookRoute(hookID), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return IncomingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteIncomingWebhook deletes and Incoming Webhook given the hook ID +func (c *Client4) DeleteIncomingWebhook(hookID string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetIncomingWebhookRoute(hookID)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// CreateOutgoingWebhook creates an outgoing webhook for a team or channel. +func (c *Client4) CreateOutgoingWebhook(hook *OutgoingWebhook) (*OutgoingWebhook, *Response) { + if r, err := c.DoApiPost(c.GetOutgoingWebhooksRoute(), hook.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateOutgoingWebhook creates an outgoing webhook for a team or channel. +func (c *Client4) UpdateOutgoingWebhook(hook *OutgoingWebhook) (*OutgoingWebhook, *Response) { + if r, err := c.DoApiPut(c.GetOutgoingWebhookRoute(hook.Id), hook.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// GetOutgoingWebhooks returns a page of outgoing webhooks on the system. Page counting starts at 0. +func (c *Client4) GetOutgoingWebhooks(page int, perPage int, etag string) ([]*OutgoingWebhook, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetOutgoingWebhooksRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookListFromJson(r.Body), BuildResponse(r) + } +} + +// GetOutgoingWebhook outgoing webhooks on the system requested by Hook Id. +func (c *Client4) GetOutgoingWebhook(hookId string) (*OutgoingWebhook, *Response) { + if r, err := c.DoApiGet(c.GetOutgoingWebhookRoute(hookId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// GetOutgoingWebhooksForChannel returns a page of outgoing webhooks for a channel. Page counting starts at 0. +func (c *Client4) GetOutgoingWebhooksForChannel(channelId string, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&channel_id=%v", page, perPage, channelId) + if r, err := c.DoApiGet(c.GetOutgoingWebhooksRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookListFromJson(r.Body), BuildResponse(r) + } +} + +// GetOutgoingWebhooksForTeam returns a page of outgoing webhooks for a team. Page counting starts at 0. +func (c *Client4) GetOutgoingWebhooksForTeam(teamId string, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId) + if r, err := c.DoApiGet(c.GetOutgoingWebhooksRoute()+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookListFromJson(r.Body), BuildResponse(r) + } +} + +// RegenOutgoingHookToken regenerate the outgoing webhook token. +func (c *Client4) RegenOutgoingHookToken(hookId string) (*OutgoingWebhook, *Response) { + if r, err := c.DoApiPost(c.GetOutgoingWebhookRoute(hookId)+"/regen_token", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OutgoingWebhookFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteOutgoingWebhook delete the outgoing webhook on the system requested by Hook Id. +func (c *Client4) DeleteOutgoingWebhook(hookId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetOutgoingWebhookRoute(hookId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Preferences Section + +// GetPreferences returns the user's preferences. +func (c *Client4) GetPreferences(userId string) (Preferences, *Response) { + if r, err := c.DoApiGet(c.GetPreferencesRoute(userId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + preferences, _ := PreferencesFromJson(r.Body) + defer closeBody(r) + return preferences, BuildResponse(r) + } +} + +// UpdatePreferences saves the user's preferences. +func (c *Client4) UpdatePreferences(userId string, preferences *Preferences) (bool, *Response) { + if r, err := c.DoApiPut(c.GetPreferencesRoute(userId), preferences.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return true, BuildResponse(r) + } +} + +// DeletePreferences deletes the user's preferences. +func (c *Client4) DeletePreferences(userId string, preferences *Preferences) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPreferencesRoute(userId)+"/delete", preferences.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return true, BuildResponse(r) + } +} + +// GetPreferencesByCategory returns the user's preferences from the provided category string. +func (c *Client4) GetPreferencesByCategory(userId string, category string) (Preferences, *Response) { + url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s", category) + if r, err := c.DoApiGet(url, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + preferences, _ := PreferencesFromJson(r.Body) + defer closeBody(r) + return preferences, BuildResponse(r) + } +} + +// GetPreferenceByCategoryAndName returns the user's preferences from the provided category and preference name string. +func (c *Client4) GetPreferenceByCategoryAndName(userId string, category string, preferenceName string) (*Preference, *Response) { + url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s/name/%v", category, preferenceName) + if r, err := c.DoApiGet(url, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PreferenceFromJson(r.Body), BuildResponse(r) + } +} + +// SAML Section + +// GetSamlMetadata returns metadata for the SAML configuration. +func (c *Client4) GetSamlMetadata() (string, *Response) { + if r, err := c.DoApiGet(c.GetSamlRoute()+"/metadata", ""); err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + buf := new(bytes.Buffer) + buf.ReadFrom(r.Body) + return buf.String(), BuildResponse(r) + } +} + +func samlFileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, error) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("certificate", filename); err != nil { + return nil, nil, err + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return nil, nil, err + } + + if err := writer.Close(); err != nil { + return nil, nil, err + } + + return body.Bytes(), writer, nil +} + +// UploadSamlIdpCertificate will upload an IDP certificate for SAML and set the config to use it. +func (c *Client4) UploadSamlIdpCertificate(data []byte, filename string) (bool, *Response) { + body, writer, err := samlFileToMultipart(data, filename) + if err != nil { + return false, &Response{Error: NewAppError("UploadSamlIdpCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + _, resp := c.DoUploadFile(c.GetSamlRoute()+"/certificate/idp", body, writer.FormDataContentType()) + return resp.Error == nil, resp +} + +// UploadSamlPublicCertificate will upload a public certificate for SAML and set the config to use it. +func (c *Client4) UploadSamlPublicCertificate(data []byte, filename string) (bool, *Response) { + body, writer, err := samlFileToMultipart(data, filename) + if err != nil { + return false, &Response{Error: NewAppError("UploadSamlPublicCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + _, resp := c.DoUploadFile(c.GetSamlRoute()+"/certificate/public", body, writer.FormDataContentType()) + return resp.Error == nil, resp +} + +// UploadSamlPrivateCertificate will upload a private key for SAML and set the config to use it. +func (c *Client4) UploadSamlPrivateCertificate(data []byte, filename string) (bool, *Response) { + body, writer, err := samlFileToMultipart(data, filename) + if err != nil { + return false, &Response{Error: NewAppError("UploadSamlPrivateCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + _, resp := c.DoUploadFile(c.GetSamlRoute()+"/certificate/private", body, writer.FormDataContentType()) + return resp.Error == nil, resp +} + +// DeleteSamlIdpCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML. +func (c *Client4) DeleteSamlIdpCertificate() (bool, *Response) { + if r, err := c.DoApiDelete(c.GetSamlRoute() + "/certificate/idp"); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// DeleteSamlPublicCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML. +func (c *Client4) DeleteSamlPublicCertificate() (bool, *Response) { + if r, err := c.DoApiDelete(c.GetSamlRoute() + "/certificate/public"); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// DeleteSamlPrivateCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML. +func (c *Client4) DeleteSamlPrivateCertificate() (bool, *Response) { + if r, err := c.DoApiDelete(c.GetSamlRoute() + "/certificate/private"); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetSamlCertificateStatus returns metadata for the SAML configuration. +func (c *Client4) GetSamlCertificateStatus() (*SamlCertificateStatus, *Response) { + if r, err := c.DoApiGet(c.GetSamlRoute()+"/certificate/status", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SamlCertificateStatusFromJson(r.Body), BuildResponse(r) + } +} + +// Compliance Section + +// CreateComplianceReport creates an incoming webhook for a channel. +func (c *Client4) CreateComplianceReport(report *Compliance) (*Compliance, *Response) { + if r, err := c.DoApiPost(c.GetComplianceReportsRoute(), report.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ComplianceFromJson(r.Body), BuildResponse(r) + } +} + +// GetComplianceReports returns list of compliance reports. +func (c *Client4) GetComplianceReports(page, perPage int) (Compliances, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetComplianceReportsRoute()+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CompliancesFromJson(r.Body), BuildResponse(r) + } +} + +// GetComplianceReport returns a compliance report. +func (c *Client4) GetComplianceReport(reportId string) (*Compliance, *Response) { + if r, err := c.DoApiGet(c.GetComplianceReportRoute(reportId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ComplianceFromJson(r.Body), BuildResponse(r) + } +} + +// DownloadComplianceReport returns a full compliance report as a file. +func (c *Client4) DownloadComplianceReport(reportId string) ([]byte, *Response) { + var rq *http.Request + rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetComplianceReportRoute(reportId), nil) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, &Response{Error: NewAppError("DownloadComplianceReport", "model.client.connecting.app_error", nil, err.Error(), http.StatusBadRequest)} + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else if data, err := ioutil.ReadAll(rp.Body); err != nil { + return nil, BuildErrorResponse(rp, NewAppError("DownloadComplianceReport", "model.client.read_file.app_error", nil, err.Error(), rp.StatusCode)) + } else { + return data, BuildResponse(rp) + } + } +} + +// Cluster Section + +// GetClusterStatus returns the status of all the configured cluster nodes. +func (c *Client4) GetClusterStatus() ([]*ClusterInfo, *Response) { + if r, err := c.DoApiGet(c.GetClusterRoute()+"/status", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ClusterInfosFromJson(r.Body), BuildResponse(r) + } +} + +// LDAP Section + +// SyncLdap will force a sync with the configured LDAP server. +func (c *Client4) SyncLdap() (bool, *Response) { + if r, err := c.DoApiPost(c.GetLdapRoute()+"/sync", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// TestLdap will attempt to connect to the configured LDAP server and return OK if configured +// correctly. +func (c *Client4) TestLdap() (bool, *Response) { + if r, err := c.DoApiPost(c.GetLdapRoute()+"/test", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Audits Section + +// GetAudits returns a list of audits for the whole system. +func (c *Client4) GetAudits(page int, perPage int, etag string) (Audits, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet("/audits"+query, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return AuditsFromJson(r.Body), BuildResponse(r) + } +} + +// Brand Section + +// GetBrandImage retrieves the previously uploaded brand image. +func (c *Client4) GetBrandImage() ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetBrandRoute()+"/image", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if r.StatusCode >= 300 { + return nil, BuildErrorResponse(r, AppErrorFromJson(r.Body)) + } else if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetBrandImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// UploadBrandImage sets the brand image for the system. +func (c *Client4) UploadBrandImage(data []byte) (bool, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("image", "brand.png"); err != nil { + return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { + return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + if err := writer.Close(); err != nil { + return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} + } + + rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetBrandRoute()+"/image", bytes.NewReader(body.Bytes())) + rq.Header.Set("Content-Type", writer.FormDataContentType()) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetBrandRoute()+"/image", "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)} + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return CheckStatusOK(rp), BuildResponse(rp) + } + } +} + +// Logs Section + +// GetLogs page of logs as a string array. +func (c *Client4) GetLogs(page, perPage int) ([]string, *Response) { + query := fmt.Sprintf("?page=%v&logs_per_page=%v", page, perPage) + if r, err := c.DoApiGet("/logs"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ArrayFromJson(r.Body), BuildResponse(r) + } +} + +// PostLog is a convenience Web Service call so clients can log messages into +// the server-side logs. For example we typically log javascript error messages +// into the server-side. It returns the log message if the logging was successful. +func (c *Client4) PostLog(message map[string]string) (map[string]string, *Response) { + if r, err := c.DoApiPost("/logs", MapToJson(message)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body), BuildResponse(r) + } +} + +// OAuth Section + +// CreateOAuthApp will register a new OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. +func (c *Client4) CreateOAuthApp(app *OAuthApp) (*OAuthApp, *Response) { + if r, err := c.DoApiPost(c.GetOAuthAppsRoute(), app.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateOAuthApp +func (c *Client4) UpdateOAuthApp(app *OAuthApp) (*OAuthApp, *Response) { + if r, err := c.DoApiPut(c.GetOAuthAppRoute(app.Id), app.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppFromJson(r.Body), BuildResponse(r) + } +} + +// GetOAuthApps gets a page of registered OAuth 2.0 client applications with Mattermost acting as an OAuth 2.0 service provider. +func (c *Client4) GetOAuthApps(page, perPage int) ([]*OAuthApp, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetOAuthAppsRoute()+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppListFromJson(r.Body), BuildResponse(r) + } +} + +// GetOAuthApp gets a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. +func (c *Client4) GetOAuthApp(appId string) (*OAuthApp, *Response) { + if r, err := c.DoApiGet(c.GetOAuthAppRoute(appId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppFromJson(r.Body), BuildResponse(r) + } +} + +// GetOAuthAppInfo gets a sanitized version of a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. +func (c *Client4) GetOAuthAppInfo(appId string) (*OAuthApp, *Response) { + if r, err := c.DoApiGet(c.GetOAuthAppRoute(appId)+"/info", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteOAuthApp deletes a registered OAuth 2.0 client application. +func (c *Client4) DeleteOAuthApp(appId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetOAuthAppRoute(appId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// RegenerateOAuthAppSecret regenerates the client secret for a registered OAuth 2.0 client application. +func (c *Client4) RegenerateOAuthAppSecret(appId string) (*OAuthApp, *Response) { + if r, err := c.DoApiPost(c.GetOAuthAppRoute(appId)+"/regen_secret", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppFromJson(r.Body), BuildResponse(r) + } +} + +// GetAuthorizedOAuthAppsForUser gets a page of OAuth 2.0 client applications the user has authorized to use access their account. +func (c *Client4) GetAuthorizedOAuthAppsForUser(userId string, page, perPage int) ([]*OAuthApp, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/oauth/apps/authorized"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return OAuthAppListFromJson(r.Body), BuildResponse(r) + } +} + +// AuthorizeOAuthApp will authorize an OAuth 2.0 client application to access a user's account and provide a redirect link to follow. +func (c *Client4) AuthorizeOAuthApp(authRequest *AuthorizeRequest) (string, *Response) { + if r, err := c.DoApiRequest(http.MethodPost, c.Url+"/oauth/authorize", authRequest.ToJson(), ""); err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body)["redirect"], BuildResponse(r) + } +} + +// DeauthorizeOAuthApp will deauthorize an OAuth 2.0 client application from accessing a user's account. +func (c *Client4) DeauthorizeOAuthApp(appId string) (bool, *Response) { + requestData := map[string]string{"client_id": appId} + if r, err := c.DoApiRequest(http.MethodPost, c.Url+"/oauth/deauthorize", MapToJson(requestData), ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetOAuthAccessToken is a test helper function for the OAuth access token endpoint. +func (c *Client4) GetOAuthAccessToken(data url.Values) (*AccessResponse, *Response) { + rq, _ := http.NewRequest(http.MethodPost, c.Url+"/oauth/access_token", strings.NewReader(data.Encode())) + rq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.Url+"/oauth/access_token", "model.client.connecting.app_error", nil, err.Error(), 403)} + } else { + defer closeBody(rp) + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return AccessResponseFromJson(rp.Body), BuildResponse(rp) + } + } +} + +// Elasticsearch Section + +// TestElasticsearch will attempt to connect to the configured Elasticsearch server and return OK if configured +// correctly. +func (c *Client4) TestElasticsearch() (bool, *Response) { + if r, err := c.DoApiPost(c.GetElasticsearchRoute()+"/test", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// PurgeElasticsearchIndexes immediately deletes all Elasticsearch indexes. +func (c *Client4) PurgeElasticsearchIndexes() (bool, *Response) { + if r, err := c.DoApiPost(c.GetElasticsearchRoute()+"/purge_indexes", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Data Retention Section + +// GetDataRetentionPolicy will get the current server data retention policy details. +func (c *Client4) GetDataRetentionPolicy() (*DataRetentionPolicy, *Response) { + if r, err := c.DoApiGet(c.GetDataRetentionRoute()+"/policy", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return DataRetentionPolicyFromJson(r.Body), BuildResponse(r) + } +} + +// Commands Section + +// CreateCommand will create a new command if the user have the right permissions. +func (c *Client4) CreateCommand(cmd *Command) (*Command, *Response) { + if r, err := c.DoApiPost(c.GetCommandsRoute(), cmd.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CommandFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateCommand updates a command based on the provided Command struct +func (c *Client4) UpdateCommand(cmd *Command) (*Command, *Response) { + if r, err := c.DoApiPut(c.GetCommandRoute(cmd.Id), cmd.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CommandFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteCommand deletes a command based on the provided command id string +func (c *Client4) DeleteCommand(commandId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetCommandRoute(commandId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// ListCommands will retrieve a list of commands available in the team. +func (c *Client4) ListCommands(teamId string, customOnly bool) ([]*Command, *Response) { + query := fmt.Sprintf("?team_id=%v&custom_only=%v", teamId, customOnly) + if r, err := c.DoApiGet(c.GetCommandsRoute()+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CommandListFromJson(r.Body), BuildResponse(r) + } +} + +// ExecuteCommand executes a given slash command. +func (c *Client4) ExecuteCommand(channelId, command string) (*CommandResponse, *Response) { + commandArgs := &CommandArgs{ + ChannelId: channelId, + Command: command, + } + if r, err := c.DoApiPost(c.GetCommandsRoute()+"/execute", commandArgs.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + response, _ := CommandResponseFromJson(r.Body) + return response, BuildResponse(r) + } +} + +// ExecuteCommand executes a given slash command against the specified team +// Use this when executing slash commands in a DM/GM, since the team id cannot be inferred in that case +func (c *Client4) ExecuteCommandWithTeam(channelId, teamId, command string) (*CommandResponse, *Response) { + commandArgs := &CommandArgs{ + ChannelId: channelId, + TeamId: teamId, + Command: command, + } + if r, err := c.DoApiPost(c.GetCommandsRoute()+"/execute", commandArgs.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + response, _ := CommandResponseFromJson(r.Body) + return response, BuildResponse(r) + } +} + +// ListCommands will retrieve a list of commands available in the team. +func (c *Client4) ListAutocompleteCommands(teamId string) ([]*Command, *Response) { + if r, err := c.DoApiGet(c.GetTeamAutoCompleteCommandsRoute(teamId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CommandListFromJson(r.Body), BuildResponse(r) + } +} + +// RegenCommandToken will create a new token if the user have the right permissions. +func (c *Client4) RegenCommandToken(commandId string) (string, *Response) { + if r, err := c.DoApiPut(c.GetCommandRoute(commandId)+"/regen_token", ""); err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body)["token"], BuildResponse(r) + } +} + +// Status Section + +// GetUserStatus returns a user based on the provided user id string. +func (c *Client4) GetUserStatus(userId, etag string) (*Status, *Response) { + if r, err := c.DoApiGet(c.GetUserStatusRoute(userId), etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return StatusFromJson(r.Body), BuildResponse(r) + } +} + +// GetUsersStatusesByIds returns a list of users status based on the provided user ids. +func (c *Client4) GetUsersStatusesByIds(userIds []string) ([]*Status, *Response) { + if r, err := c.DoApiPost(c.GetUserStatusesRoute()+"/ids", ArrayToJson(userIds)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return StatusListFromJson(r.Body), BuildResponse(r) + } +} + +// UpdateUserStatus sets a user's status based on the provided user id string. +func (c *Client4) UpdateUserStatus(userId string, userStatus *Status) (*Status, *Response) { + if r, err := c.DoApiPut(c.GetUserStatusRoute(userId), userStatus.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return StatusFromJson(r.Body), BuildResponse(r) + + } +} + +// Webrtc Section + +// GetWebrtcToken returns a valid token, stun server and turn server with credentials to +// use with the Mattermost WebRTC service. +func (c *Client4) GetWebrtcToken() (*WebrtcInfoResponse, *Response) { + if r, err := c.DoApiGet("/webrtc/token", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return WebrtcInfoResponseFromJson(r.Body), BuildResponse(r) + } +} + +// Emoji Section + +// CreateEmoji will save an emoji to the server if the current user has permission +// to do so. If successful, the provided emoji will be returned with its Id field +// filled in. Otherwise, an error will be returned. +func (c *Client4) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoji, *Response) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("image", filename); err != nil { + return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0)} + } else if _, err = io.Copy(part, bytes.NewBuffer(image)); err != nil { + return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0)} + } + + if err := writer.WriteField("emoji", emoji.ToJson()); err != nil { + return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.emoji.app_error", nil, err.Error(), 0)} + } + + if err := writer.Close(); err != nil { + return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.writer.app_error", nil, err.Error(), 0)} + } + + return c.DoEmojiUploadFile(c.GetEmojisRoute(), body.Bytes(), writer.FormDataContentType()) +} + +// GetEmojiList returns a page of custom emoji on the system. +func (c *Client4) GetEmojiList(page, perPage int) ([]*Emoji, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) + if r, err := c.DoApiGet(c.GetEmojisRoute()+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return EmojiListFromJson(r.Body), BuildResponse(r) + } +} + +// GetSortedEmojiList returns a page of custom emoji on the system sorted based on the sort +// parameter, blank for no sorting and "name" to sort by emoji names. +func (c *Client4) GetSortedEmojiList(page, perPage int, sort string) ([]*Emoji, *Response) { + query := fmt.Sprintf("?page=%v&per_page=%v&sort=%v", page, perPage, sort) + if r, err := c.DoApiGet(c.GetEmojisRoute()+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return EmojiListFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteEmoji delete an custom emoji on the provided emoji id string. +func (c *Client4) DeleteEmoji(emojiId string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetEmojiRoute(emojiId)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetEmoji returns a custom emoji based on the emojiId string. +func (c *Client4) GetEmoji(emojiId string) (*Emoji, *Response) { + if r, err := c.DoApiGet(c.GetEmojiRoute(emojiId), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return EmojiFromJson(r.Body), BuildResponse(r) + } +} + +// GetEmojiByName returns a custom emoji based on the name string. +func (c *Client4) GetEmojiByName(name string) (*Emoji, *Response) { + if r, err := c.DoApiGet(c.GetEmojiByNameRoute(name), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return EmojiFromJson(r.Body), BuildResponse(r) + } +} + +// GetEmojiImage returns the emoji image. +func (c *Client4) GetEmojiImage(emojiId string) ([]byte, *Response) { + if r, err := c.DoApiGet(c.GetEmojiRoute(emojiId)+"/image", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + + if data, err := ioutil.ReadAll(r.Body); err != nil { + return nil, BuildErrorResponse(r, NewAppError("GetEmojiImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) + } else { + return data, BuildResponse(r) + } + } +} + +// SearchEmoji returns a list of emoji matching some search criteria. +func (c *Client4) SearchEmoji(search *EmojiSearch) ([]*Emoji, *Response) { + if r, err := c.DoApiPost(c.GetEmojisRoute()+"/search", search.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return EmojiListFromJson(r.Body), BuildResponse(r) + } +} + +// AutocompleteEmoji returns a list of emoji starting with or matching name. +func (c *Client4) AutocompleteEmoji(name string, etag string) ([]*Emoji, *Response) { + query := fmt.Sprintf("?name=%v", name) + if r, err := c.DoApiGet(c.GetEmojisRoute()+"/autocomplete"+query, ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return EmojiListFromJson(r.Body), BuildResponse(r) + } +} + +// Reaction Section + +// SaveReaction saves an emoji reaction for a post. Returns the saved reaction if successful, otherwise an error will be returned. +func (c *Client4) SaveReaction(reaction *Reaction) (*Reaction, *Response) { + if r, err := c.DoApiPost(c.GetReactionsRoute(), reaction.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ReactionFromJson(r.Body), BuildResponse(r) + } +} + +// GetReactions returns a list of reactions to a post. +func (c *Client4) GetReactions(postId string) ([]*Reaction, *Response) { + if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/reactions", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ReactionsFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteReaction deletes reaction of a user in a post. +func (c *Client4) DeleteReaction(reaction *Reaction) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetUserRoute(reaction.UserId) + c.GetPostRoute(reaction.PostId) + fmt.Sprintf("/reactions/%v", reaction.EmojiName)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Timezone Section + +// GetSupportedTimezone returns a page of supported timezones on the system. +func (c *Client4) GetSupportedTimezone() (SupportedTimezones, *Response) { + if r, err := c.DoApiGet(c.GetTimezonesRoute(), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TimezonesFromJson(r.Body), BuildResponse(r) + } +} + +// Open Graph Metadata Section + +// OpenGraph return the open graph metadata for a particular url if the site have the metadata +func (c *Client4) OpenGraph(url string) (map[string]string, *Response) { + requestBody := make(map[string]string) + requestBody["url"] = url + + if r, err := c.DoApiPost(c.GetOpenGraphRoute(), MapToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body), BuildResponse(r) + } +} + +// Jobs Section + +// GetJob gets a single job. +func (c *Client4) GetJob(id string) (*Job, *Response) { + if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("/%v", id), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return JobFromJson(r.Body), BuildResponse(r) + } +} + +// Get all jobs, sorted with the job that was created most recently first. +func (c *Client4) GetJobs(page int, perPage int) ([]*Job, *Response) { + if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("?page=%v&per_page=%v", page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return JobsFromJson(r.Body), BuildResponse(r) + } +} + +// GetJobsByType gets all jobs of a given type, sorted with the job that was created most recently first. +func (c *Client4) GetJobsByType(jobType string, page int, perPage int) ([]*Job, *Response) { + if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("/type/%v?page=%v&per_page=%v", jobType, page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return JobsFromJson(r.Body), BuildResponse(r) + } +} + +// CreateJob creates a job based on the provided job struct. +func (c *Client4) CreateJob(job *Job) (*Job, *Response) { + if r, err := c.DoApiPost(c.GetJobsRoute(), job.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return JobFromJson(r.Body), BuildResponse(r) + } +} + +// CancelJob requests the cancellation of the job with the provided Id. +func (c *Client4) CancelJob(jobId string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetJobsRoute()+fmt.Sprintf("/%v/cancel", jobId), ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// Roles Section + +// GetRole gets a single role by ID. +func (c *Client4) GetRole(id string) (*Role, *Response) { + if r, err := c.DoApiGet(c.GetRolesRoute()+fmt.Sprintf("/%v", id), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleFromJson(r.Body), BuildResponse(r) + } +} + +// GetRoleByName gets a single role by Name. +func (c *Client4) GetRoleByName(name string) (*Role, *Response) { + if r, err := c.DoApiGet(c.GetRolesRoute()+fmt.Sprintf("/name/%v", name), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleFromJson(r.Body), BuildResponse(r) + } +} + +// GetRolesByNames returns a list of roles based on the provided role names. +func (c *Client4) GetRolesByNames(roleNames []string) ([]*Role, *Response) { + if r, err := c.DoApiPost(c.GetRolesRoute()+"/names", ArrayToJson(roleNames)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleListFromJson(r.Body), BuildResponse(r) + } +} + +// PatchRole partially updates a role in the system. Any missing fields are not updated. +func (c *Client4) PatchRole(roleId string, patch *RolePatch) (*Role, *Response) { + if r, err := c.DoApiPut(c.GetRolesRoute()+fmt.Sprintf("/%v/patch", roleId), patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return RoleFromJson(r.Body), BuildResponse(r) + } +} + +// Schemes Section + +// CreateScheme creates a new Scheme. +func (c *Client4) CreateScheme(scheme *Scheme) (*Scheme, *Response) { + if r, err := c.DoApiPost(c.GetSchemesRoute(), scheme.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemeFromJson(r.Body), BuildResponse(r) + } +} + +// GetScheme gets a single scheme by ID. +func (c *Client4) GetScheme(id string) (*Scheme, *Response) { + if r, err := c.DoApiGet(c.GetSchemeRoute(id), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemeFromJson(r.Body), BuildResponse(r) + } +} + +// Get all schemes, sorted with the most recently created first, optionally filtered by scope. +func (c *Client4) GetSchemes(scope string, page int, perPage int) ([]*Scheme, *Response) { + if r, err := c.DoApiGet(c.GetSchemesRoute()+fmt.Sprintf("?scope=%v&page=%v&per_page=%v", scope, page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemesFromJson(r.Body), BuildResponse(r) + } +} + +// DeleteScheme deletes a single scheme by ID. +func (c *Client4) DeleteScheme(id string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetSchemeRoute(id)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// PatchScheme partially updates a scheme in the system. Any missing fields are not updated. +func (c *Client4) PatchScheme(id string, patch *SchemePatch) (*Scheme, *Response) { + if r, err := c.DoApiPut(c.GetSchemeRoute(id)+"/patch", patch.ToJson()); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return SchemeFromJson(r.Body), BuildResponse(r) + } +} + +// Get the teams using this scheme, sorted alphabetically by display name. +func (c *Client4) GetTeamsForScheme(schemeId string, page int, perPage int) ([]*Team, *Response) { + if r, err := c.DoApiGet(c.GetSchemeRoute(schemeId)+fmt.Sprintf("/teams?page=%v&per_page=%v", page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TeamListFromJson(r.Body), BuildResponse(r) + } +} + +// Get the channels using this scheme, sorted alphabetically by display name. +func (c *Client4) GetChannelsForScheme(schemeId string, page int, perPage int) (ChannelList, *Response) { + if r, err := c.DoApiGet(c.GetSchemeRoute(schemeId)+fmt.Sprintf("/channels?page=%v&per_page=%v", page, perPage), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return *ChannelListFromJson(r.Body), BuildResponse(r) + } +} + +// Plugin Section + +// UploadPlugin takes an io.Reader stream pointing to the contents of a .tar.gz plugin. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) UploadPlugin(file io.Reader) (*Manifest, *Response) { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + + if part, err := writer.CreateFormFile("plugin", "plugin.tar.gz"); err != nil { + return nil, &Response{Error: NewAppError("UploadPlugin", "model.client.writer.app_error", nil, err.Error(), 0)} + } else if _, err = io.Copy(part, file); err != nil { + return nil, &Response{Error: NewAppError("UploadPlugin", "model.client.writer.app_error", nil, err.Error(), 0)} + } + + if err := writer.Close(); err != nil { + return nil, &Response{Error: NewAppError("UploadPlugin", "model.client.writer.app_error", nil, err.Error(), 0)} + } + + rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetPluginsRoute(), body) + rq.Header.Set("Content-Type", writer.FormDataContentType()) + + if len(c.AuthToken) > 0 { + rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) + } + + if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { + return nil, BuildErrorResponse(rp, NewAppError("UploadPlugin", "model.client.connecting.app_error", nil, err.Error(), 0)) + } else { + defer closeBody(rp) + + if rp.StatusCode >= 300 { + return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) + } else { + return ManifestFromJson(rp.Body), BuildResponse(rp) + } + } +} + +// GetPlugins will return a list of plugin manifests for currently active plugins. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) GetPlugins() (*PluginsResponse, *Response) { + if r, err := c.DoApiGet(c.GetPluginsRoute(), ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PluginsResponseFromJson(r.Body), BuildResponse(r) + } +} + +// GetPluginStatuses will return the plugins installed on any server in the cluster, for reporting +// to the administrator via the system console. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) GetPluginStatuses() (PluginStatuses, *Response) { + if r, err := c.DoApiGet(c.GetPluginsRoute(), "/statuses"); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PluginStatusesFromJson(r.Body), BuildResponse(r) + } +} + +// RemovePlugin will deactivate and delete a plugin. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) RemovePlugin(id string) (bool, *Response) { + if r, err := c.DoApiDelete(c.GetPluginRoute(id)); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetWebappPlugins will return a list of plugins that the webapp should download. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) GetWebappPlugins() ([]*Manifest, *Response) { + if r, err := c.DoApiGet(c.GetPluginsRoute()+"/webapp", ""); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ManifestListFromJson(r.Body), BuildResponse(r) + } +} + +// ActivatePlugin will activate an plugin installed. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) EnablePlugin(id string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/enable", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// DeactivatePlugin will deactivate an active plugin. +// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. +func (c *Client4) DisablePlugin(id string) (bool, *Response) { + if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/disable", ""); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateChannelScheme will update a channel's scheme. +func (c *Client4) UpdateChannelScheme(channelId, schemeId string) (bool, *Response) { + sip := &SchemeIDPatch{SchemeID: &schemeId} + if r, err := c.DoApiPut(c.GetChannelSchemeRoute(channelId), sip.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// UpdateTeamScheme will update a team's scheme. +func (c *Client4) UpdateTeamScheme(teamId, schemeId string) (bool, *Response) { + sip := &SchemeIDPatch{SchemeID: &schemeId} + if r, err := c.DoApiPut(c.GetTeamSchemeRoute(teamId), sip.ToJson()); err != nil { + return false, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + +// GetRedirectLocation retrieves the value of the 'Location' header of an HTTP response for a given URL. +func (c *Client4) GetRedirectLocation(urlParam, etag string) (string, *Response) { + url := fmt.Sprintf("%s?url=%s", c.GetRedirectLocationRoute(), url.QueryEscape(urlParam)) + if r, err := c.DoApiGet(url, etag); err != nil { + return "", BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return MapFromJson(r.Body)["location"], BuildResponse(r) + } +} + +func (c *Client4) RegisteTermsOfServiceAction(userId, termsOfServiceId string, accepted bool) (*bool, *Response) { + url := c.GetRegisterTermsOfServiceRoute(userId) + data := map[string]interface{}{"termsOfServiceId": termsOfServiceId, "accepted": accepted} + + if r, err := c.DoApiPost(url, StringInterfaceToJson(data)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return NewBool(CheckStatusOK(r)), BuildResponse(r) + } +} + +func (c *Client4) GetTermsOfService(etag string) (*TermsOfService, *Response) { + url := c.GetTermsOfServiceRoute() + + if r, err := c.DoApiGet(url, etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TermsOfServiceFromJson(r.Body), BuildResponse(r) + } +} + +func (c *Client4) CreateTermsOfService(text, userId string) (*TermsOfService, *Response) { + url := c.GetTermsOfServiceRoute() + + data := map[string]string{"text": text} + if r, err := c.DoApiPost(url, MapToJson(data)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return TermsOfServiceFromJson(r.Body), BuildResponse(r) + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/cluster_discovery.go b/vendor/github.com/mattermost/mattermost-server/model/cluster_discovery.go new file mode 100644 index 00000000..796d3dda --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/cluster_discovery.go @@ -0,0 +1,133 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "os" +) + +const ( + CDS_OFFLINE_AFTER_MILLIS = 1000 * 60 * 30 // 30 minutes + CDS_TYPE_APP = "mattermost_app" +) + +type ClusterDiscovery struct { + Id string `json:"id"` + Type string `json:"type"` + ClusterName string `json:"cluster_name"` + Hostname string `json:"hostname"` + GossipPort int32 `json:"gossip_port"` + Port int32 `json:"port"` + CreateAt int64 `json:"create_at"` + LastPingAt int64 `json:"last_ping_at"` +} + +func (o *ClusterDiscovery) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + o.LastPingAt = o.CreateAt + } +} + +func (o *ClusterDiscovery) AutoFillHostname() { + // attempt to set the hostname from the OS + if len(o.Hostname) == 0 { + if hn, err := os.Hostname(); err == nil { + o.Hostname = hn + } + } +} + +func (o *ClusterDiscovery) AutoFillIpAddress() { + // attempt to set the hostname to the first non-local IP address + if len(o.Hostname) == 0 { + o.Hostname = GetServerIpAddress() + } +} + +func (o *ClusterDiscovery) IsEqual(in *ClusterDiscovery) bool { + if in == nil { + return false + } + + if o.Type != in.Type { + return false + } + + if o.ClusterName != in.ClusterName { + return false + } + + if o.Hostname != in.Hostname { + return false + } + + return true +} + +func FilterClusterDiscovery(vs []*ClusterDiscovery, f func(*ClusterDiscovery) bool) []*ClusterDiscovery { + copy := make([]*ClusterDiscovery, 0) + for _, v := range vs { + if f(v) { + copy = append(copy, v) + } + } + + return copy +} + +func (o *ClusterDiscovery) IsValid() *AppError { + if len(o.Id) != 26 { + return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ClusterName) == 0 { + return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.name.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Type) == 0 { + return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.type.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Hostname) == 0 { + return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.hostname.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) + } + + if o.LastPingAt == 0 { + return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.last_ping_at.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (o *ClusterDiscovery) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } + + return string(b) +} + +func ClusterDiscoveryFromJson(data io.Reader) *ClusterDiscovery { + decoder := json.NewDecoder(data) + var me ClusterDiscovery + err := decoder.Decode(&me) + if err == nil { + return &me + } + + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/cluster_info.go b/vendor/github.com/mattermost/mattermost-server/model/cluster_info.go new file mode 100644 index 00000000..46a3487a --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/cluster_info.go @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ClusterInfo struct { + Id string `json:"id"` + Version string `json:"version"` + ConfigHash string `json:"config_hash"` + IpAddress string `json:"ipaddress"` + Hostname string `json:"hostname"` +} + +func (me *ClusterInfo) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func ClusterInfoFromJson(data io.Reader) *ClusterInfo { + var me *ClusterInfo + json.NewDecoder(data).Decode(&me) + return me +} + +func ClusterInfosToJson(objmap []*ClusterInfo) string { + b, _ := json.Marshal(objmap) + return string(b) +} + +func ClusterInfosFromJson(data io.Reader) []*ClusterInfo { + decoder := json.NewDecoder(data) + + var objmap []*ClusterInfo + if err := decoder.Decode(&objmap); err != nil { + return make([]*ClusterInfo, 0) + } else { + return objmap + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/cluster_message.go b/vendor/github.com/mattermost/mattermost-server/model/cluster_message.go new file mode 100644 index 00000000..d02da3ee --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/cluster_message.go @@ -0,0 +1,48 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + CLUSTER_EVENT_PUBLISH = "publish" + CLUSTER_EVENT_UPDATE_STATUS = "update_status" + CLUSTER_EVENT_INVALIDATE_ALL_CACHES = "inv_all_caches" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS = "inv_reactions" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOK = "inv_webhook" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS = "inv_channel_posts" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS = "inv_channel_members_notify_props" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS = "inv_channel_members" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME = "inv_channel_name" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER = "inv_user" + CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles" + CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes" + + CLUSTER_SEND_BEST_EFFORT = "best_effort" + CLUSTER_SEND_RELIABLE = "reliable" +) + +type ClusterMessage struct { + Event string `json:"event"` + SendType string `json:"-"` + WaitForAllToSend bool `json:"-"` + Data string `json:"data,omitempty"` + Props map[string]string `json:"props,omitempty"` +} + +func (o *ClusterMessage) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ClusterMessageFromJson(data io.Reader) *ClusterMessage { + var o *ClusterMessage + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/cluster_stats.go b/vendor/github.com/mattermost/mattermost-server/model/cluster_stats.go new file mode 100644 index 00000000..064f7b81 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/cluster_stats.go @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type ClusterStats struct { + Id string `json:"id"` + TotalWebsocketConnections int `json:"total_websocket_connections"` + TotalReadDbConnections int `json:"total_read_db_connections"` + TotalMasterDbConnections int `json:"total_master_db_connections"` +} + +func (me *ClusterStats) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func ClusterStatsFromJson(data io.Reader) *ClusterStats { + var me *ClusterStats + json.NewDecoder(data).Decode(&me) + return me +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/command.go b/vendor/github.com/mattermost/mattermost-server/model/command.go new file mode 100644 index 00000000..b23e5020 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/command.go @@ -0,0 +1,139 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "strings" +) + +const ( + COMMAND_METHOD_POST = "P" + COMMAND_METHOD_GET = "G" + MIN_TRIGGER_LENGTH = 1 + MAX_TRIGGER_LENGTH = 128 +) + +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, _ := json.Marshal(o) + return string(b) +} + +func CommandFromJson(data io.Reader) *Command { + var o *Command + json.NewDecoder(data).Decode(&o) + return o +} + +func CommandListToJson(l []*Command) string { + b, _ := json.Marshal(l) + return string(b) +} + +func CommandListFromJson(data io.Reader) []*Command { + var o []*Command + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *Command) IsValid() *AppError { + + if len(o.Id) != 26 { + return NewAppError("Command.IsValid", "model.command.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Token) != 26 { + return NewAppError("Command.IsValid", "model.command.is_valid.token.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("Command.IsValid", "model.command.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("Command.IsValid", "model.command.is_valid.update_at.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.CreatorId) != 26 { + return NewAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.TeamId) != 26 { + return NewAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Trigger) < MIN_TRIGGER_LENGTH || len(o.Trigger) > MAX_TRIGGER_LENGTH || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") { + return NewAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.URL) == 0 || len(o.URL) > 1024 { + return NewAppError("Command.IsValid", "model.command.is_valid.url.app_error", nil, "", http.StatusBadRequest) + } + + if !IsValidHttpUrl(o.URL) { + return NewAppError("Command.IsValid", "model.command.is_valid.url_http.app_error", nil, "", http.StatusBadRequest) + } + + if !(o.Method == COMMAND_METHOD_GET || o.Method == COMMAND_METHOD_POST) { + return NewAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.DisplayName) > 64 { + return NewAppError("Command.IsValid", "model.command.is_valid.display_name.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Description) > 128 { + return NewAppError("Command.IsValid", "model.command.is_valid.description.app_error", nil, "", http.StatusBadRequest) + } + + 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/mattermost-server/model/command_args.go b/vendor/github.com/mattermost/mattermost-server/model/command_args.go new file mode 100644 index 00000000..4a635a1a --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/command_args.go @@ -0,0 +1,34 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type CommandArgs struct { + UserId string `json:"user_id"` + ChannelId string `json:"channel_id"` + TeamId string `json:"team_id"` + RootId string `json:"root_id"` + ParentId string `json:"parent_id"` + Command string `json:"command"` + SiteURL string `json:"-"` + T goi18n.TranslateFunc `json:"-"` + Session Session `json:"-"` +} + +func (o *CommandArgs) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func CommandArgsFromJson(data io.Reader) *CommandArgs { + var o *CommandArgs + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/command_response.go b/vendor/github.com/mattermost/mattermost-server/model/command_response.go new file mode 100644 index 00000000..1ed5286d --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/command_response.go @@ -0,0 +1,67 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "io/ioutil" + "strings" + + "github.com/mattermost/mattermost-server/utils/jsonutils" +) + +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"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + Type string `json:"type"` + Props StringInterface `json:"props"` + GotoLocation string `json:"goto_location"` + Attachments []*SlackAttachment `json:"attachments"` +} + +func (o *CommandResponse) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func CommandResponseFromHTTPBody(contentType string, body io.Reader) (*CommandResponse, error) { + if strings.TrimSpace(strings.Split(contentType, ";")[0]) == "application/json" { + return CommandResponseFromJson(body) + } + if b, err := ioutil.ReadAll(body); err == nil { + return CommandResponseFromPlainText(string(b)), nil + } + return nil, nil +} + +func CommandResponseFromPlainText(text string) *CommandResponse { + return &CommandResponse{ + Text: text, + } +} + +func CommandResponseFromJson(data io.Reader) (*CommandResponse, error) { + b, err := ioutil.ReadAll(data) + if err != nil { + return nil, err + } + + var o CommandResponse + err = json.Unmarshal(b, &o) + if err != nil { + return nil, jsonutils.HumanizeJsonError(err, b) + } + + o.Attachments = StringifySlackFieldValue(o.Attachments) + + return &o, nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/command_webhook.go b/vendor/github.com/mattermost/mattermost-server/model/command_webhook.go new file mode 100644 index 00000000..0b00e00b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/command_webhook.go @@ -0,0 +1,65 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "net/http" +) + +type CommandWebhook struct { + Id string + CreateAt int64 + CommandId string + UserId string + ChannelId string + RootId string + ParentId string + UseCount int +} + +const ( + COMMAND_WEBHOOK_LIFETIME = 1000 * 60 * 30 +) + +func (o *CommandWebhook) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + } +} + +func (o *CommandWebhook) IsValid() *AppError { + if len(o.Id) != 26 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.id.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.CommandId) != 26 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.command_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.UserId) != 26 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ChannelId) != 26 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.channel_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.RootId) != 0 && len(o.RootId) != 26 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.root_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ParentId) != 0 && len(o.ParentId) != 26 { + return NewAppError("CommandWebhook.IsValid", "model.command_hook.parent_id.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/compliance.go b/vendor/github.com/mattermost/mattermost-server/model/compliance.go new file mode 100644 index 00000000..5546b783 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/compliance.go @@ -0,0 +1,119 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "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, _ := json.Marshal(o) + 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 = NormalizeEmail(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 NewAppError("Compliance.IsValid", "model.compliance.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if me.CreateAt == 0 { + return NewAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) + } + + if len(me.Desc) > 512 || len(me.Desc) == 0 { + return NewAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "", http.StatusBadRequest) + } + + if me.StartAt == 0 { + return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_at.app_error", nil, "", http.StatusBadRequest) + } + + if me.EndAt == 0 { + return NewAppError("Compliance.IsValid", "model.compliance.is_valid.end_at.app_error", nil, "", http.StatusBadRequest) + } + + if me.EndAt <= me.StartAt { + return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_end_at.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func ComplianceFromJson(data io.Reader) *Compliance { + var o *Compliance + json.NewDecoder(data).Decode(&o) + return o +} + +func (o Compliances) ToJson() string { + if b, err := json.Marshal(o); err != nil { + return "[]" + } else { + return string(b) + } +} + +func CompliancesFromJson(data io.Reader) Compliances { + var o Compliances + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/compliance_post.go b/vendor/github.com/mattermost/mattermost-server/model/compliance_post.go new file mode 100644 index 00000000..75e8de1f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/compliance_post.go @@ -0,0 +1,117 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "regexp" + "time" +) + +type CompliancePost struct { + + // From Team + TeamName string + TeamDisplayName string + + // From Channel + ChannelName string + ChannelDisplayName string + ChannelType 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 + PostFileIds string +} + +func CompliancePostHeader() []string { + return []string{ + "TeamName", + "TeamDisplayName", + + "ChannelName", + "ChannelDisplayName", + "ChannelType", + + "UserUsername", + "UserEmail", + "UserNickname", + + "PostId", + "PostCreateAt", + "PostUpdateAt", + "PostDeleteAt", + "PostRootId", + "PostParentId", + "PostOriginalId", + "PostMessage", + "PostType", + "PostProps", + "PostHashtags", + "PostFileIds", + } +} + +func cleanComplianceStrings(in string) string { + if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched { + return "'" + in + + } else { + return in + } +} + +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{ + cleanComplianceStrings(me.TeamName), + cleanComplianceStrings(me.TeamDisplayName), + + cleanComplianceStrings(me.ChannelName), + cleanComplianceStrings(me.ChannelDisplayName), + cleanComplianceStrings(me.ChannelType), + + cleanComplianceStrings(me.UserUsername), + cleanComplianceStrings(me.UserEmail), + cleanComplianceStrings(me.UserNickname), + + me.PostId, + time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339), + postUpdateAt, + postDeleteAt, + + me.PostRootId, + me.PostParentId, + me.PostOriginalId, + cleanComplianceStrings(me.PostMessage), + me.PostType, + me.PostProps, + me.PostHashtags, + me.PostFileIds, + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/config.go b/vendor/github.com/mattermost/mattermost-server/model/config.go new file mode 100644 index 00000000..d59b8d6d --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/config.go @@ -0,0 +1,2569 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "math" + "net" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +const ( + CONN_SECURITY_NONE = "" + CONN_SECURITY_PLAIN = "PLAIN" + CONN_SECURITY_TLS = "TLS" + CONN_SECURITY_STARTTLS = "STARTTLS" + + IMAGE_DRIVER_LOCAL = "local" + IMAGE_DRIVER_S3 = "amazons3" + + DATABASE_DRIVER_SQLITE = "sqlite3" + DATABASE_DRIVER_MYSQL = "mysql" + DATABASE_DRIVER_POSTGRES = "postgres" + + MINIO_ACCESS_KEY = "minioaccesskey" + MINIO_SECRET_KEY = "miniosecretkey" + MINIO_BUCKET = "mattermost-test" + + PASSWORD_MAXIMUM_LENGTH = 64 + PASSWORD_MINIMUM_LENGTH = 5 + + SERVICE_GITLAB = "gitlab" + SERVICE_GOOGLE = "google" + SERVICE_OFFICE365 = "office365" + + GENERIC_NO_CHANNEL_NOTIFICATION = "generic_no_channel" + GENERIC_NOTIFICATION = "generic" + FULL_NOTIFICATION = "full" + + DIRECT_MESSAGE_ANY = "any" + DIRECT_MESSAGE_TEAM = "team" + + SHOW_USERNAME = "username" + SHOW_NICKNAME_FULLNAME = "nickname_full_name" + SHOW_FULLNAME = "full_name" + + PERMISSIONS_ALL = "all" + PERMISSIONS_CHANNEL_ADMIN = "channel_admin" + PERMISSIONS_TEAM_ADMIN = "team_admin" + PERMISSIONS_SYSTEM_ADMIN = "system_admin" + + FAKE_SETTING = "********************************" + + RESTRICT_EMOJI_CREATION_ALL = "all" + RESTRICT_EMOJI_CREATION_ADMIN = "admin" + RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" + + PERMISSIONS_DELETE_POST_ALL = "all" + PERMISSIONS_DELETE_POST_TEAM_ADMIN = "team_admin" + PERMISSIONS_DELETE_POST_SYSTEM_ADMIN = "system_admin" + + ALLOW_EDIT_POST_ALWAYS = "always" + ALLOW_EDIT_POST_NEVER = "never" + ALLOW_EDIT_POST_TIME_LIMIT = "time_limit" + + GROUP_UNREAD_CHANNELS_DISABLED = "disabled" + GROUP_UNREAD_CHANNELS_DEFAULT_ON = "default_on" + GROUP_UNREAD_CHANNELS_DEFAULT_OFF = "default_off" + + EMAIL_BATCHING_BUFFER_SIZE = 256 + EMAIL_BATCHING_INTERVAL = 30 + + EMAIL_NOTIFICATION_CONTENTS_FULL = "full" + EMAIL_NOTIFICATION_CONTENTS_GENERIC = "generic" + + SITENAME_MAX_LENGTH = 30 + + SERVICE_SETTINGS_DEFAULT_SITE_URL = "" + SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE = "" + SERVICE_SETTINGS_DEFAULT_TLS_KEY_FILE = "" + SERVICE_SETTINGS_DEFAULT_READ_TIMEOUT = 300 + SERVICE_SETTINGS_DEFAULT_WRITE_TIMEOUT = 300 + SERVICE_SETTINGS_DEFAULT_MAX_LOGIN_ATTEMPTS = 10 + SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM = "" + SERVICE_SETTINGS_DEFAULT_LISTEN_AND_ADDRESS = ":8065" + SERVICE_SETTINGS_DEFAULT_GFYCAT_API_KEY = "2_KtH_W5" + SERVICE_SETTINGS_DEFAULT_GFYCAT_API_SECRET = "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof" + + TEAM_SETTINGS_DEFAULT_MAX_USERS_PER_TEAM = 50 + TEAM_SETTINGS_DEFAULT_CUSTOM_BRAND_TEXT = "" + TEAM_SETTINGS_DEFAULT_CUSTOM_DESCRIPTION_TEXT = "" + TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT = 300 + + SQL_SETTINGS_DEFAULT_DATA_SOURCE = "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s" + + FILE_SETTINGS_DEFAULT_DIRECTORY = "./data/" + + EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION = "" + + SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/" + SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/" + SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/" + SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/" + SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/" + SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com" + + LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = "" + LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME = "" + + SAML_SETTINGS_DEFAULT_ID_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE = "" + SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = "" + + NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://about.mattermost.com/downloads/" + NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-android-app/" + NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-ios-app/" + + WEBRTC_SETTINGS_DEFAULT_STUN_URI = "" + WEBRTC_SETTINGS_DEFAULT_TURN_URI = "" + + ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS = 2500 + + ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR = "#f2a93b" + ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR = "#333333" + + TEAM_SETTINGS_DEFAULT_TEAM_TEXT = "default" + + ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL = "" + ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME = "" + ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD = "" + ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS = 1 + ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS = 1 + ELASTICSEARCH_SETTINGS_DEFAULT_AGGREGATE_POSTS_AFTER_DAYS = 365 + ELASTICSEARCH_SETTINGS_DEFAULT_POSTS_AGGREGATOR_JOB_START_TIME = "03:00" + ELASTICSEARCH_SETTINGS_DEFAULT_INDEX_PREFIX = "" + ELASTICSEARCH_SETTINGS_DEFAULT_LIVE_INDEXING_BATCH_SIZE = 1 + ELASTICSEARCH_SETTINGS_DEFAULT_BULK_INDEXING_TIME_WINDOW_SECONDS = 3600 + ELASTICSEARCH_SETTINGS_DEFAULT_REQUEST_TIMEOUT_SECONDS = 30 + + DATA_RETENTION_SETTINGS_DEFAULT_MESSAGE_RETENTION_DAYS = 365 + DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS = 365 + DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME = "02:00" + + PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins" + PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins" + + TIMEZONE_SETTINGS_DEFAULT_SUPPORTED_TIMEZONES_PATH = "timezones.json" + + COMPLIANCE_EXPORT_TYPE_CSV = "csv" + COMPLIANCE_EXPORT_TYPE_ACTIANCE = "actiance" + COMPLIANCE_EXPORT_TYPE_GLOBALRELAY = "globalrelay" + GLOBALRELAY_CUSTOMER_TYPE_A9 = "A9" + GLOBALRELAY_CUSTOMER_TYPE_A10 = "A10" + + CLIENT_SIDE_CERT_CHECK_PRIMARY_AUTH = "primary" + CLIENT_SIDE_CERT_CHECK_SECONDARY_AUTH = "secondary" +) + +type ServiceSettings struct { + SiteURL *string + WebsocketURL *string + LicenseFileLocation *string + ListenAddress *string + ConnectionSecurity *string + TLSCertFile *string + TLSKeyFile *string + UseLetsEncrypt *bool + LetsEncryptCertificateCacheFile *string + Forward80To443 *bool + ReadTimeout *int + WriteTimeout *int + MaximumLoginAttempts *int + GoroutineHealthThreshold *int + GoogleDeveloperKey string + EnableOAuthServiceProvider bool + EnableIncomingWebhooks bool + EnableOutgoingWebhooks bool + EnableCommands *bool + EnableOnlyAdminIntegrations *bool + EnablePostUsernameOverride bool + EnablePostIconOverride bool + EnableLinkPreviews *bool + EnableTesting bool + EnableDeveloper *bool + EnableSecurityFixAlert *bool + EnableInsecureOutgoingConnections *bool + AllowedUntrustedInternalConnections *string + EnableMultifactorAuthentication *bool + EnforceMultifactorAuthentication *bool + EnableUserAccessTokens *bool + AllowCorsFrom *string + CorsExposedHeaders *string + CorsAllowCredentials *bool + CorsDebug *bool + AllowCookiesForSubdomains *bool + SessionLengthWebInDays *int + SessionLengthMobileInDays *int + SessionLengthSSOInDays *int + SessionCacheInMinutes *int + SessionIdleTimeoutInMinutes *int + WebsocketSecurePort *int + WebsocketPort *int + WebserverMode *string + EnableCustomEmoji *bool + EnableEmojiPicker *bool + EnableGifPicker *bool + GfycatApiKey *string + GfycatApiSecret *string + RestrictCustomEmojiCreation *string + RestrictPostDelete *string + AllowEditPost *string + PostEditTimeLimit *int + TimeBetweenUserTypingUpdatesMilliseconds *int64 + EnablePostSearch *bool + EnableUserTypingMessages *bool + EnableChannelViewedMessages *bool + EnableUserStatuses *bool + ExperimentalEnableAuthenticationTransfer *bool + ClusterLogTimeoutMilliseconds *int + CloseUnusedDirectMessages *bool + EnablePreviewFeatures *bool + EnableTutorial *bool + ExperimentalEnableDefaultChannelLeaveJoinMessages *bool + ExperimentalGroupUnreadChannels *string + ExperimentalChannelOrganization *bool + ImageProxyType *string + ImageProxyURL *string + ImageProxyOptions *string + EnableAPITeamDeletion *bool + ExperimentalEnableHardenedMode *bool + ExperimentalLimitClientConfig *bool + EnableEmailInvitations *bool +} + +func (s *ServiceSettings) SetDefaults() { + if s.EnableEmailInvitations == nil { + // If the site URL is also not present then assume this is a clean install + if s.SiteURL == nil { + s.EnableEmailInvitations = NewBool(false) + } else { + s.EnableEmailInvitations = NewBool(true) + } + } + + if s.SiteURL == nil { + s.SiteURL = NewString(SERVICE_SETTINGS_DEFAULT_SITE_URL) + } + + if s.WebsocketURL == nil { + s.WebsocketURL = NewString("") + } + + if s.LicenseFileLocation == nil { + s.LicenseFileLocation = NewString("") + } + + if s.ListenAddress == nil { + s.ListenAddress = NewString(SERVICE_SETTINGS_DEFAULT_LISTEN_AND_ADDRESS) + } + + if s.EnableLinkPreviews == nil { + s.EnableLinkPreviews = NewBool(false) + } + + if s.EnableDeveloper == nil { + s.EnableDeveloper = NewBool(false) + } + + if s.EnableSecurityFixAlert == nil { + s.EnableSecurityFixAlert = NewBool(true) + } + + if s.EnableInsecureOutgoingConnections == nil { + s.EnableInsecureOutgoingConnections = NewBool(false) + } + + if s.AllowedUntrustedInternalConnections == nil { + s.AllowedUntrustedInternalConnections = NewString("") + } + + if s.EnableMultifactorAuthentication == nil { + s.EnableMultifactorAuthentication = NewBool(false) + } + + if s.EnforceMultifactorAuthentication == nil { + s.EnforceMultifactorAuthentication = NewBool(false) + } + + if s.EnableUserAccessTokens == nil { + s.EnableUserAccessTokens = NewBool(false) + } + + if s.GoroutineHealthThreshold == nil { + s.GoroutineHealthThreshold = NewInt(-1) + } + + if s.ConnectionSecurity == nil { + s.ConnectionSecurity = NewString("") + } + + if s.TLSKeyFile == nil { + s.TLSKeyFile = NewString(SERVICE_SETTINGS_DEFAULT_TLS_KEY_FILE) + } + + if s.TLSCertFile == nil { + s.TLSCertFile = NewString(SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE) + } + + if s.UseLetsEncrypt == nil { + s.UseLetsEncrypt = NewBool(false) + } + + if s.LetsEncryptCertificateCacheFile == nil { + s.LetsEncryptCertificateCacheFile = NewString("./config/letsencrypt.cache") + } + + if s.ReadTimeout == nil { + s.ReadTimeout = NewInt(SERVICE_SETTINGS_DEFAULT_READ_TIMEOUT) + } + + if s.WriteTimeout == nil { + s.WriteTimeout = NewInt(SERVICE_SETTINGS_DEFAULT_WRITE_TIMEOUT) + } + + if s.MaximumLoginAttempts == nil { + s.MaximumLoginAttempts = NewInt(SERVICE_SETTINGS_DEFAULT_MAX_LOGIN_ATTEMPTS) + } + + if s.Forward80To443 == nil { + s.Forward80To443 = NewBool(false) + } + + if s.TimeBetweenUserTypingUpdatesMilliseconds == nil { + s.TimeBetweenUserTypingUpdatesMilliseconds = NewInt64(5000) + } + + if s.EnablePostSearch == nil { + s.EnablePostSearch = NewBool(true) + } + + if s.EnableUserTypingMessages == nil { + s.EnableUserTypingMessages = NewBool(true) + } + + if s.EnableChannelViewedMessages == nil { + s.EnableChannelViewedMessages = NewBool(true) + } + + if s.EnableUserStatuses == nil { + s.EnableUserStatuses = NewBool(true) + } + + if s.ClusterLogTimeoutMilliseconds == nil { + s.ClusterLogTimeoutMilliseconds = NewInt(2000) + } + + if s.CloseUnusedDirectMessages == nil { + s.CloseUnusedDirectMessages = NewBool(false) + } + + if s.EnableTutorial == nil { + s.EnableTutorial = NewBool(true) + } + + if s.SessionLengthWebInDays == nil { + s.SessionLengthWebInDays = NewInt(30) + } + + if s.SessionLengthMobileInDays == nil { + s.SessionLengthMobileInDays = NewInt(30) + } + + if s.SessionLengthSSOInDays == nil { + s.SessionLengthSSOInDays = NewInt(30) + } + + if s.SessionCacheInMinutes == nil { + s.SessionCacheInMinutes = NewInt(10) + } + + if s.SessionIdleTimeoutInMinutes == nil { + s.SessionIdleTimeoutInMinutes = NewInt(0) + } + + if s.EnableCommands == nil { + s.EnableCommands = NewBool(false) + } + + if s.EnableOnlyAdminIntegrations == nil { + s.EnableOnlyAdminIntegrations = NewBool(true) + } + + if s.WebsocketPort == nil { + s.WebsocketPort = NewInt(80) + } + + if s.WebsocketSecurePort == nil { + s.WebsocketSecurePort = NewInt(443) + } + + if s.AllowCorsFrom == nil { + s.AllowCorsFrom = NewString(SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM) + } + + if s.CorsExposedHeaders == nil { + s.CorsExposedHeaders = NewString("") + } + + if s.CorsAllowCredentials == nil { + s.CorsAllowCredentials = NewBool(false) + } + + if s.CorsDebug == nil { + s.CorsDebug = NewBool(false) + } + + if s.AllowCookiesForSubdomains == nil { + s.AllowCookiesForSubdomains = NewBool(false) + } + + if s.WebserverMode == nil { + s.WebserverMode = NewString("gzip") + } else if *s.WebserverMode == "regular" { + *s.WebserverMode = "gzip" + } + + if s.EnableCustomEmoji == nil { + s.EnableCustomEmoji = NewBool(false) + } + + if s.EnableEmojiPicker == nil { + s.EnableEmojiPicker = NewBool(true) + } + + if s.EnableGifPicker == nil { + s.EnableGifPicker = NewBool(false) + } + + if s.GfycatApiKey == nil || *s.GfycatApiKey == "" { + s.GfycatApiKey = NewString(SERVICE_SETTINGS_DEFAULT_GFYCAT_API_KEY) + } + + if s.GfycatApiSecret == nil || *s.GfycatApiSecret == "" { + s.GfycatApiSecret = NewString(SERVICE_SETTINGS_DEFAULT_GFYCAT_API_SECRET) + } + + if s.RestrictCustomEmojiCreation == nil { + s.RestrictCustomEmojiCreation = NewString(RESTRICT_EMOJI_CREATION_ALL) + } + + if s.RestrictPostDelete == nil { + s.RestrictPostDelete = NewString(PERMISSIONS_DELETE_POST_ALL) + } + + if s.AllowEditPost == nil { + s.AllowEditPost = NewString(ALLOW_EDIT_POST_ALWAYS) + } + + if s.ExperimentalEnableAuthenticationTransfer == nil { + s.ExperimentalEnableAuthenticationTransfer = NewBool(true) + } + + if s.PostEditTimeLimit == nil { + s.PostEditTimeLimit = NewInt(-1) + } + + if s.EnablePreviewFeatures == nil { + s.EnablePreviewFeatures = NewBool(true) + } + + if s.ExperimentalEnableDefaultChannelLeaveJoinMessages == nil { + s.ExperimentalEnableDefaultChannelLeaveJoinMessages = NewBool(true) + } + + if s.ExperimentalGroupUnreadChannels == nil { + s.ExperimentalGroupUnreadChannels = NewString(GROUP_UNREAD_CHANNELS_DISABLED) + } else if *s.ExperimentalGroupUnreadChannels == "0" { + s.ExperimentalGroupUnreadChannels = NewString(GROUP_UNREAD_CHANNELS_DISABLED) + } else if *s.ExperimentalGroupUnreadChannels == "1" { + s.ExperimentalGroupUnreadChannels = NewString(GROUP_UNREAD_CHANNELS_DEFAULT_ON) + } + + if s.ExperimentalChannelOrganization == nil { + experimentalUnreadEnabled := *s.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DISABLED + s.ExperimentalChannelOrganization = NewBool(experimentalUnreadEnabled) + } + + if s.ImageProxyType == nil { + s.ImageProxyType = NewString("") + } + + if s.ImageProxyURL == nil { + s.ImageProxyURL = NewString("") + } + + if s.ImageProxyOptions == nil { + s.ImageProxyOptions = NewString("") + } + + if s.EnableAPITeamDeletion == nil { + s.EnableAPITeamDeletion = NewBool(false) + } + + if s.ExperimentalEnableHardenedMode == nil { + s.ExperimentalEnableHardenedMode = NewBool(false) + } + + if s.ExperimentalLimitClientConfig == nil { + s.ExperimentalLimitClientConfig = NewBool(false) + } +} + +type ClusterSettings struct { + Enable *bool + ClusterName *string + OverrideHostname *string + UseIpAddress *bool + UseExperimentalGossip *bool + ReadOnlyConfig *bool + GossipPort *int + StreamingPort *int + MaxIdleConns *int + MaxIdleConnsPerHost *int + IdleConnTimeoutMilliseconds *int +} + +func (s *ClusterSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(false) + } + + if s.ClusterName == nil { + s.ClusterName = NewString("") + } + + if s.OverrideHostname == nil { + s.OverrideHostname = NewString("") + } + + if s.UseIpAddress == nil { + s.UseIpAddress = NewBool(true) + } + + if s.UseExperimentalGossip == nil { + s.UseExperimentalGossip = NewBool(false) + } + + if s.ReadOnlyConfig == nil { + s.ReadOnlyConfig = NewBool(true) + } + + if s.GossipPort == nil { + s.GossipPort = NewInt(8074) + } + + if s.StreamingPort == nil { + s.StreamingPort = NewInt(8075) + } + + if s.MaxIdleConns == nil { + s.MaxIdleConns = NewInt(100) + } + + if s.MaxIdleConnsPerHost == nil { + s.MaxIdleConnsPerHost = NewInt(128) + } + + if s.IdleConnTimeoutMilliseconds == nil { + s.IdleConnTimeoutMilliseconds = NewInt(90000) + } +} + +type MetricsSettings struct { + Enable *bool + BlockProfileRate *int + ListenAddress *string +} + +func (s *MetricsSettings) SetDefaults() { + if s.ListenAddress == nil { + s.ListenAddress = NewString(":8067") + } + + if s.Enable == nil { + s.Enable = NewBool(false) + } + + if s.BlockProfileRate == nil { + s.BlockProfileRate = NewInt(0) + } +} + +type ExperimentalSettings struct { + ClientSideCertEnable *bool + ClientSideCertCheck *string +} + +func (s *ExperimentalSettings) SetDefaults() { + if s.ClientSideCertEnable == nil { + s.ClientSideCertEnable = NewBool(false) + } + + if s.ClientSideCertCheck == nil { + s.ClientSideCertCheck = NewString(CLIENT_SIDE_CERT_CHECK_SECONDARY_AUTH) + } +} + +type AnalyticsSettings struct { + MaxUsersForStatistics *int +} + +func (s *AnalyticsSettings) SetDefaults() { + if s.MaxUsersForStatistics == nil { + s.MaxUsersForStatistics = NewInt(ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS) + } +} + +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 + DataSourceSearchReplicas []string + MaxIdleConns *int + ConnMaxLifetimeMilliseconds *int + MaxOpenConns *int + Trace bool + AtRestEncryptKey string + QueryTimeout *int + EnablePublicChannelsMaterialization *bool +} + +func (s *SqlSettings) SetDefaults() { + if s.DriverName == nil { + s.DriverName = NewString(DATABASE_DRIVER_MYSQL) + } + + if s.DataSource == nil { + s.DataSource = NewString(SQL_SETTINGS_DEFAULT_DATA_SOURCE) + } + + if len(s.AtRestEncryptKey) == 0 { + s.AtRestEncryptKey = NewRandomString(32) + } + + if s.MaxIdleConns == nil { + s.MaxIdleConns = NewInt(20) + } + + if s.MaxOpenConns == nil { + s.MaxOpenConns = NewInt(300) + } + + if s.ConnMaxLifetimeMilliseconds == nil { + s.ConnMaxLifetimeMilliseconds = NewInt(3600000) + } + + if s.QueryTimeout == nil { + s.QueryTimeout = NewInt(30) + } + + if s.EnablePublicChannelsMaterialization == nil { + s.EnablePublicChannelsMaterialization = NewBool(true) + } +} + +type LogSettings struct { + EnableConsole bool + ConsoleLevel string + ConsoleJson *bool + EnableFile bool + FileLevel string + FileJson *bool + FileLocation string + EnableWebhookDebugging bool + EnableDiagnostics *bool +} + +func (s *LogSettings) SetDefaults() { + if s.EnableDiagnostics == nil { + s.EnableDiagnostics = NewBool(true) + } + + if s.ConsoleJson == nil { + s.ConsoleJson = NewBool(true) + } + + if s.FileJson == nil { + s.FileJson = NewBool(true) + } +} + +type PasswordSettings struct { + MinimumLength *int + Lowercase *bool + Number *bool + Uppercase *bool + Symbol *bool +} + +func (s *PasswordSettings) SetDefaults() { + if s.MinimumLength == nil { + s.MinimumLength = NewInt(PASSWORD_MINIMUM_LENGTH) + } + + if s.Lowercase == nil { + s.Lowercase = NewBool(false) + } + + if s.Number == nil { + s.Number = NewBool(false) + } + + if s.Uppercase == nil { + s.Uppercase = NewBool(false) + } + + if s.Symbol == nil { + s.Symbol = NewBool(false) + } +} + +type FileSettings struct { + EnableFileAttachments *bool + EnableMobileUpload *bool + EnableMobileDownload *bool + MaxFileSize *int64 + DriverName *string + Directory string + EnablePublicLink bool + PublicLinkSalt *string + InitialFont string + AmazonS3AccessKeyId string + AmazonS3SecretAccessKey string + AmazonS3Bucket string + AmazonS3Region string + AmazonS3Endpoint string + AmazonS3SSL *bool + AmazonS3SignV2 *bool + AmazonS3SSE *bool + AmazonS3Trace *bool +} + +func (s *FileSettings) SetDefaults() { + if s.DriverName == nil { + s.DriverName = NewString(IMAGE_DRIVER_LOCAL) + } + + if s.AmazonS3Endpoint == "" { + // Defaults to "s3.amazonaws.com" + s.AmazonS3Endpoint = "s3.amazonaws.com" + } + + if s.AmazonS3SSL == nil { + s.AmazonS3SSL = NewBool(true) // Secure by default. + } + + if s.AmazonS3SignV2 == nil { + s.AmazonS3SignV2 = new(bool) + // Signature v2 is not enabled by default. + } + + if s.AmazonS3SSE == nil { + s.AmazonS3SSE = NewBool(false) // Not Encrypted by default. + } + + if s.AmazonS3Trace == nil { + s.AmazonS3Trace = NewBool(false) + } + + if s.EnableFileAttachments == nil { + s.EnableFileAttachments = NewBool(true) + } + + if s.EnableMobileUpload == nil { + s.EnableMobileUpload = NewBool(true) + } + + if s.EnableMobileDownload == nil { + s.EnableMobileDownload = NewBool(true) + } + + if s.MaxFileSize == nil { + s.MaxFileSize = NewInt64(52428800) // 50 MB + } + + if s.PublicLinkSalt == nil || len(*s.PublicLinkSalt) == 0 { + s.PublicLinkSalt = NewString(NewRandomString(32)) + } + + if s.InitialFont == "" { + // Defaults to "nunito-bold.ttf" + s.InitialFont = "nunito-bold.ttf" + } + + if s.Directory == "" { + s.Directory = FILE_SETTINGS_DEFAULT_DIRECTORY + } +} + +type EmailSettings struct { + EnableSignUpWithEmail bool + EnableSignInWithEmail *bool + EnableSignInWithUsername *bool + SendEmailNotifications bool + UseChannelInEmailNotifications *bool + RequireEmailVerification bool + FeedbackName string + FeedbackEmail string + FeedbackOrganization *string + EnableSMTPAuth *bool + SMTPUsername string + SMTPPassword string + SMTPServer string + SMTPPort string + ConnectionSecurity string + InviteSalt string + SendPushNotifications *bool + PushNotificationServer *string + PushNotificationContents *string + EnableEmailBatching *bool + EmailBatchingBufferSize *int + EmailBatchingInterval *int + EnablePreviewModeBanner *bool + SkipServerCertificateVerification *bool + EmailNotificationContentsType *string + LoginButtonColor *string + LoginButtonBorderColor *string + LoginButtonTextColor *string +} + +func (s *EmailSettings) SetDefaults() { + if len(s.InviteSalt) == 0 { + s.InviteSalt = NewRandomString(32) + } + + if s.EnableSignInWithEmail == nil { + s.EnableSignInWithEmail = NewBool(s.EnableSignUpWithEmail) + } + + if s.EnableSignInWithUsername == nil { + s.EnableSignInWithUsername = NewBool(false) + } + + if s.UseChannelInEmailNotifications == nil { + s.UseChannelInEmailNotifications = NewBool(false) + } + + if s.SendPushNotifications == nil { + s.SendPushNotifications = NewBool(false) + } + + if s.PushNotificationServer == nil { + s.PushNotificationServer = NewString("") + } + + if s.PushNotificationContents == nil { + s.PushNotificationContents = NewString(GENERIC_NOTIFICATION) + } + + if s.FeedbackOrganization == nil { + s.FeedbackOrganization = NewString(EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION) + } + + if s.EnableEmailBatching == nil { + s.EnableEmailBatching = NewBool(false) + } + + if s.EmailBatchingBufferSize == nil { + s.EmailBatchingBufferSize = NewInt(EMAIL_BATCHING_BUFFER_SIZE) + } + + if s.EmailBatchingInterval == nil { + s.EmailBatchingInterval = NewInt(EMAIL_BATCHING_INTERVAL) + } + + if s.EnablePreviewModeBanner == nil { + s.EnablePreviewModeBanner = NewBool(true) + } + + if s.EnableSMTPAuth == nil { + s.EnableSMTPAuth = new(bool) + if s.ConnectionSecurity == CONN_SECURITY_NONE { + *s.EnableSMTPAuth = false + } else { + *s.EnableSMTPAuth = true + } + } + + if s.ConnectionSecurity == CONN_SECURITY_PLAIN { + s.ConnectionSecurity = CONN_SECURITY_NONE + } + + if s.SkipServerCertificateVerification == nil { + s.SkipServerCertificateVerification = NewBool(false) + } + + if s.EmailNotificationContentsType == nil { + s.EmailNotificationContentsType = NewString(EMAIL_NOTIFICATION_CONTENTS_FULL) + } + + if s.LoginButtonColor == nil { + s.LoginButtonColor = NewString("#0000") + } + + if s.LoginButtonBorderColor == nil { + s.LoginButtonBorderColor = NewString("#2389D7") + } + + if s.LoginButtonTextColor == nil { + s.LoginButtonTextColor = NewString("#2389D7") + } +} + +type ExtensionSettings struct { + EnableExperimentalExtensions *bool + AllowedExtensionsIDs []string +} + +func (s *ExtensionSettings) SetDefaults() { + if s.EnableExperimentalExtensions == nil { + s.EnableExperimentalExtensions = NewBool(false) + } + + if s.AllowedExtensionsIDs == nil { + s.AllowedExtensionsIDs = []string{} + } +} + +type RateLimitSettings struct { + Enable *bool + PerSec *int + MaxBurst *int + MemoryStoreSize *int + VaryByRemoteAddr *bool + VaryByUser *bool + VaryByHeader string +} + +func (s *RateLimitSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(false) + } + + if s.PerSec == nil { + s.PerSec = NewInt(10) + } + + if s.MaxBurst == nil { + s.MaxBurst = NewInt(100) + } + + if s.MemoryStoreSize == nil { + s.MemoryStoreSize = NewInt(10000) + } + + if s.VaryByRemoteAddr == nil { + s.VaryByRemoteAddr = NewBool(true) + } + + if s.VaryByUser == nil { + s.VaryByUser = NewBool(false) + } +} + +type PrivacySettings struct { + ShowEmailAddress bool + ShowFullName bool +} + +type SupportSettings struct { + TermsOfServiceLink *string + PrivacyPolicyLink *string + AboutLink *string + HelpLink *string + ReportAProblemLink *string + SupportEmail *string + CustomTermsOfServiceEnabled *bool +} + +func (s *SupportSettings) SetDefaults() { + if !IsSafeLink(s.TermsOfServiceLink) { + *s.TermsOfServiceLink = SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK + } + + if s.TermsOfServiceLink == nil { + s.TermsOfServiceLink = NewString(SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK) + } + + if !IsSafeLink(s.PrivacyPolicyLink) { + *s.PrivacyPolicyLink = "" + } + + if s.PrivacyPolicyLink == nil { + s.PrivacyPolicyLink = NewString(SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK) + } + + if !IsSafeLink(s.AboutLink) { + *s.AboutLink = "" + } + + if s.AboutLink == nil { + s.AboutLink = NewString(SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK) + } + + if !IsSafeLink(s.HelpLink) { + *s.HelpLink = "" + } + + if s.HelpLink == nil { + s.HelpLink = NewString(SUPPORT_SETTINGS_DEFAULT_HELP_LINK) + } + + if !IsSafeLink(s.ReportAProblemLink) { + *s.ReportAProblemLink = "" + } + + if s.ReportAProblemLink == nil { + s.ReportAProblemLink = NewString(SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK) + } + + if s.SupportEmail == nil { + s.SupportEmail = NewString(SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL) + } + + if s.CustomTermsOfServiceEnabled == nil { + s.CustomTermsOfServiceEnabled = NewBool(false) + } +} + +type AnnouncementSettings struct { + EnableBanner *bool + BannerText *string + BannerColor *string + BannerTextColor *string + AllowBannerDismissal *bool +} + +func (s *AnnouncementSettings) SetDefaults() { + if s.EnableBanner == nil { + s.EnableBanner = NewBool(false) + } + + if s.BannerText == nil { + s.BannerText = NewString("") + } + + if s.BannerColor == nil { + s.BannerColor = NewString(ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR) + } + + if s.BannerTextColor == nil { + s.BannerTextColor = NewString(ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR) + } + + if s.AllowBannerDismissal == nil { + s.AllowBannerDismissal = NewBool(true) + } +} + +type ThemeSettings struct { + EnableThemeSelection *bool + DefaultTheme *string + AllowCustomThemes *bool + AllowedThemes []string +} + +func (s *ThemeSettings) SetDefaults() { + if s.EnableThemeSelection == nil { + s.EnableThemeSelection = NewBool(true) + } + + if s.DefaultTheme == nil { + s.DefaultTheme = NewString(TEAM_SETTINGS_DEFAULT_TEAM_TEXT) + } + + if s.AllowCustomThemes == nil { + s.AllowCustomThemes = NewBool(true) + } + + if s.AllowedThemes == nil { + s.AllowedThemes = []string{} + } +} + +type TeamSettings struct { + SiteName string + MaxUsersPerTeam *int + EnableTeamCreation *bool + EnableUserCreation *bool + EnableOpenServer *bool + EnableUserDeactivation *bool + RestrictCreationToDomains string + EnableCustomBrand *bool + CustomBrandText *string + CustomDescriptionText *string + RestrictDirectMessage *string + RestrictTeamInvite *string + RestrictPublicChannelManagement *string + RestrictPrivateChannelManagement *string + RestrictPublicChannelCreation *string + RestrictPrivateChannelCreation *string + RestrictPublicChannelDeletion *string + RestrictPrivateChannelDeletion *string + RestrictPrivateChannelManageMembers *string + EnableXToLeaveChannelsFromLHS *bool + UserStatusAwayTimeout *int64 + MaxChannelsPerTeam *int64 + MaxNotificationsPerChannel *int64 + EnableConfirmNotificationsToChannel *bool + TeammateNameDisplay *string + ExperimentalViewArchivedChannels *bool + ExperimentalEnableAutomaticReplies *bool + ExperimentalHideTownSquareinLHS *bool + ExperimentalTownSquareIsReadOnly *bool + ExperimentalPrimaryTeam *string + ExperimentalDefaultChannels []string +} + +func (s *TeamSettings) SetDefaults() { + if s.MaxUsersPerTeam == nil { + s.MaxUsersPerTeam = NewInt(TEAM_SETTINGS_DEFAULT_MAX_USERS_PER_TEAM) + } + + if s.EnableCustomBrand == nil { + s.EnableCustomBrand = NewBool(false) + } + + if s.EnableUserDeactivation == nil { + s.EnableUserDeactivation = NewBool(false) + } + + if s.CustomBrandText == nil { + s.CustomBrandText = NewString(TEAM_SETTINGS_DEFAULT_CUSTOM_BRAND_TEXT) + } + + if s.CustomDescriptionText == nil { + s.CustomDescriptionText = NewString(TEAM_SETTINGS_DEFAULT_CUSTOM_DESCRIPTION_TEXT) + } + + if s.EnableOpenServer == nil { + s.EnableOpenServer = NewBool(false) + } + + if s.RestrictDirectMessage == nil { + s.RestrictDirectMessage = NewString(DIRECT_MESSAGE_ANY) + } + + if s.RestrictTeamInvite == nil { + s.RestrictTeamInvite = NewString(PERMISSIONS_ALL) + } + + if s.RestrictPublicChannelManagement == nil { + s.RestrictPublicChannelManagement = NewString(PERMISSIONS_ALL) + } + + if s.RestrictPrivateChannelManagement == nil { + s.RestrictPrivateChannelManagement = NewString(PERMISSIONS_ALL) + } + + if s.RestrictPublicChannelCreation == nil { + s.RestrictPublicChannelCreation = new(string) + // If this setting does not exist, assume migration from <3.6, so use management setting as default. + if *s.RestrictPublicChannelManagement == PERMISSIONS_CHANNEL_ADMIN { + *s.RestrictPublicChannelCreation = PERMISSIONS_TEAM_ADMIN + } else { + *s.RestrictPublicChannelCreation = *s.RestrictPublicChannelManagement + } + } + + if s.RestrictPrivateChannelCreation == nil { + // If this setting does not exist, assume migration from <3.6, so use management setting as default. + if *s.RestrictPrivateChannelManagement == PERMISSIONS_CHANNEL_ADMIN { + s.RestrictPrivateChannelCreation = NewString(PERMISSIONS_TEAM_ADMIN) + } else { + s.RestrictPrivateChannelCreation = NewString(*s.RestrictPrivateChannelManagement) + } + } + + if s.RestrictPublicChannelDeletion == nil { + // If this setting does not exist, assume migration from <3.6, so use management setting as default. + s.RestrictPublicChannelDeletion = NewString(*s.RestrictPublicChannelManagement) + } + + if s.RestrictPrivateChannelDeletion == nil { + // If this setting does not exist, assume migration from <3.6, so use management setting as default. + s.RestrictPrivateChannelDeletion = NewString(*s.RestrictPrivateChannelManagement) + } + + if s.RestrictPrivateChannelManageMembers == nil { + s.RestrictPrivateChannelManageMembers = NewString(PERMISSIONS_ALL) + } + + if s.EnableXToLeaveChannelsFromLHS == nil { + s.EnableXToLeaveChannelsFromLHS = NewBool(false) + } + + if s.UserStatusAwayTimeout == nil { + s.UserStatusAwayTimeout = NewInt64(TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT) + } + + if s.MaxChannelsPerTeam == nil { + s.MaxChannelsPerTeam = NewInt64(2000) + } + + if s.MaxNotificationsPerChannel == nil { + s.MaxNotificationsPerChannel = NewInt64(1000) + } + + if s.EnableConfirmNotificationsToChannel == nil { + s.EnableConfirmNotificationsToChannel = NewBool(true) + } + + if s.ExperimentalEnableAutomaticReplies == nil { + s.ExperimentalEnableAutomaticReplies = NewBool(false) + } + + if s.ExperimentalHideTownSquareinLHS == nil { + s.ExperimentalHideTownSquareinLHS = NewBool(false) + } + + if s.ExperimentalTownSquareIsReadOnly == nil { + s.ExperimentalTownSquareIsReadOnly = NewBool(false) + } + + if s.ExperimentalPrimaryTeam == nil { + s.ExperimentalPrimaryTeam = NewString("") + } + + if s.ExperimentalDefaultChannels == nil { + s.ExperimentalDefaultChannels = []string{} + } + + if s.EnableTeamCreation == nil { + s.EnableTeamCreation = NewBool(true) + } + + if s.EnableUserCreation == nil { + s.EnableUserCreation = NewBool(true) + } + + if s.ExperimentalViewArchivedChannels == nil { + s.ExperimentalViewArchivedChannels = NewBool(false) + } +} + +type ClientRequirements struct { + AndroidLatestVersion string + AndroidMinVersion string + DesktopLatestVersion string + DesktopMinVersion string + IosLatestVersion string + IosMinVersion string +} + +type LdapSettings struct { + // Basic + Enable *bool + EnableSync *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 + NicknameAttribute *string + IdAttribute *string + PositionAttribute *string + LoginIdAttribute *string + + // Synchronization + SyncIntervalMinutes *int + + // Advanced + SkipCertificateVerification *bool + QueryTimeout *int + MaxPageSize *int + + // Customization + LoginFieldName *string + + LoginButtonColor *string + LoginButtonBorderColor *string + LoginButtonTextColor *string +} + +func (s *LdapSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(false) + } + + // When unset should default to LDAP Enabled + if s.EnableSync == nil { + s.EnableSync = NewBool(*s.Enable) + } + + if s.LdapServer == nil { + s.LdapServer = NewString("") + } + + if s.LdapPort == nil { + s.LdapPort = NewInt(389) + } + + if s.ConnectionSecurity == nil { + s.ConnectionSecurity = NewString("") + } + + if s.BaseDN == nil { + s.BaseDN = NewString("") + } + + if s.BindUsername == nil { + s.BindUsername = NewString("") + } + + if s.BindPassword == nil { + s.BindPassword = NewString("") + } + + if s.UserFilter == nil { + s.UserFilter = NewString("") + } + + if s.FirstNameAttribute == nil { + s.FirstNameAttribute = NewString(LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE) + } + + if s.LastNameAttribute == nil { + s.LastNameAttribute = NewString(LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE) + } + + if s.EmailAttribute == nil { + s.EmailAttribute = NewString(LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE) + } + + if s.UsernameAttribute == nil { + s.UsernameAttribute = NewString(LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE) + } + + if s.NicknameAttribute == nil { + s.NicknameAttribute = NewString(LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE) + } + + if s.IdAttribute == nil { + s.IdAttribute = NewString(LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE) + } + + if s.PositionAttribute == nil { + s.PositionAttribute = NewString(LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE) + } + + // For those upgrading to the version when LoginIdAttribute was added + // they need IdAttribute == LoginIdAttribute not to break + if s.LoginIdAttribute == nil { + s.LoginIdAttribute = s.IdAttribute + } + + if s.SyncIntervalMinutes == nil { + s.SyncIntervalMinutes = NewInt(60) + } + + if s.SkipCertificateVerification == nil { + s.SkipCertificateVerification = NewBool(false) + } + + if s.QueryTimeout == nil { + s.QueryTimeout = NewInt(60) + } + + if s.MaxPageSize == nil { + s.MaxPageSize = NewInt(0) + } + + if s.LoginFieldName == nil { + s.LoginFieldName = NewString(LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME) + } + + if s.LoginButtonColor == nil { + s.LoginButtonColor = NewString("#0000") + } + + if s.LoginButtonBorderColor == nil { + s.LoginButtonBorderColor = NewString("#2389D7") + } + + if s.LoginButtonTextColor == nil { + s.LoginButtonTextColor = NewString("#2389D7") + } +} + +type ComplianceSettings struct { + Enable *bool + Directory *string + EnableDaily *bool +} + +func (s *ComplianceSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(false) + } + + if s.Directory == nil { + s.Directory = NewString("./data/") + } + + if s.EnableDaily == nil { + s.EnableDaily = NewBool(false) + } +} + +type LocalizationSettings struct { + DefaultServerLocale *string + DefaultClientLocale *string + AvailableLocales *string +} + +func (s *LocalizationSettings) SetDefaults() { + if s.DefaultServerLocale == nil { + s.DefaultServerLocale = NewString(DEFAULT_LOCALE) + } + + if s.DefaultClientLocale == nil { + s.DefaultClientLocale = NewString(DEFAULT_LOCALE) + } + + if s.AvailableLocales == nil { + s.AvailableLocales = NewString("") + } +} + +type SamlSettings struct { + // Basic + Enable *bool + EnableSyncWithLdap *bool + EnableSyncWithLdapIncludeAuth *bool + + Verify *bool + Encrypt *bool + + IdpUrl *string + IdpDescriptorUrl *string + AssertionConsumerServiceURL *string + + ScopingIDPProviderId *string + ScopingIDPName *string + + IdpCertificateFile *string + PublicCertificateFile *string + PrivateKeyFile *string + + // User Mapping + IdAttribute *string + FirstNameAttribute *string + LastNameAttribute *string + EmailAttribute *string + UsernameAttribute *string + NicknameAttribute *string + LocaleAttribute *string + PositionAttribute *string + + LoginButtonText *string + + LoginButtonColor *string + LoginButtonBorderColor *string + LoginButtonTextColor *string +} + +func (s *SamlSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(false) + } + + if s.EnableSyncWithLdap == nil { + s.EnableSyncWithLdap = NewBool(false) + } + + if s.EnableSyncWithLdapIncludeAuth == nil { + s.EnableSyncWithLdapIncludeAuth = NewBool(false) + } + + if s.Verify == nil { + s.Verify = NewBool(true) + } + + if s.Encrypt == nil { + s.Encrypt = NewBool(true) + } + + if s.IdpUrl == nil { + s.IdpUrl = NewString("") + } + + if s.IdpDescriptorUrl == nil { + s.IdpDescriptorUrl = NewString("") + } + + if s.IdpCertificateFile == nil { + s.IdpCertificateFile = NewString("") + } + + if s.PublicCertificateFile == nil { + s.PublicCertificateFile = NewString("") + } + + if s.PrivateKeyFile == nil { + s.PrivateKeyFile = NewString("") + } + + if s.AssertionConsumerServiceURL == nil { + s.AssertionConsumerServiceURL = NewString("") + } + + if s.ScopingIDPProviderId == nil { + s.ScopingIDPProviderId = NewString("") + } + + if s.ScopingIDPName == nil { + s.ScopingIDPName = NewString("") + } + + if s.LoginButtonText == nil || *s.LoginButtonText == "" { + s.LoginButtonText = NewString(USER_AUTH_SERVICE_SAML_TEXT) + } + + if s.IdAttribute == nil { + s.IdAttribute = NewString(SAML_SETTINGS_DEFAULT_ID_ATTRIBUTE) + } + + if s.FirstNameAttribute == nil { + s.FirstNameAttribute = NewString(SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE) + } + + if s.LastNameAttribute == nil { + s.LastNameAttribute = NewString(SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE) + } + + if s.EmailAttribute == nil { + s.EmailAttribute = NewString(SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE) + } + + if s.UsernameAttribute == nil { + s.UsernameAttribute = NewString(SAML_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE) + } + + if s.NicknameAttribute == nil { + s.NicknameAttribute = NewString(SAML_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE) + } + + if s.PositionAttribute == nil { + s.PositionAttribute = NewString(SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE) + } + + if s.LocaleAttribute == nil { + s.LocaleAttribute = NewString(SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE) + } + + if s.LoginButtonColor == nil { + s.LoginButtonColor = NewString("#34a28b") + } + + if s.LoginButtonBorderColor == nil { + s.LoginButtonBorderColor = NewString("#2389D7") + } + + if s.LoginButtonTextColor == nil { + s.LoginButtonTextColor = NewString("#ffffff") + } +} + +type NativeAppSettings struct { + AppDownloadLink *string + AndroidAppDownloadLink *string + IosAppDownloadLink *string +} + +func (s *NativeAppSettings) SetDefaults() { + if s.AppDownloadLink == nil { + s.AppDownloadLink = NewString(NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK) + } + + if s.AndroidAppDownloadLink == nil { + s.AndroidAppDownloadLink = NewString(NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK) + } + + if s.IosAppDownloadLink == nil { + s.IosAppDownloadLink = NewString(NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK) + } +} + +type WebrtcSettings struct { + Enable *bool + GatewayWebsocketUrl *string + GatewayAdminUrl *string + GatewayAdminSecret *string + StunURI *string + TurnURI *string + TurnUsername *string + TurnSharedKey *string +} + +func (s *WebrtcSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(false) + } + + if s.GatewayWebsocketUrl == nil { + s.GatewayWebsocketUrl = NewString("") + } + + if s.GatewayAdminUrl == nil { + s.GatewayAdminUrl = NewString("") + } + + if s.GatewayAdminSecret == nil { + s.GatewayAdminSecret = NewString("") + } + + if s.StunURI == nil { + s.StunURI = NewString(WEBRTC_SETTINGS_DEFAULT_STUN_URI) + } + + if s.TurnURI == nil { + s.TurnURI = NewString(WEBRTC_SETTINGS_DEFAULT_TURN_URI) + } + + if s.TurnUsername == nil { + s.TurnUsername = NewString("") + } + + if s.TurnSharedKey == nil { + s.TurnSharedKey = NewString("") + } +} + +type ElasticsearchSettings struct { + ConnectionUrl *string + Username *string + Password *string + EnableIndexing *bool + EnableSearching *bool + Sniff *bool + PostIndexReplicas *int + PostIndexShards *int + AggregatePostsAfterDays *int + PostsAggregatorJobStartTime *string + IndexPrefix *string + LiveIndexingBatchSize *int + BulkIndexingTimeWindowSeconds *int + RequestTimeoutSeconds *int +} + +func (s *ElasticsearchSettings) SetDefaults() { + if s.ConnectionUrl == nil { + s.ConnectionUrl = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL) + } + + if s.Username == nil { + s.Username = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME) + } + + if s.Password == nil { + s.Password = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD) + } + + if s.EnableIndexing == nil { + s.EnableIndexing = NewBool(false) + } + + if s.EnableSearching == nil { + s.EnableSearching = NewBool(false) + } + + if s.Sniff == nil { + s.Sniff = NewBool(true) + } + + if s.PostIndexReplicas == nil { + s.PostIndexReplicas = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS) + } + + if s.PostIndexShards == nil { + s.PostIndexShards = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS) + } + + if s.AggregatePostsAfterDays == nil { + s.AggregatePostsAfterDays = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_AGGREGATE_POSTS_AFTER_DAYS) + } + + if s.PostsAggregatorJobStartTime == nil { + s.PostsAggregatorJobStartTime = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_POSTS_AGGREGATOR_JOB_START_TIME) + } + + if s.IndexPrefix == nil { + s.IndexPrefix = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_INDEX_PREFIX) + } + + if s.LiveIndexingBatchSize == nil { + s.LiveIndexingBatchSize = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_LIVE_INDEXING_BATCH_SIZE) + } + + if s.BulkIndexingTimeWindowSeconds == nil { + s.BulkIndexingTimeWindowSeconds = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_BULK_INDEXING_TIME_WINDOW_SECONDS) + } + + if s.RequestTimeoutSeconds == nil { + s.RequestTimeoutSeconds = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_REQUEST_TIMEOUT_SECONDS) + } +} + +type DataRetentionSettings struct { + EnableMessageDeletion *bool + EnableFileDeletion *bool + MessageRetentionDays *int + FileRetentionDays *int + DeletionJobStartTime *string +} + +func (s *DataRetentionSettings) SetDefaults() { + if s.EnableMessageDeletion == nil { + s.EnableMessageDeletion = NewBool(false) + } + + if s.EnableFileDeletion == nil { + s.EnableFileDeletion = NewBool(false) + } + + if s.MessageRetentionDays == nil { + s.MessageRetentionDays = NewInt(DATA_RETENTION_SETTINGS_DEFAULT_MESSAGE_RETENTION_DAYS) + } + + if s.FileRetentionDays == nil { + s.FileRetentionDays = NewInt(DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS) + } + + if s.DeletionJobStartTime == nil { + s.DeletionJobStartTime = NewString(DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME) + } +} + +type JobSettings struct { + RunJobs *bool + RunScheduler *bool +} + +func (s *JobSettings) SetDefaults() { + if s.RunJobs == nil { + s.RunJobs = NewBool(true) + } + + if s.RunScheduler == nil { + s.RunScheduler = NewBool(true) + } +} + +type PluginState struct { + Enable bool +} + +type PluginSettings struct { + Enable *bool + EnableUploads *bool + Directory *string + ClientDirectory *string + Plugins map[string]map[string]interface{} + PluginStates map[string]*PluginState +} + +func (s *PluginSettings) SetDefaults() { + if s.Enable == nil { + s.Enable = NewBool(true) + } + + if s.EnableUploads == nil { + s.EnableUploads = NewBool(false) + } + + if s.Directory == nil { + s.Directory = NewString(PLUGIN_SETTINGS_DEFAULT_DIRECTORY) + } + + if *s.Directory == "" { + *s.Directory = PLUGIN_SETTINGS_DEFAULT_DIRECTORY + } + + if s.ClientDirectory == nil { + s.ClientDirectory = NewString(PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY) + } + + if *s.ClientDirectory == "" { + *s.ClientDirectory = PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY + } + + if s.Plugins == nil { + s.Plugins = make(map[string]map[string]interface{}) + } + + if s.PluginStates == nil { + s.PluginStates = make(map[string]*PluginState) + } +} + +type GlobalRelayMessageExportSettings struct { + CustomerType *string // must be either A9 or A10, dictates SMTP server url + SmtpUsername *string + SmtpPassword *string + EmailAddress *string // the address to send messages to +} + +func (s *GlobalRelayMessageExportSettings) SetDefaults() { + if s.CustomerType == nil { + s.CustomerType = NewString(GLOBALRELAY_CUSTOMER_TYPE_A9) + } + if s.SmtpUsername == nil { + s.SmtpUsername = NewString("") + } + if s.SmtpPassword == nil { + s.SmtpPassword = NewString("") + } + if s.EmailAddress == nil { + s.EmailAddress = NewString("") + } +} + +type MessageExportSettings struct { + EnableExport *bool + ExportFormat *string + DailyRunTime *string + ExportFromTimestamp *int64 + BatchSize *int + + // formatter-specific settings - these are only expected to be non-nil if ExportFormat is set to the associated format + GlobalRelaySettings *GlobalRelayMessageExportSettings +} + +func (s *MessageExportSettings) SetDefaults() { + if s.EnableExport == nil { + s.EnableExport = NewBool(false) + } + + if s.ExportFormat == nil { + s.ExportFormat = NewString(COMPLIANCE_EXPORT_TYPE_ACTIANCE) + } + + if s.DailyRunTime == nil { + s.DailyRunTime = NewString("01:00") + } + + if s.ExportFromTimestamp == nil { + s.ExportFromTimestamp = NewInt64(0) + } + + if s.EnableExport != nil && *s.EnableExport && *s.ExportFromTimestamp == int64(0) { + // when the feature is enabled via the System Console, use the current timestamp as the start time for future exports + s.ExportFromTimestamp = NewInt64(GetMillis()) + } else if s.EnableExport != nil && !*s.EnableExport { + // when the feature is disabled, reset the timestamp so that the timestamp will be set if the feature is re-enabled + s.ExportFromTimestamp = NewInt64(0) + } + + if s.BatchSize == nil { + s.BatchSize = NewInt(10000) + } + + if s.GlobalRelaySettings == nil { + s.GlobalRelaySettings = &GlobalRelayMessageExportSettings{} + } + s.GlobalRelaySettings.SetDefaults() +} + +type DisplaySettings struct { + CustomUrlSchemes *[]string + ExperimentalTimezone *bool +} + +func (s *DisplaySettings) SetDefaults() { + if s.CustomUrlSchemes == nil { + customUrlSchemes := []string{} + s.CustomUrlSchemes = &customUrlSchemes + } + + if s.ExperimentalTimezone == nil { + s.ExperimentalTimezone = NewBool(false) + } +} + +type TimezoneSettings struct { + SupportedTimezonesPath *string +} + +func (s *TimezoneSettings) SetDefaults() { + if s.SupportedTimezonesPath == nil { + s.SupportedTimezonesPath = NewString(TIMEZONE_SETTINGS_DEFAULT_SUPPORTED_TIMEZONES_PATH) + } +} + +type ConfigFunc func() *Config + +type Config struct { + ServiceSettings ServiceSettings + TeamSettings TeamSettings + ClientRequirements ClientRequirements + SqlSettings SqlSettings + LogSettings LogSettings + PasswordSettings PasswordSettings + FileSettings FileSettings + EmailSettings EmailSettings + ExtensionSettings ExtensionSettings + RateLimitSettings RateLimitSettings + PrivacySettings PrivacySettings + SupportSettings SupportSettings + AnnouncementSettings AnnouncementSettings + ThemeSettings ThemeSettings + GitLabSettings SSOSettings + GoogleSettings SSOSettings + Office365Settings SSOSettings + LdapSettings LdapSettings + ComplianceSettings ComplianceSettings + LocalizationSettings LocalizationSettings + SamlSettings SamlSettings + NativeAppSettings NativeAppSettings + ClusterSettings ClusterSettings + MetricsSettings MetricsSettings + ExperimentalSettings ExperimentalSettings + AnalyticsSettings AnalyticsSettings + WebrtcSettings WebrtcSettings + ElasticsearchSettings ElasticsearchSettings + DataRetentionSettings DataRetentionSettings + MessageExportSettings MessageExportSettings + JobSettings JobSettings + PluginSettings PluginSettings + DisplaySettings DisplaySettings + TimezoneSettings TimezoneSettings +} + +func (o *Config) Clone() *Config { + var ret Config + if err := json.Unmarshal([]byte(o.ToJson()), &ret); err != nil { + panic(err) + } + return &ret +} + +func (o *Config) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func (o *Config) GetSSOService(service string) *SSOSettings { + switch service { + case SERVICE_GITLAB: + return &o.GitLabSettings + case SERVICE_GOOGLE: + return &o.GoogleSettings + case SERVICE_OFFICE365: + return &o.Office365Settings + } + + return nil +} + +func ConfigFromJson(data io.Reader) *Config { + var o *Config + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *Config) SetDefaults() { + o.LdapSettings.SetDefaults() + o.SamlSettings.SetDefaults() + + if o.TeamSettings.TeammateNameDisplay == nil { + o.TeamSettings.TeammateNameDisplay = NewString(SHOW_USERNAME) + + if *o.SamlSettings.Enable || *o.LdapSettings.Enable { + *o.TeamSettings.TeammateNameDisplay = SHOW_FULLNAME + } + } + + o.SqlSettings.SetDefaults() + o.FileSettings.SetDefaults() + o.EmailSettings.SetDefaults() + o.ServiceSettings.SetDefaults() + o.PasswordSettings.SetDefaults() + o.TeamSettings.SetDefaults() + o.MetricsSettings.SetDefaults() + o.ExperimentalSettings.SetDefaults() + o.SupportSettings.SetDefaults() + o.AnnouncementSettings.SetDefaults() + o.ThemeSettings.SetDefaults() + o.ClusterSettings.SetDefaults() + o.PluginSettings.SetDefaults() + o.AnalyticsSettings.SetDefaults() + o.ComplianceSettings.SetDefaults() + o.LocalizationSettings.SetDefaults() + o.ElasticsearchSettings.SetDefaults() + o.NativeAppSettings.SetDefaults() + o.DataRetentionSettings.SetDefaults() + o.RateLimitSettings.SetDefaults() + o.LogSettings.SetDefaults() + o.JobSettings.SetDefaults() + o.WebrtcSettings.SetDefaults() + o.MessageExportSettings.SetDefaults() + o.TimezoneSettings.SetDefaults() + o.DisplaySettings.SetDefaults() + o.ExtensionSettings.SetDefaults() +} + +func (o *Config) IsValid() *AppError { + if len(*o.ServiceSettings.SiteURL) == 0 && *o.EmailSettings.EnableEmailBatching { + return NewAppError("Config.IsValid", "model.config.is_valid.site_url_email_batching.app_error", nil, "", http.StatusBadRequest) + } + + if *o.ClusterSettings.Enable && *o.EmailSettings.EnableEmailBatching { + return NewAppError("Config.IsValid", "model.config.is_valid.cluster_email_batching.app_error", nil, "", http.StatusBadRequest) + } + + if len(*o.ServiceSettings.SiteURL) == 0 && *o.ServiceSettings.AllowCookiesForSubdomains { + return NewAppError("Config.IsValid", "model.config.is_valid.allow_cookies_for_subdomains.app_error", nil, "", http.StatusBadRequest) + } + + if err := o.TeamSettings.isValid(); err != nil { + return err + } + + if err := o.SqlSettings.isValid(); err != nil { + return err + } + + if err := o.FileSettings.isValid(); err != nil { + return err + } + + if err := o.EmailSettings.isValid(); err != nil { + return err + } + + if err := o.LdapSettings.isValid(); err != nil { + return err + } + + if err := o.SamlSettings.isValid(); err != nil { + return err + } + + if *o.PasswordSettings.MinimumLength < PASSWORD_MINIMUM_LENGTH || *o.PasswordSettings.MinimumLength > PASSWORD_MAXIMUM_LENGTH { + return NewAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "", http.StatusBadRequest) + } + + if err := o.RateLimitSettings.isValid(); err != nil { + return err + } + + if err := o.WebrtcSettings.isValid(); err != nil { + return err + } + + if err := o.ServiceSettings.isValid(); err != nil { + return err + } + + if err := o.ElasticsearchSettings.isValid(); err != nil { + return err + } + + if err := o.DataRetentionSettings.isValid(); err != nil { + return err + } + + if err := o.LocalizationSettings.isValid(); err != nil { + return err + } + + if err := o.MessageExportSettings.isValid(o.FileSettings); err != nil { + return err + } + + if err := o.DisplaySettings.isValid(); err != nil { + return err + } + + return nil +} + +func (ts *TeamSettings) isValid() *AppError { + if *ts.MaxUsersPerTeam <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "", http.StatusBadRequest) + } + + if *ts.MaxChannelsPerTeam <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "", http.StatusBadRequest) + } + + if *ts.MaxNotificationsPerChannel <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.max_notify_per_channel.app_error", nil, "", http.StatusBadRequest) + } + + if !(*ts.RestrictDirectMessage == DIRECT_MESSAGE_ANY || *ts.RestrictDirectMessage == DIRECT_MESSAGE_TEAM) { + return NewAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "", http.StatusBadRequest) + } + + if !(*ts.TeammateNameDisplay == SHOW_FULLNAME || *ts.TeammateNameDisplay == SHOW_NICKNAME_FULLNAME || *ts.TeammateNameDisplay == SHOW_USERNAME) { + return NewAppError("Config.IsValid", "model.config.is_valid.teammate_name_display.app_error", nil, "", http.StatusBadRequest) + } + + if len(ts.SiteName) > SITENAME_MAX_LENGTH { + return NewAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "", http.StatusBadRequest) + } + + return nil +} + +func (ss *SqlSettings) isValid() *AppError { + if len(ss.AtRestEncryptKey) < 32 { + return NewAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "", http.StatusBadRequest) + } + + if !(*ss.DriverName == DATABASE_DRIVER_MYSQL || *ss.DriverName == DATABASE_DRIVER_POSTGRES) { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_driver.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.MaxIdleConns <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.ConnMaxLifetimeMilliseconds < 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_conn_max_lifetime_milliseconds.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.QueryTimeout <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.DataSource) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.MaxOpenConns <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (fs *FileSettings) isValid() *AppError { + if *fs.MaxFileSize <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.max_file_size.app_error", nil, "", http.StatusBadRequest) + } + + if !(*fs.DriverName == IMAGE_DRIVER_LOCAL || *fs.DriverName == IMAGE_DRIVER_S3) { + return NewAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "", http.StatusBadRequest) + } + + if len(*fs.PublicLinkSalt) < 32 { + return NewAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (es *EmailSettings) isValid() *AppError { + if !(es.ConnectionSecurity == CONN_SECURITY_NONE || es.ConnectionSecurity == CONN_SECURITY_TLS || es.ConnectionSecurity == CONN_SECURITY_STARTTLS || es.ConnectionSecurity == CONN_SECURITY_PLAIN) { + return NewAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "", http.StatusBadRequest) + } + + if len(es.InviteSalt) < 32 { + return NewAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "", http.StatusBadRequest) + } + + if *es.EmailBatchingBufferSize <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "", http.StatusBadRequest) + } + + if *es.EmailBatchingInterval < 30 { + return NewAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "", http.StatusBadRequest) + } + + if !(*es.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_FULL || *es.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_GENERIC) { + return NewAppError("Config.IsValid", "model.config.is_valid.email_notification_contents_type.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (rls *RateLimitSettings) isValid() *AppError { + if *rls.MemoryStoreSize <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "", http.StatusBadRequest) + } + + if *rls.PerSec <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.rate_sec.app_error", nil, "", http.StatusBadRequest) + } + + if *rls.MaxBurst <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.max_burst.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (ls *LdapSettings) isValid() *AppError { + if !(*ls.ConnectionSecurity == CONN_SECURITY_NONE || *ls.ConnectionSecurity == CONN_SECURITY_TLS || *ls.ConnectionSecurity == CONN_SECURITY_STARTTLS) { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "", http.StatusBadRequest) + } + + if *ls.SyncIntervalMinutes <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "", http.StatusBadRequest) + } + + if *ls.MaxPageSize < 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_max_page_size.app_error", nil, "", http.StatusBadRequest) + } + + if *ls.Enable { + if *ls.LdapServer == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "", http.StatusBadRequest) + } + + if *ls.BaseDN == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "", http.StatusBadRequest) + } + + if *ls.EmailAttribute == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_email", nil, "", http.StatusBadRequest) + } + + if *ls.UsernameAttribute == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_username", nil, "", http.StatusBadRequest) + } + + if *ls.IdAttribute == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "", http.StatusBadRequest) + } + + if *ls.LoginIdAttribute == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.ldap_login_id", nil, "", http.StatusBadRequest) + } + } + + return nil +} + +func (ss *SamlSettings) isValid() *AppError { + if *ss.Enable { + if len(*ss.IdpUrl) == 0 || !IsValidHttpUrl(*ss.IdpUrl) { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.IdpDescriptorUrl) == 0 || !IsValidHttpUrl(*ss.IdpDescriptorUrl) { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_descriptor_url.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.IdpCertificateFile) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_cert.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.EmailAttribute) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.UsernameAttribute) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_username_attribute.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.Verify { + if len(*ss.AssertionConsumerServiceURL) == 0 || !IsValidHttpUrl(*ss.AssertionConsumerServiceURL) { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "", http.StatusBadRequest) + } + } + + if *ss.Encrypt { + if len(*ss.PrivateKeyFile) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_private_key.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.PublicCertificateFile) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_public_cert.app_error", nil, "", http.StatusBadRequest) + } + } + + if len(*ss.EmailAttribute) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "", http.StatusBadRequest) + } + } + + return nil +} + +func (ws *WebrtcSettings) isValid() *AppError { + if *ws.Enable { + if len(*ws.GatewayWebsocketUrl) == 0 || !IsValidWebsocketUrl(*ws.GatewayWebsocketUrl) { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_ws_url.app_error", nil, "", http.StatusBadRequest) + } else if len(*ws.GatewayAdminUrl) == 0 || !IsValidHttpUrl(*ws.GatewayAdminUrl) { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_url.app_error", nil, "", http.StatusBadRequest) + } else if len(*ws.GatewayAdminSecret) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_secret.app_error", nil, "", http.StatusBadRequest) + } else if len(*ws.StunURI) != 0 && !IsValidTurnOrStunServer(*ws.StunURI) { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_stun_uri.app_error", nil, "", http.StatusBadRequest) + } else if len(*ws.TurnURI) != 0 { + if !IsValidTurnOrStunServer(*ws.TurnURI) { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_uri.app_error", nil, "", http.StatusBadRequest) + } + if len(*ws.TurnUsername) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_username.app_error", nil, "", http.StatusBadRequest) + } else if len(*ws.TurnSharedKey) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_shared_key.app_error", nil, "", http.StatusBadRequest) + } + } + } + + return nil +} + +func (ss *ServiceSettings) isValid() *AppError { + if !(*ss.ConnectionSecurity == CONN_SECURITY_NONE || *ss.ConnectionSecurity == CONN_SECURITY_TLS) { + return NewAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.ReadTimeout <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.WriteTimeout <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.TimeBetweenUserTypingUpdatesMilliseconds < 1000 { + return NewAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.MaximumLoginAttempts <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "", http.StatusBadRequest) + } + + if len(*ss.SiteURL) != 0 { + if _, err := url.ParseRequestURI(*ss.SiteURL); err != nil { + return NewAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "", http.StatusBadRequest) + } + } + + if len(*ss.WebsocketURL) != 0 { + if _, err := url.ParseRequestURI(*ss.WebsocketURL); err != nil { + return NewAppError("Config.IsValid", "model.config.is_valid.websocket_url.app_error", nil, "", http.StatusBadRequest) + } + } + + host, port, _ := net.SplitHostPort(*ss.ListenAddress) + var isValidHost bool + if host == "" { + isValidHost = true + } else { + isValidHost = (net.ParseIP(host) != nil) || IsDomainName(host) + } + portInt, err := strconv.Atoi(port) + if err != nil || !isValidHost || portInt < 0 || portInt > math.MaxUint16 { + return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest) + } + + if *ss.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DISABLED && + *ss.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DEFAULT_ON && + *ss.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DEFAULT_OFF { + return NewAppError("Config.IsValid", "model.config.is_valid.group_unread_channels.app_error", nil, "", http.StatusBadRequest) + } + + switch *ss.ImageProxyType { + case "": + case "atmos/camo": + if *ss.ImageProxyOptions == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_options.app_error", nil, "", http.StatusBadRequest) + } + default: + return NewAppError("Config.IsValid", "model.config.is_valid.image_proxy_type.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (ess *ElasticsearchSettings) isValid() *AppError { + if *ess.EnableIndexing { + if len(*ess.ConnectionUrl) == 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.connection_url.app_error", nil, "", http.StatusBadRequest) + } + } + + if *ess.EnableSearching && !*ess.EnableIndexing { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.enable_searching.app_error", nil, "", http.StatusBadRequest) + } + + if *ess.AggregatePostsAfterDays < 1 { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.aggregate_posts_after_days.app_error", nil, "", http.StatusBadRequest) + } + + if _, err := time.Parse("15:04", *ess.PostsAggregatorJobStartTime); err != nil { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.posts_aggregator_job_start_time.app_error", nil, err.Error(), http.StatusBadRequest) + } + + if *ess.LiveIndexingBatchSize < 1 { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.live_indexing_batch_size.app_error", nil, "", http.StatusBadRequest) + } + + if *ess.BulkIndexingTimeWindowSeconds < 1 { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.bulk_indexing_time_window_seconds.app_error", nil, "", http.StatusBadRequest) + } + + if *ess.RequestTimeoutSeconds < 1 { + return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.request_timeout_seconds.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (drs *DataRetentionSettings) isValid() *AppError { + if *drs.MessageRetentionDays <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", nil, "", http.StatusBadRequest) + } + + if *drs.FileRetentionDays <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_days_too_low.app_error", nil, "", http.StatusBadRequest) + } + + if _, err := time.Parse("15:04", *drs.DeletionJobStartTime); err != nil { + return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.deletion_job_start_time.app_error", nil, err.Error(), http.StatusBadRequest) + } + + return nil +} + +func (ls *LocalizationSettings) isValid() *AppError { + if len(*ls.AvailableLocales) > 0 { + if !strings.Contains(*ls.AvailableLocales, *ls.DefaultClientLocale) { + return NewAppError("Config.IsValid", "model.config.is_valid.localization.available_locales.app_error", nil, "", http.StatusBadRequest) + } + } + + return nil +} + +func (mes *MessageExportSettings) isValid(fs FileSettings) *AppError { + if mes.EnableExport == nil { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.enable.app_error", nil, "", http.StatusBadRequest) + } + if *mes.EnableExport { + if mes.ExportFromTimestamp == nil || *mes.ExportFromTimestamp < 0 || *mes.ExportFromTimestamp > GetMillis() { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_from.app_error", nil, "", http.StatusBadRequest) + } else if mes.DailyRunTime == nil { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, "", http.StatusBadRequest) + } else if _, err := time.Parse("15:04", *mes.DailyRunTime); err != nil { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, err.Error(), http.StatusBadRequest) + } else if mes.BatchSize == nil || *mes.BatchSize < 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.batch_size.app_error", nil, "", http.StatusBadRequest) + } else if mes.ExportFormat == nil || (*mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_ACTIANCE && *mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_GLOBALRELAY && *mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_CSV) { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_type.app_error", nil, "", http.StatusBadRequest) + } + + if *mes.ExportFormat == COMPLIANCE_EXPORT_TYPE_GLOBALRELAY { + if mes.GlobalRelaySettings == nil { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.config_missing.app_error", nil, "", http.StatusBadRequest) + } else if mes.GlobalRelaySettings.CustomerType == nil || (*mes.GlobalRelaySettings.CustomerType != GLOBALRELAY_CUSTOMER_TYPE_A9 && *mes.GlobalRelaySettings.CustomerType != GLOBALRELAY_CUSTOMER_TYPE_A10) { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.customer_type.app_error", nil, "", http.StatusBadRequest) + } else if mes.GlobalRelaySettings.EmailAddress == nil || !strings.Contains(*mes.GlobalRelaySettings.EmailAddress, "@") { + // validating email addresses is hard - just make sure it contains an '@' sign + // see https://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.email_address.app_error", nil, "", http.StatusBadRequest) + } else if mes.GlobalRelaySettings.SmtpUsername == nil || *mes.GlobalRelaySettings.SmtpUsername == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.smtp_username.app_error", nil, "", http.StatusBadRequest) + } else if mes.GlobalRelaySettings.SmtpPassword == nil || *mes.GlobalRelaySettings.SmtpPassword == "" { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.smtp_password.app_error", nil, "", http.StatusBadRequest) + } + } + } + return nil +} + +func (ds *DisplaySettings) isValid() *AppError { + if len(*ds.CustomUrlSchemes) != 0 { + validProtocolPattern := regexp.MustCompile(`(?i)^\s*[a-z][a-z0-9-]*\s*$`) + + for _, scheme := range *ds.CustomUrlSchemes { + if !validProtocolPattern.MatchString(scheme) { + return NewAppError( + "Config.IsValid", + "model.config.is_valid.display.custom_url_schemes.app_error", + map[string]interface{}{"Scheme": scheme}, + "", + http.StatusBadRequest, + ) + } + } + } + + return nil +} + +func (o *Config) GetSanitizeOptions() map[string]bool { + options := map[string]bool{} + options["fullname"] = o.PrivacySettings.ShowFullName + options["email"] = o.PrivacySettings.ShowEmailAddress + + return options +} + +func (o *Config) Sanitize() { + if o.LdapSettings.BindPassword != nil && len(*o.LdapSettings.BindPassword) > 0 { + *o.LdapSettings.BindPassword = FAKE_SETTING + } + + *o.FileSettings.PublicLinkSalt = FAKE_SETTING + if len(o.FileSettings.AmazonS3SecretAccessKey) > 0 { + o.FileSettings.AmazonS3SecretAccessKey = FAKE_SETTING + } + + o.EmailSettings.InviteSalt = FAKE_SETTING + if len(o.EmailSettings.SMTPPassword) > 0 { + o.EmailSettings.SMTPPassword = FAKE_SETTING + } + + if len(o.GitLabSettings.Secret) > 0 { + o.GitLabSettings.Secret = FAKE_SETTING + } + + *o.SqlSettings.DataSource = FAKE_SETTING + o.SqlSettings.AtRestEncryptKey = FAKE_SETTING + + for i := range o.SqlSettings.DataSourceReplicas { + o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING + } + + for i := range o.SqlSettings.DataSourceSearchReplicas { + o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING + } + + *o.ElasticsearchSettings.Password = FAKE_SETTING +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/data_retention_policy.go b/vendor/github.com/mattermost/mattermost-server/model/data_retention_policy.go new file mode 100644 index 00000000..dbb13374 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/data_retention_policy.go @@ -0,0 +1,27 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type DataRetentionPolicy struct { + MessageDeletionEnabled bool `json:"message_deletion_enabled"` + FileDeletionEnabled bool `json:"file_deletion_enabled"` + MessageRetentionCutoff int64 `json:"message_retention_cutoff"` + FileRetentionCutoff int64 `json:"file_retention_cutoff"` +} + +func (me *DataRetentionPolicy) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func DataRetentionPolicyFromJson(data io.Reader) *DataRetentionPolicy { + var me *DataRetentionPolicy + json.NewDecoder(data).Decode(&me) + return me +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/emoji.go b/vendor/github.com/mattermost/mattermost-server/model/emoji.go new file mode 100644 index 00000000..f14af89d --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/emoji.go @@ -0,0 +1,88 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" +) + +const ( + EMOJI_NAME_MAX_LENGTH = 64 + EMOJI_SORT_BY_NAME = "name" +) + +type Emoji struct { + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + CreatorId string `json:"creator_id"` + Name string `json:"name"` +} + +func inSystemEmoji(emojiName string) bool { + _, ok := SystemEmojis[emojiName] + return ok +} + +func (emoji *Emoji) IsValid() *AppError { + if len(emoji.Id) != 26 { + return NewAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "", http.StatusBadRequest) + } + + if emoji.CreateAt == 0 { + return NewAppError("Emoji.IsValid", "model.emoji.create_at.app_error", nil, "id="+emoji.Id, http.StatusBadRequest) + } + + if emoji.UpdateAt == 0 { + return NewAppError("Emoji.IsValid", "model.emoji.update_at.app_error", nil, "id="+emoji.Id, http.StatusBadRequest) + } + + if len(emoji.CreatorId) > 26 { + return NewAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "", http.StatusBadRequest) + } + + return IsValidEmojiName(emoji.Name) +} + +func IsValidEmojiName(name string) *AppError { + if len(name) == 0 || len(name) > EMOJI_NAME_MAX_LENGTH || !IsValidAlphaNumHyphenUnderscore(name, false) || inSystemEmoji(name) { + return NewAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (emoji *Emoji) PreSave() { + if emoji.Id == "" { + emoji.Id = NewId() + } + + emoji.CreateAt = GetMillis() + emoji.UpdateAt = emoji.CreateAt +} + +func (emoji *Emoji) ToJson() string { + b, _ := json.Marshal(emoji) + return string(b) +} + +func EmojiFromJson(data io.Reader) *Emoji { + var emoji *Emoji + json.NewDecoder(data).Decode(&emoji) + return emoji +} + +func EmojiListToJson(emojiList []*Emoji) string { + b, _ := json.Marshal(emojiList) + return string(b) +} + +func EmojiListFromJson(data io.Reader) []*Emoji { + var emojiList []*Emoji + json.NewDecoder(data).Decode(&emojiList) + return emojiList +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/emoji_data.go b/vendor/github.com/mattermost/mattermost-server/model/emoji_data.go new file mode 100644 index 00000000..f6e62e68 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/emoji_data.go @@ -0,0 +1,6 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +var SystemEmojis = map[string]string{"policewoman": "1f46e-200d-2640-fe0f", "family_man_girl_medium_skin_tone": "1f468-1f3fd", "man_technologist": "1f468-200d-1f4bb", "family_woman_girl_medium_light_skin_tone": "1f469-1f3fc", "massage_woman_medium_light_skin_tone": "1f486-1f3fc-200d-2640-fe0f", "family_woman_woman_boy": "1f469-200d-1f469-200d-1f466", "rice_scene": "1f391", "notes": "1f3b6", "burundi": "1f1e7-1f1ee", "woman_medium_skin_tone": "1f469-1f3fd", "tipping_hand_man_medium_dark_skin_tone": "1f481-1f3fe-200d-2642-fe0f", "new_moon": "1f311", "belize": "1f1e7-1f1ff", "bhutan": "1f1e7-1f1f9", "eu": "1f1ea-1f1fa", "point_up_dark_skin_tone": "261d-1f3ff", "older_man_medium_light_skin_tone": "1f474-1f3fc", "prince": "1f934", "walking_man": "1f6b6", "telephone_receiver": "1f4de", "arrow_upper_right": "2197-fe0f", "taiwan": "1f1f9-1f1fc", "-1_light_skin_tone": "1f44e-1f3fb", "bear": "1f43b", "derelict_house": "1f3da", "blue_book": "1f4d8", "ok": "1f197", "woman_farmer_medium_light_skin_tone": "1f469-1f3fc", "man_shrugging_light_skin_tone": "1f937-1f3fb-200d-2642-fe0f", "dancing_women": "1f46f", "cd": "1f4bf", "tada": "1f389", "virgo": "264d-fe0f", "white_flower": "1f4ae", "guardswoman_medium_dark_skin_tone": "1f482-1f3fe-200d-2640-fe0f", "performing_arts": "1f3ad", "prayer_beads": "1f4ff", "congo_brazzaville": "1f1e8-1f1ec", "point_down_medium_skin_tone": "1f447-1f3fd", "raised_hand_with_fingers_splayed_light_skin_tone": "1f590-1f3fb", "man_playing_water_polo_medium_skin_tone": "1f93d-1f3fd-200d-2642-fe0f", "four_leaf_clover": "1f340", "microphone": "1f3a4", "heartpulse": "1f497", "north_korea": "1f1f0-1f1f5", "neutral_face": "1f610", "volleyball": "1f3d0", "man_playing_water_polo": "1f93d-200d-2642-fe0f", "uk": "1f1ec-1f1e7", "wallis_futuna": "1f1fc-1f1eb", "earth_africa": "1f30d", "droplet": "1f4a7", "construction_worker_man_medium_dark_skin_tone": "1f477-1f3fe-200d-2640-fe0f", "family_woman_woman_girl_boy_medium_light_skin_tone": "1f469-1f3fc", "mountain_biking_man_medium_dark_skin_tone": "1f6b5-1f3fe-200d-2640-fe0f", "vulcan_salute_light_skin_tone": "1f596-1f3fb", "woman_shrugging_dark_skin_tone": "1f937-1f3ff-200d-2640-fe0f", "walking_man_medium_light_skin_tone": "1f6b6-1f3fc-200d-2640-fe0f", "wave": "1f44b", "framed_picture": "1f5bc", "mag": "1f50d", "fist_left": "1f91b", "building_construction": "1f3d7", "clock9": "1f558", "cayman_islands": "1f1f0-1f1fe", "laos": "1f1f1-1f1e6", "woman_playing_handball_dark_skin_tone": "1f93e-1f3ff-200d-2640-fe0f", "man_office_worker": "1f468-200d-1f4bc", "family_man_woman_girl": "1f468-200d-1f469-200d-1f467", "wilted_flower": "1f940", "books": "1f4da", "rage": "1f621", "rice_ball": "1f359", "desert": "1f3dc", "malta": "1f1f2-1f1f9", "haircut_woman_dark_skin_tone": "1f487-1f3ff-200d-2640-fe0f", "symbols": "1f523", "marshall_islands": "1f1f2-1f1ed", "sierra_leone": "1f1f8-1f1f1", "crossed_fingers_medium_dark_skin_tone": "1f91e-1f3fe", "man_judge_medium_skin_tone": "1f468-1f3fd", "bamboo": "1f38d", "keyboard": "2328-fe0f", "clock10": "1f559", "massage_man_medium_skin_tone": "1f486-1f3fd-200d-2642-fe0f", "tipping_hand_man_dark_skin_tone": "1f481-1f3ff-200d-2642-fe0f", "man_facepalming_light_skin_tone": "1f926-1f3fb-200d-2642-fe0f", "train": "1f68b", "traffic_light": "1f6a5", "vietnam": "1f1fb-1f1f3", "boy_medium_light_skin_tone": "1f466-1f3fc", "man_farmer_light_skin_tone": "1f468-1f3fb", "man_singer_medium_dark_skin_tone": "1f468-1f3fe", "woman_cartwheeling_medium_light_skin_tone": "1f938-1f3fc-200d-2640-fe0f", "top": "1f51d", "gb": "1f1ec-1f1e7", "mouse2": "1f401", "do_not_litter": "1f6af", "south_sudan": "1f1f8-1f1f8", "bowing_woman_light_skin_tone": "1f647-1f3fb-200d-2640-fe0f", "family_man_man_girl_girl_medium_light_skin_tone": "1f468-1f3fc", "japanese_goblin": "1f47a", "camel": "1f42b", "taurus": "2649-fe0f", "mute": "1f507", "woman_mechanic_medium_dark_skin_tone": "1f469-1f3fe", "surfer": "1f3c4", "tipping_hand_man": "1f481-200d-2642-fe0f", "family_woman_woman_boy_boy": "1f469-200d-1f469-200d-1f466-200d-1f466", "floppy_disk": "1f4be", "atm": "1f3e7", "clock230": "1f55d", "prince_light_skin_tone": "1f934-1f3fb", "name_badge": "1f4db", "octocat": "octocat", "family_woman_woman_girl_girl_dark_skin_tone": "1f469-1f3ff", "christmas_tree": "1f384", "waxing_gibbous_moon": "1f314", "mountain_cableway": "1f6a0", "woman_scientist_medium_dark_skin_tone": "1f469-1f3fe", "haircut_man_medium_dark_skin_tone": "1f487-1f3fe-200d-2642-fe0f", "basketball_woman_medium_dark_skin_tone": "26f9-1f3fe-200d-2640-fe0f", "family_man_man_boy_medium_light_skin_tone": "1f468-1f3fc", "rowing_woman_medium_dark_skin_tone": "1f6a3-1f3fe-200d-2640-fe0f", "bowling": "1f3b3", "shinto_shrine": "26e9", "round_pushpin": "1f4cd", "cyprus": "1f1e8-1f1fe", "open_hands_dark_skin_tone": "1f450-1f3ff", "clap_medium_skin_tone": "1f44f-1f3fd", "bath_medium_dark_skin_tone": "1f6c0-1f3fe", "briefcase": "1f4bc", "tiger": "1f42f", "morocco": "1f1f2-1f1e6", "open_hands_light_skin_tone": "1f450-1f3fb", "hand_light_skin_tone": "270b-1f3fb", "weight_lifting_man_medium_dark_skin_tone": "1f3cb-1f3fe-200d-2640-fe0f", "mans_shoe": "1f45e", "poland": "1f1f5-1f1f1", "raised_hands_medium_skin_tone": "1f64c-1f3fd", "family_man_woman_girl_boy_light_skin_tone": "1f468-1f3fb", "woman_playing_handball_medium_skin_tone": "1f93e-1f3fd-200d-2640-fe0f", "office": "1f3e2", "woman_singer_medium_dark_skin_tone": "1f469-1f3fe", "family_woman_woman_boy_boy_medium_light_skin_tone": "1f469-1f3fc", "scorpion": "1f982", "tomato": "1f345", "goal_net": "1f945", "chad": "1f1f9-1f1e9", "family_man_woman_girl_boy_medium_skin_tone": "1f468-1f3fd", "mountain_biking_man_light_skin_tone": "1f6b5-1f3fb-200d-2640-fe0f", "weight_lifting_man_dark_skin_tone": "1f3cb-1f3ff-200d-2640-fe0f", "eyeglasses": "1f453", "golfing_woman": "1f3cc-fe0f-200d-2640-fe0f", "dvd": "1f4c0", "clipboard": "1f4cb", "ireland": "1f1ee-1f1ea", "woman_student_dark_skin_tone": "1f469-1f3ff", "angry": "1f620", "baby": "1f476", "women_wrestling": "1f93c-200d-2640-fe0f", "black_square_button": "1f532", "male_detective_medium_light_skin_tone": "1f575-1f3fc-200d-2640-fe0f", "dancer_dark_skin_tone": "1f483-1f3ff", "id": "1f194", "vibration_mode": "1f4f3", "handshake": "1f91d", "tiger2": "1f405", "leaves": "1f343", "baseball": "26be-fe0f", "golf": "26f3-fe0f", "toilet": "1f6bd", "male_detective_dark_skin_tone": "1f575-1f3ff-200d-2640-fe0f", "family_woman_boy": "1f469-200d-1f466", "duck": "1f986", "writing_hand_medium_dark_skin_tone": "270d-1f3fe", "woman_singer_medium_light_skin_tone": "1f469-1f3fc", "man_teacher_medium_skin_tone": "1f468-1f3fd", "lips": "1f444", "octopus": "1f419", "policeman_medium_dark_skin_tone": "1f46e-1f3fe-200d-2640-fe0f", "man_factory_worker_dark_skin_tone": "1f468-1f3ff", "man_astronaut_light_skin_tone": "1f468-1f3fb", "ok_man_dark_skin_tone": "1f646-1f3ff-200d-2642-fe0f", "couple_with_heart": "1f491", "pray_medium_skin_tone": "1f64f-1f3fd", "woman_health_worker_light_skin_tone": "1f469-1f3fb", "tipping_hand_woman_medium_light_skin_tone": "1f481-1f3fc-200d-2640-fe0f", "no_good_woman_dark_skin_tone": "1f645-1f3ff-200d-2640-fe0f", "no_good_woman_medium_dark_skin_tone": "1f645-1f3fe-200d-2640-fe0f", "thumbsdown": "1f44e", "fist": "270a", "camera_flash": "1f4f8", "azerbaijan": "1f1e6-1f1ff", "woman_with_turban_medium_light_skin_tone": "1f473-1f3fc-200d-2640-fe0f", "man_singer_medium_skin_tone": "1f468-1f3fd", "mali": "1f1f2-1f1f1", "blonde_woman_medium_dark_skin_tone": "1f471-1f3fe-200d-2640-fe0f", "family_man_man_girl_light_skin_tone": "1f468-1f3fb", "biking_woman_medium_skin_tone": "1f6b4-1f3fd-200d-2640-fe0f", "crab": "1f980", "green_salad": "1f957", "men_wrestling": "1f93c-200d-2642-fe0f", "-1_medium_dark_skin_tone": "1f44e-1f3fe", "baby_dark_skin_tone": "1f476-1f3ff", "surfing_woman_light_skin_tone": "1f3c4-1f3fb-200d-2640-fe0f", "stuck_out_tongue_winking_eye": "1f61c", "plate_with_cutlery": "1f37d", "swimmer": "1f3ca", "blonde_woman_medium_light_skin_tone": "1f471-1f3fc-200d-2640-fe0f", "curly_loop": "27b0", "india": "1f1ee-1f1f3", "norfolk_island": "1f1f3-1f1eb", "mountain_biking_woman_dark_skin_tone": "1f6b5-1f3ff-200d-2640-fe0f", "ghost": "1f47b", "boar": "1f417", "railway_track": "1f6e4", "100": "1f4af", "metal_medium_dark_skin_tone": "1f918-1f3fe", "woman_playing_handball_light_skin_tone": "1f93e-1f3fb-200d-2640-fe0f", "barber": "1f488", "clock730": "1f562", "equatorial_guinea": "1f1ec-1f1f6", "maldives": "1f1f2-1f1fb", "weight_lifting_woman_medium_dark_skin_tone": "1f3cb-1f3fe-200d-2640-fe0f", "eagle": "1f985", "tea": "1f375", "tanabata_tree": "1f38b", "night_with_stars": "1f303", "balloon": "1f388", "on": "1f51b", "lizard": "1f98e", "beer": "1f37a", "part_alternation_mark": "303d-fe0f", "white_square_button": "1f533", "clock430": "1f55f", "gibraltar": "1f1ec-1f1ee", "massage_woman_medium_skin_tone": "1f486-1f3fd-200d-2640-fe0f", "sweat": "1f613", "athletic_shoe": "1f45f", "joystick": "1f579", "biohazard": "2623-fe0f", "muscle_medium_light_skin_tone": "1f4aa-1f3fc", "bride_with_veil_medium_light_skin_tone": "1f470-1f3fc", "parasol_on_ground": "26f1", "costa_rica": "1f1e8-1f1f7", "woman_student_light_skin_tone": "1f469-1f3fb", "massage_woman_medium_dark_skin_tone": "1f486-1f3fe-200d-2640-fe0f", "surfing_woman_medium_dark_skin_tone": "1f3c4-1f3fe-200d-2640-fe0f", "rowing_woman": "1f6a3-200d-2640-fe0f", "guardsman_light_skin_tone": "1f482-1f3fb-200d-2640-fe0f", "construction_worker_man_light_skin_tone": "1f477-1f3fb-200d-2640-fe0f", "family_woman_boy_boy": "1f469-200d-1f466-200d-1f466", "small_airplane": "1f6e9", "baggage_claim": "1f6c4", "bosnia_herzegovina": "1f1e7-1f1e6", "falkland_islands": "1f1eb-1f1f0", "crossed_fingers_medium_light_skin_tone": "1f91e-1f3fc", "man_scientist_medium_light_skin_tone": "1f468-1f3fc", "family_woman_girl_boy_medium_skin_tone": "1f469-1f3fd", "satisfied": "1f606", "u5408": "1f234", "cn": "1f1e8-1f1f3", "isle_of_man": "1f1ee-1f1f2", "fist_raised_medium_light_skin_tone": "270a-1f3fc", "family_man_woman_girl_dark_skin_tone": "1f468-1f3ff", "family_woman_girl_dark_skin_tone": "1f469-1f3ff", "family_man_boy_medium_skin_tone": "1f468-1f3fd", "money_mouth_face": "1f911", "syringe": "1f489", "hand_medium_light_skin_tone": "270b-1f3fc", "writing_hand_medium_skin_tone": "270d-1f3fd", "man_farmer_medium_light_skin_tone": "1f468-1f3fc", "woman_artist_dark_skin_tone": "1f469-1f3ff", "tickets": "1f39f", "man_cartwheeling_light_skin_tone": "1f938-1f3fb-200d-2642-fe0f", "squid": "1f991", "fish": "1f41f", "memo": "1f4dd", "eye_speech_bubble": "1f441-200d-1f5e8", "+1_light_skin_tone": "1f44d-1f3fb", "tulip": "1f337", "blossom": "1f33c", "family_woman_woman_girl_medium_dark_skin_tone": "1f469-1f3fe", "triumph": "1f624", "rooster": "1f413", "ng": "1f196", "blonde_man_medium_light_skin_tone": "1f471-1f3fc-200d-2640-fe0f", "policeman_light_skin_tone": "1f46e-1f3fb-200d-2640-fe0f", "woman_cook_dark_skin_tone": "1f469-1f3ff", "pray_dark_skin_tone": "1f64f-1f3ff", "point_up_2_medium_skin_tone": "1f446-1f3fd", "busts_in_silhouette": "1f465", "tornado": "1f32a", "woman_juggling": "1f939-200d-2640-fe0f", "cupid": "1f498", "white_check_mark": "2705", "aruba": "1f1e6-1f1fc", "family_man_boy_boy_medium_dark_skin_tone": "1f468-1f3fe", "woman_cartwheeling_dark_skin_tone": "1f938-1f3ff-200d-2640-fe0f", "woman_playing_water_polo_dark_skin_tone": "1f93d-1f3ff-200d-2640-fe0f", "relaxed": "263a-fe0f", "birthday": "1f382", "high_brightness": "1f506", "couple_with_heart_woman_woman_medium_light_skin_tone": "1f469-1f3fc", "biking_man_dark_skin_tone": "1f6b4-1f3ff-200d-2640-fe0f", "disappointed_relieved": "1f625", "canary_islands": "1f1ee-1f1e8", "st_pierre_miquelon": "1f1f5-1f1f2", "trinidad_tobago": "1f1f9-1f1f9", "turkmenistan": "1f1f9-1f1f2", "pouting_woman_medium_skin_tone": "1f64e-1f3fd-200d-2640-fe0f", "man_student_dark_skin_tone": "1f468-1f3ff", "princess_dark_skin_tone": "1f478-1f3ff", "family_woman_boy_boy_medium_skin_tone": "1f469-1f3fd", "old_key": "1f5dd", "muscle_medium_skin_tone": "1f4aa-1f3fd", "ear_medium_dark_skin_tone": "1f442-1f3fe", "girl_medium_dark_skin_tone": "1f467-1f3fe", "man_pilot_dark_skin_tone": "1f468-1f3ff", "wolf": "1f43a", "gem": "1f48e", "arrow_double_up": "23eb", "woman_factory_worker_light_skin_tone": "1f469-1f3fb", "woman_mechanic_medium_skin_tone": "1f469-1f3fd", "woman_firefighter_light_skin_tone": "1f469-1f3fb", "sunglasses": "1f60e", "snake": "1f40d", "pen": "1f58a", "nose_medium_skin_tone": "1f443-1f3fd", "weight_lifting_man": "1f3cb-fe0f", "alarm_clock": "23f0", "golfing_woman_medium_light_skin_tone": "1f3cc-1f3fc-200d-2640-fe0f", "vulcan_salute": "1f596", "earth_asia": "1f30f", "+1_dark_skin_tone": "1f44d-1f3ff", "family_woman_boy_medium_dark_skin_tone": "1f469-1f3fe", "family_woman_girl_girl_light_skin_tone": "1f469-1f3fb", "weight_lifting_man_medium_light_skin_tone": "1f3cb-1f3fc-200d-2640-fe0f", "angel": "1f47c", "peach": "1f351", "truck": "1f69a", "tajikistan": "1f1f9-1f1ef", "tr": "1f1f9-1f1f7", "running_woman_medium_skin_tone": "1f3c3-1f3fd-200d-2640-fe0f", "wrench": "1f527", "black_flag": "1f3f4", "cape_verde": "1f1e8-1f1fb", "man_technologist_medium_light_skin_tone": "1f468-1f3fc", "mountain_biking_woman_medium_light_skin_tone": "1f6b5-1f3fc-200d-2640-fe0f", "man_astronaut_medium_skin_tone": "1f468-1f3fd", "man_in_tuxedo_medium_light_skin_tone": "1f935-1f3fc", "see_no_evil": "1f648", "egg": "1f95a", "1234": "1f522", "lesotho": "1f1f1-1f1f8", "middle_finger_medium_skin_tone": "1f595-1f3fd", "woman_health_worker_medium_light_skin_tone": "1f469-1f3fc", "sneezing_face": "1f927", "man_cook": "1f468-200d-1f373", "mortar_board": "1f393", "candle": "1f56f", "basketball_man_medium_skin_tone": "26f9-1f3fd-200d-2640-fe0f", "ferris_wheel": "1f3a1", "martinique": "1f1f2-1f1f6", "st_vincent_grenadines": "1f1fb-1f1e8", "yemen": "1f1fe-1f1ea", "pray_light_skin_tone": "1f64f-1f3fb", "man_in_tuxedo_medium_skin_tone": "1f935-1f3fd", "woman_pilot_medium_dark_skin_tone": "1f469-1f3fe", "pregnant_woman_dark_skin_tone": "1f930-1f3ff", "fu": "1f595", "haircut": "1f487", "boxing_glove": "1f94a", "page_with_curl": "1f4c3", "muscle_light_skin_tone": "1f4aa-1f3fb", "woman_firefighter_dark_skin_tone": "1f469-1f3ff", "ok_woman_light_skin_tone": "1f646-1f3fb-200d-2640-fe0f", "family_man_woman_boy_boy_medium_light_skin_tone": "1f468-1f3fc", "family_woman_boy_dark_skin_tone": "1f469-1f3ff", "weight_lifting_man_light_skin_tone": "1f3cb-1f3fb-200d-2640-fe0f", "blonde_man": "1f471", "woman_technologist": "1f469-200d-1f4bb", "boom": "1f4a5", "1st_place_medal": "1f947", "nine": "0039-fe0f-20e3", "czech_republic": "1f1e8-1f1ff", "meat_on_bone": "1f356", "hamburger": "1f354", "video_game": "1f3ae", "clock2": "1f551", "woman_facepalming_dark_skin_tone": "1f926-1f3ff-200d-2640-fe0f", "couplekiss_man_man_medium_dark_skin_tone": "1f468-1f3fe", "arrow_lower_right": "2198-fe0f", "haircut_woman_light_skin_tone": "1f487-1f3fb-200d-2640-fe0f", "woman_dark_skin_tone": "1f469-1f3ff", "older_man_light_skin_tone": "1f474-1f3fb", "first_quarter_moon_with_face": "1f31b", "fries": "1f35f", "restroom": "1f6bb", "zero": "0030-fe0f-20e3", "fr": "1f1eb-1f1f7", "kuwait": "1f1f0-1f1fc", "man_health_worker_medium_skin_tone": "1f468-1f3fd", "woman_judge_light_skin_tone": "1f469-1f3fb", "man_judge_dark_skin_tone": "1f468-1f3ff", "ok_woman_medium_skin_tone": "1f646-1f3fd-200d-2640-fe0f", "bike": "1f6b2", "registered": "00ae-fe0f", "blonde_woman_medium_skin_tone": "1f471-1f3fd-200d-2640-fe0f", "stuck_out_tongue_closed_eyes": "1f61d", "collision": "1f4a5", "wheelchair": "267f-fe0f", "black_circle": "26ab-fe0f", "point_up_2_light_skin_tone": "1f446-1f3fb", "older_man": "1f474", "suspension_railway": "1f69f", "libra": "264e-fe0f", "crossed_flags": "1f38c", "man_cartwheeling_medium_dark_skin_tone": "1f938-1f3fe-200d-2642-fe0f", "man_playing_water_polo_dark_skin_tone": "1f93d-1f3ff-200d-2642-fe0f", "scream": "1f631", "no_good_man": "1f645-200d-2642-fe0f", "timer_clock": "23f2", "venezuela": "1f1fb-1f1ea", "raised_back_of_hand_light_skin_tone": "1f91a-1f3fb", "woman_technologist_medium_skin_tone": "1f469-1f3fd", "popcorn": "1f37f", "romania": "1f1f7-1f1f4", "togo": "1f1f9-1f1ec", "writing_hand_dark_skin_tone": "270d-1f3ff", "woman_singer_dark_skin_tone": "1f469-1f3ff", "pouting_woman_medium_dark_skin_tone": "1f64e-1f3fe-200d-2640-fe0f", "man_health_worker_light_skin_tone": "1f468-1f3fb", "dancer_medium_light_skin_tone": "1f483-1f3fc", "phone": "260e-fe0f", "chart": "1f4b9", "repeat": "1f501", "mahjong": "1f004-fe0f", "liberia": "1f1f1-1f1f7", "rage3": "rage3", "person_frowning": "1f64d", "open_hands_medium_skin_tone": "1f450-1f3fd", "man_dark_skin_tone": "1f468-1f3ff", "man_factory_worker_medium_light_skin_tone": "1f468-1f3fc", "man_astronaut_medium_light_skin_tone": "1f468-1f3fc", "ring": "1f48d", "ok_hand_medium_skin_tone": "1f44c-1f3fd", "santa": "1f385", "beach_umbrella": "1f3d6", "finland": "1f1eb-1f1ee", "woman_facepalming_medium_skin_tone": "1f926-1f3fd-200d-2640-fe0f", "woman_shrugging_medium_light_skin_tone": "1f937-1f3fc-200d-2640-fe0f", "sunflower": "1f33b", "ok_hand_dark_skin_tone": "1f44c-1f3ff", "santa_medium_skin_tone": "1f385-1f3fd", "call_me_hand_medium_skin_tone": "1f919-1f3fd", "man_firefighter_light_skin_tone": "1f468-1f3fb", "kiss": "1f48b", "mandarin": "1f34a", "dollar": "1f4b5", "clock3": "1f552", "argentina": "1f1e6-1f1f7", "fist_left_light_skin_tone": "1f91b-1f3fb", "santa_light_skin_tone": "1f385-1f3fb", "family_man_girl_boy_medium_light_skin_tone": "1f468-1f3fc", "sushi": "1f363", "rice": "1f35a", "mailbox_with_mail": "1f4ec", "woman_cook_medium_dark_skin_tone": "1f469-1f3fe", "family_man_woman_girl_girl_medium_dark_skin_tone": "1f468-1f3fe", "straight_ruler": "1f4cf", "blue_heart": "1f499", "slightly_frowning_face": "1f641", "crossed_fingers": "1f91e", "seedling": "1f331", "herb": "1f33f", "medal_military": "1f396", "camping": "1f3d5", "arrow_backward": "25c0-fe0f", "heavy_multiplication_x": "2716-fe0f", "icecream": "1f366", "heavy_dollar_sign": "1f4b2", "frowning_woman_dark_skin_tone": "1f64d-1f3ff-200d-2640-fe0f", "family_man_woman_boy_boy_medium_dark_skin_tone": "1f468-1f3fe", "brazil": "1f1e7-1f1f7", "fist_right_medium_light_skin_tone": "1f91c-1f3fc", "man_scientist_medium_dark_skin_tone": "1f468-1f3fe", "family_woman_girl_light_skin_tone": "1f469-1f3fb", "swimming_man_medium_dark_skin_tone": "1f3ca-1f3fe-200d-2640-fe0f", "man": "1f468", "lemon": "1f34b", "japanese_castle": "1f3ef", "cinema": "1f3a6", "wave_light_skin_tone": "1f44b-1f3fb", "middle_finger_medium_light_skin_tone": "1f595-1f3fc", "five": "0035-fe0f-20e3", "boy_medium_dark_skin_tone": "1f466-1f3fe", "woman_technologist_dark_skin_tone": "1f469-1f3ff", "man_playing_handball_light_skin_tone": "1f93e-1f3fb-200d-2642-fe0f", "construction_worker_man": "1f477", "stadium": "1f3df", "biking_woman_dark_skin_tone": "1f6b4-1f3ff-200d-2640-fe0f", "trophy": "1f3c6", "arrow_left": "2b05-fe0f", "boy_dark_skin_tone": "1f466-1f3ff", "no_good_woman_light_skin_tone": "1f645-1f3fb-200d-2640-fe0f", "skull_and_crossbones": "2620-fe0f", "couple_with_heart_woman_woman": "1f469-200d-2764-fe0f-200d-1f469", "no_bicycles": "1f6b3", "bell": "1f514", "feelsgood": "feelsgood", "bowing_man_dark_skin_tone": "1f647-1f3ff-200d-2640-fe0f", "mouse": "1f42d", "anchor": "2693-fe0f", "cyclone": "1f300", "solomon_islands": "1f1f8-1f1e7", "basketball": "1f3c0", "notebook_with_decorative_cover": "1f4d4", "family_man_girl_girl_dark_skin_tone": "1f468-1f3ff", "singapore": "1f1f8-1f1ec", "golfing_man_medium_dark_skin_tone": "1f3cc-1f3fe-200d-2640-fe0f", "woman": "1f469", "fried_shrimp": "1f364", "construction": "1f6a7", "eight_pointed_black_star": "2734-fe0f", "black_joker": "1f0cf", "cambodia": "1f1f0-1f1ed", "mount_fuji": "1f5fb", "link": "1f517", "womens": "1f6ba", "family_man_man_boy_boy_medium_skin_tone": "1f468-1f3fd", "joy": "1f602", "crying_cat_face": "1f63f", "parking": "1f17f-fe0f", "barbados": "1f1e7-1f1e7", "bowing_woman_dark_skin_tone": "1f647-1f3ff-200d-2640-fe0f", "angel_medium_light_skin_tone": "1f47c-1f3fc", "waning_gibbous_moon": "1f316", "synagogue": "1f54d", "american_samoa": "1f1e6-1f1f8", "basketball_woman_medium_light_skin_tone": "26f9-1f3fc-200d-2640-fe0f", "bug": "1f41b", "woman_farmer_light_skin_tone": "1f469-1f3fb", "door": "1f6aa", "place_of_worship": "1f6d0", "eight_spoked_asterisk": "2733-fe0f", "mrs_claus_dark_skin_tone": "1f936-1f3ff", "u7a7a": "1f233", "man_astronaut_medium_dark_skin_tone": "1f468-1f3fe", "jack_o_lantern": "1f383", "lock_with_ink_pen": "1f50f", "male_detective_medium_skin_tone": "1f575-1f3fd-200d-2640-fe0f", "woman_firefighter_medium_dark_skin_tone": "1f469-1f3fe", "smiling_imp": "1f608", "tv": "1f4fa", "pouting_man": "1f64e-200d-2642-fe0f", "e-mail": "1f4e7", "package": "1f4e6", "clock130": "1f55c", "family_man_boy_light_skin_tone": "1f468-1f3fb", "cat2": "1f408", "mountain_biking_woman_medium_dark_skin_tone": "1f6b5-1f3fe-200d-2640-fe0f", "nauseated_face": "1f922", "fountain": "26f2-fe0f", "middle_finger": "1f595", "dancers": "1f46f", "cactus": "1f335", "man_student_light_skin_tone": "1f468-1f3fb", "family_man_man_girl_dark_skin_tone": "1f468-1f3ff", "family_man_girl_girl_medium_skin_tone": "1f468-1f3fd", "us_virgin_islands": "1f1fb-1f1ee", "woman_astronaut_medium_dark_skin_tone": "1f469-1f3fe", "honeybee": "1f41d", "bouquet": "1f490", "golfing_man": "1f3cc-fe0f", "u7981": "1f232", "french_guiana": "1f1ec-1f1eb", "kenya": "1f1f0-1f1ea", "melon": "1f348", "nicaragua": "1f1f3-1f1ee", "raised_hand_with_fingers_splayed_medium_light_skin_tone": "1f590-1f3fc", "bath_light_skin_tone": "1f6c0-1f3fb", "man_pilot_medium_light_skin_tone": "1f468-1f3fc", "european_post_office": "1f3e4", "mobile_phone_off": "1f4f4", "no_smoking": "1f6ad", "family_woman_girl_girl_medium_light_skin_tone": "1f469-1f3fc", "family_woman_girl_medium_dark_skin_tone": "1f469-1f3fe", "man_juggling_medium_dark_skin_tone": "1f939-1f3fe-200d-2642-fe0f", "expressionless": "1f611", "school_satchel": "1f392", "film_strip": "1f39e", "running_man_light_skin_tone": "1f3c3-1f3fb-200d-2640-fe0f", "family_man_woman_girl_medium_dark_skin_tone": "1f468-1f3fe", "family_woman_woman_boy_boy_light_skin_tone": "1f469-1f3fb", "smiley_cat": "1f63a", "chestnut": "1f330", "girl_dark_skin_tone": "1f467-1f3ff", "bride_with_veil_medium_dark_skin_tone": "1f470-1f3fe", "family_man_man_boy_boy_light_skin_tone": "1f468-1f3fb", "family_man_boy_boy_medium_light_skin_tone": "1f468-1f3fc", "wink": "1f609", "carrot": "1f955", "credit_card": "1f4b3", "triangular_ruler": "1f4d0", "question": "2753", "+1_medium_dark_skin_tone": "1f44d-1f3fe", "man_teacher_medium_dark_skin_tone": "1f468-1f3fe", "family_man_girl_girl_medium_light_skin_tone": "1f468-1f3fc", "kimono": "1f458", "bellhop_bell": "1f6ce", "red_circle": "1f534", "call_me_hand_light_skin_tone": "1f919-1f3fb", "nail_care_medium_skin_tone": "1f485-1f3fd", "woman_teacher_medium_skin_tone": "1f469-1f3fd", "woman_juggling_dark_skin_tone": "1f939-1f3ff-200d-2640-fe0f", "runner": "1f3c3", "heavy_check_mark": "2714-fe0f", "family_man_girl_girl_medium_dark_skin_tone": "1f468-1f3fe", "tent": "26fa-fe0f", "card_index_dividers": "1f5c2", "man_singer_dark_skin_tone": "1f468-1f3ff", "man_firefighter_medium_dark_skin_tone": "1f468-1f3fe", "man_playing_handball_medium_dark_skin_tone": "1f93e-1f3fe-200d-2642-fe0f", "female_detective_medium_dark_skin_tone": "1f575-1f3fe-200d-2640-fe0f", "metal": "1f918", "dark_sunglasses": "1f576", "vertical_traffic_light": "1f6a6", "four": "0034-fe0f-20e3", "wavy_dash": "3030-fe0f", "ear_medium_light_skin_tone": "1f442-1f3fc", "man_juggling_dark_skin_tone": "1f939-1f3ff-200d-2642-fe0f", "kissing_heart": "1f618", "sweet_potato": "1f360", "gift_heart": "1f49d", "man_technologist_light_skin_tone": "1f468-1f3fb", "prince_medium_dark_skin_tone": "1f934-1f3fe", "ok_man_medium_skin_tone": "1f646-1f3fd-200d-2642-fe0f", "womans_clothes": "1f45a", "roller_coaster": "1f3a2", "woman_student_medium_light_skin_tone": "1f469-1f3fc", "zipper_mouth_face": "1f910", "person_with_blond_hair": "1f471", "leftwards_arrow_with_hook": "21a9-fe0f", "white_circle": "26aa-fe0f", "afghanistan": "1f1e6-1f1eb", "face_with_thermometer": "1f912", "bow": "1f647", "kr": "1f1f0-1f1f7", "finnadie": "finnadie", "girl_light_skin_tone": "1f467-1f3fb", "woman_farmer_medium_skin_tone": "1f469-1f3fd", "umbrella": "2614-fe0f", "ice_cream": "1f368", "point_down_medium_light_skin_tone": "1f447-1f3fc", "woman_health_worker_dark_skin_tone": "1f469-1f3ff", "rowing_woman_medium_light_skin_tone": "1f6a3-1f3fc-200d-2640-fe0f", "money_with_wings": "1f4b8", "dolls": "1f38e", "surfing_man_medium_dark_skin_tone": "1f3c4-1f3fe-200d-2640-fe0f", "mrs_claus_medium_skin_tone": "1f936-1f3fd", "basketball_man_dark_skin_tone": "26f9-1f3ff-200d-2640-fe0f", "dragon_face": "1f432", "woman_cartwheeling": "1f938-200d-2640-fe0f", "aquarius": "2652-fe0f", "sos": "1f198", "clock1230": "1f567", "haiti": "1f1ed-1f1f9", "woman_facepalming": "1f926-200d-2640-fe0f", "clinking_glasses": "1f942", "trollface": "trollface", "mrs_claus_light_skin_tone": "1f936-1f3fb", "clap": "1f44f", "couplekiss_man_man": "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", "page_facing_up": "1f4c4", "belgium": "1f1e7-1f1ea", "curacao": "1f1e8-1f1fc", "family_woman_woman_boy_dark_skin_tone": "1f469-1f3ff", "two_men_holding_hands": "1f46c", "mountain_snow": "1f3d4", "wind_chime": "1f390", "person_with_pouting_face": "1f64e", "cityscape": "1f3d9", "bride_with_veil_dark_skin_tone": "1f470-1f3ff", "frowning_woman_light_skin_tone": "1f64d-1f3fb-200d-2640-fe0f", "bath_medium_light_skin_tone": "1f6c0-1f3fc", "sheep": "1f411", "sparkler": "1f387", "frowning_woman": "1f64d", "rat": "1f400", "custard": "1f36e", "video_camera": "1f4f9", "open_umbrella": "2602-fe0f", "man_with_turban_medium_light_skin_tone": "1f473-1f3fc-200d-2640-fe0f", "woman_student_medium_dark_skin_tone": "1f469-1f3fe", "ok_woman_medium_light_skin_tone": "1f646-1f3fc-200d-2640-fe0f", "swimming_man_dark_skin_tone": "1f3ca-1f3ff-200d-2640-fe0f", "man_cook_light_skin_tone": "1f468-1f3fb", "running_woman_light_skin_tone": "1f3c3-1f3fb-200d-2640-fe0f", "rabbit": "1f430", "ox": "1f402", "corn": "1f33d", "mozambique": "1f1f2-1f1ff", "point_right_light_skin_tone": "1f449-1f3fb", "nail_care_light_skin_tone": "1f485-1f3fb", "smiley": "1f603", "new_moon_with_face": "1f31a", "croatia": "1f1ed-1f1f7", "man_judge_medium_dark_skin_tone": "1f468-1f3fe", "fist_raised": "270a", "man_astronaut": "1f468-200d-1f680", "clock1130": "1f566", "st_lucia": "1f1f1-1f1e8", "princess": "1f478", "fist_left_medium_light_skin_tone": "1f91b-1f3fc", "point_left_medium_dark_skin_tone": "1f448-1f3fe", "woman_factory_worker_medium_skin_tone": "1f469-1f3fd", "angel_dark_skin_tone": "1f47c-1f3ff", "woman_cook": "1f469-200d-1f373", "koala": "1f428", "satellite": "1f4e1", "book": "1f4d6", "large_orange_diamond": "1f536", "monaco": "1f1f2-1f1e8", "spiral_notepad": "1f5d2", "capricorn": "2651-fe0f", "bacon": "1f953", "blonde_man_medium_dark_skin_tone": "1f471-1f3fe-200d-2640-fe0f", "business_suit_levitating_dark_skin_tone": "1f574-1f3ff", "call_me_hand_medium_light_skin_tone": "1f919-1f3fc", "female_detective_medium_light_skin_tone": "1f575-1f3fc-200d-2640-fe0f", "haircut_woman_medium_light_skin_tone": "1f487-1f3fc-200d-2640-fe0f", "alien": "1f47d", "baguette_bread": "1f956", "northern_mariana_islands": "1f1f2-1f1f5", "ukraine": "1f1fa-1f1e6", "flushed": "1f633", "man_scientist": "1f468-200d-1f52c", "trident": "1f531", "family_woman_woman_girl_boy_medium_skin_tone": "1f469-1f3fd", "family_man_boy_boy": "1f468-200d-1f466-200d-1f466", "tennis": "1f3be", "fire_engine": "1f692", "pushpin": "1f4cc", "man_health_worker_medium_dark_skin_tone": "1f468-1f3fe", "boy": "1f466", "headphones": "1f3a7", "fuelpump": "26fd-fe0f", "u6709": "1f236", "man_cook_medium_skin_tone": "1f468-1f3fd", "bride_with_veil_medium_skin_tone": "1f470-1f3fd", "point_up": "261d-fe0f", "necktie": "1f454", "control_knobs": "1f39b", "austria": "1f1e6-1f1f9", "papua_new_guinea": "1f1f5-1f1ec", "alembic": "2697-fe0f", "cook_islands": "1f1e8-1f1f0", "iceland": "1f1ee-1f1f8", "car": "1f697", "potable_water": "1f6b0", "haircut_man_medium_light_skin_tone": "1f487-1f3fc-200d-2642-fe0f", "couplekiss_woman_woman_medium_light_skin_tone": "1f469-1f3fc", "couplekiss_man_man_medium_skin_tone": "1f468-1f3fd", "cookie": "1f36a", "flight_departure": "1f6eb", "muscle_dark_skin_tone": "1f4aa-1f3ff", "construction_worker_man_medium_skin_tone": "1f477-1f3fd-200d-2640-fe0f", "black_medium_small_square": "25fe-fe0f", "guyana": "1f1ec-1f1fe", "file_folder": "1f4c1", "fountain_pen": "1f58b", "construction_worker_woman_medium_skin_tone": "1f477-1f3fd-200d-2640-fe0f", "family_man_woman_girl_boy_medium_light_skin_tone": "1f468-1f3fc", "poultry_leg": "1f357", "ski": "1f3bf", "guardswoman_medium_skin_tone": "1f482-1f3fd-200d-2640-fe0f", "family_man_man_girl_medium_dark_skin_tone": "1f468-1f3fe", "family_woman_boy_light_skin_tone": "1f469-1f3fb", "trumpet": "1f3ba", "no_pedestrians": "1f6b7", "heavy_minus_sign": "2796", "fist_oncoming": "1f44a", "ambulance": "1f691", "man_artist_dark_skin_tone": "1f468-1f3ff", "drum": "1f941", "train2": "1f686", "u7121": "1f21a-fe0f", "burkina_faso": "1f1e7-1f1eb", "nose_light_skin_tone": "1f443-1f3fb", "policewoman_medium_dark_skin_tone": "1f46e-1f3fe-200d-2640-fe0f", "lantern": "1f3ee", "metal_light_skin_tone": "1f918-1f3fb", "male_detective_light_skin_tone": "1f575-1f3fb-200d-2640-fe0f", "woman_juggling_medium_dark_skin_tone": "1f939-1f3fe-200d-2640-fe0f", "rowing_man_light_skin_tone": "1f6a3-1f3fb-200d-2640-fe0f", "lying_face": "1f925", "point_left": "1f448", "rosette": "1f3f5", "houses": "1f3d8", "repeat_one": "1f502", "liechtenstein": "1f1f1-1f1ee", "cloud_with_lightning": "1f329", "man_cartwheeling": "1f938-200d-2642-fe0f", "pause_button": "23f8", "arrows_clockwise": "1f503", "raised_hand_with_fingers_splayed_dark_skin_tone": "1f590-1f3ff", "clap_dark_skin_tone": "1f44f-1f3ff", "raising_hand_man_medium_skin_tone": "1f64b-1f3fd-200d-2642-fe0f", "family_woman_woman_girl_girl_medium_dark_skin_tone": "1f469-1f3fe", "dog": "1f436", "pouting_man_medium_dark_skin_tone": "1f64e-1f3fe-200d-2642-fe0f", "surfing_woman_medium_skin_tone": "1f3c4-1f3fd-200d-2640-fe0f", "confused": "1f615", "detective": "1f575-fe0f", "studio_microphone": "1f399", "fist_oncoming_medium_light_skin_tone": "1f44a-1f3fc", "man_firefighter_medium_skin_tone": "1f468-1f3fd", "tshirt": "1f455", "trolleybus": "1f68e", "norway": "1f1f3-1f1f4", "neckbeard": "neckbeard", "three": "0033-fe0f-20e3", "point_right_medium_skin_tone": "1f449-1f3fd", "man_medium_dark_skin_tone": "1f468-1f3fe", "pouting_man_medium_light_skin_tone": "1f64e-1f3fc-200d-2642-fe0f", "clock630": "1f561", "fist_raised_medium_skin_tone": "270a-1f3fd", "anguished": "1f627", "eye": "1f441", "bride_with_veil": "1f470", "hear_no_evil": "1f649", "wine_glass": "1f377", "soon": "1f51c", "family_man_boy_boy_light_skin_tone": "1f468-1f3fb", "family_woman_boy_boy_medium_light_skin_tone": "1f469-1f3fc", "dromedary_camel": "1f42a", "chipmunk": "1f43f", "soccer": "26bd-fe0f", "man_with_gua_pi_mao_medium_light_skin_tone": "1f472-1f3fc", "business_suit_levitating_medium_light_skin_tone": "1f574-1f3fc", "running_woman_medium_dark_skin_tone": "1f3c3-1f3fe-200d-2640-fe0f", "speaking_head": "1f5e3", "menorah": "1f54e", "non-potable_water": "1f6b1", "woman_with_turban_dark_skin_tone": "1f473-1f3ff-200d-2640-fe0f", "woman_shrugging_medium_skin_tone": "1f937-1f3fd-200d-2640-fe0f", "frowning_woman_medium_light_skin_tone": "1f64d-1f3fc-200d-2640-fe0f", "biking_man_medium_light_skin_tone": "1f6b4-1f3fc-200d-2640-fe0f", "woman_shrugging": "1f937-200d-2640-fe0f", "arrow_upper_left": "2196-fe0f", "metal_medium_skin_tone": "1f918-1f3fd", "woman_factory_worker_dark_skin_tone": "1f469-1f3ff", "hotsprings": "2668-fe0f", "ear_dark_skin_tone": "1f442-1f3ff", "girl_medium_skin_tone": "1f467-1f3fd", "woman_farmer_dark_skin_tone": "1f469-1f3ff", "man_student_medium_skin_tone": "1f468-1f3fd", "biking_man_medium_skin_tone": "1f6b4-1f3fd-200d-2640-fe0f", "woman_scientist": "1f469-200d-1f52c", "vs": "1f19a", "weight_lifting_man_medium_skin_tone": "1f3cb-1f3fd-200d-2640-fe0f", "arrow_right": "27a1-fe0f", "woman_juggling_medium_light_skin_tone": "1f939-1f3fc-200d-2640-fe0f", "racehorse": "1f40e", "sun_behind_large_cloud": "1f325", "convenience_store": "1f3ea", "namibia": "1f1f3-1f1e6", "raised_hands_medium_dark_skin_tone": "1f64c-1f3fe", "man_judge_medium_light_skin_tone": "1f468-1f3fc", "dancer_medium_skin_tone": "1f483-1f3fd", "fearful": "1f628", "frog": "1f438", "shopping_cart": "1f6d2", "family_man_boy_medium_light_skin_tone": "1f468-1f3fc", "basketball_man_medium_dark_skin_tone": "26f9-1f3fe-200d-2640-fe0f", "oman": "1f1f4-1f1f2", "paraguay": "1f1f5-1f1fe", "horse": "1f434", "tram": "1f68a", "wastebasket": "1f5d1", "yen": "1f4b4", "heavy_exclamation_mark": "2757-fe0f", "arrow_double_down": "23ec", "walking_woman_dark_skin_tone": "1f6b6-1f3ff-200d-2640-fe0f", "shoe": "1f45e", "ear_of_rice": "1f33e", "mountain": "26f0", "uzbekistan": "1f1fa-1f1ff", "baby_light_skin_tone": "1f476-1f3fb", "haircut_woman_medium_dark_skin_tone": "1f487-1f3fe-200d-2640-fe0f", "golfing_woman_medium_skin_tone": "1f3cc-1f3fd-200d-2640-fe0f", "earth_americas": "1f30e", "woman_playing_water_polo_medium_light_skin_tone": "1f93d-1f3fc-200d-2640-fe0f", "walking_woman": "1f6b6-200d-2640-fe0f", "fried_egg": "1f373", "rocket": "1f680", "artificial_satellite": "1f6f0", "man_shrugging_medium_skin_tone": "1f937-1f3fd-200d-2642-fe0f", "golfing_man_dark_skin_tone": "1f3cc-1f3ff-200d-2640-fe0f", "older_woman_medium_skin_tone": "1f475-1f3fd", "man_with_gua_pi_mao_medium_dark_skin_tone": "1f472-1f3fe", "persevere": "1f623", "raising_hand_woman": "1f64b", "pig": "1f437", "european_castle": "1f3f0", "department_store": "1f3ec", "fist_right_light_skin_tone": "1f91c-1f3fb", "raising_hand_woman_dark_skin_tone": "1f64b-1f3ff-200d-2640-fe0f", "paw_prints": "1f43e", "moon": "1f314", "man_medium_skin_tone": "1f468-1f3fd", "rowing_man_dark_skin_tone": "1f6a3-1f3ff-200d-2640-fe0f", "sleepy": "1f62a", "light_rail": "1f688", "peace_symbol": "262e-fe0f", "m": "24c2-fe0f", "woman_pilot_medium_skin_tone": "1f469-1f3fd", "dango": "1f361", "minibus": "1f690", "family_man_man_girl_girl_medium_dark_skin_tone": "1f468-1f3fe", "dizzy_face": "1f635", "bowing_woman": "1f647-200d-2640-fe0f", "pig2": "1f416", "factory": "1f3ed", "small_red_triangle": "1f53a", "ok_man_light_skin_tone": "1f646-1f3fb-200d-2642-fe0f", "two_women_holding_hands": "1f46d", "funeral_urn": "26b1-fe0f", "cocos_islands": "1f1e8-1f1e8", "lipstick": "1f484", "fleur_de_lis": "269c-fe0f", "man_with_gua_pi_mao_dark_skin_tone": "1f472-1f3ff", "woman_factory_worker_medium_dark_skin_tone": "1f469-1f3fe", "no_good_man_medium_light_skin_tone": "1f645-1f3fc-200d-2642-fe0f", "horse_racing_medium_dark_skin_tone": "1f3c7-1f3fe", "clock1030": "1f565", "couplekiss_man_man_dark_skin_tone": "1f468-1f3ff", "frowning_man": "1f64d-200d-2642-fe0f", "family_woman_boy_boy_dark_skin_tone": "1f469-1f3ff", "family_man_girl_boy_light_skin_tone": "1f468-1f3fb", "smile": "1f604", "clock7": "1f556", "massage_man": "1f486-200d-2642-fe0f", "guardswoman_dark_skin_tone": "1f482-1f3ff-200d-2640-fe0f", "raising_hand_man_dark_skin_tone": "1f64b-1f3ff-200d-2642-fe0f", "woman_with_turban_medium_dark_skin_tone": "1f473-1f3fe-200d-2640-fe0f", "worried": "1f61f", "no_good": "1f645", "card_index": "1f4c7", "aland_islands": "1f1e6-1f1fd", "lion": "1f981", "hammer": "1f528", "bomb": "1f4a3", "reunion": "1f1f7-1f1ea", "walking_man_light_skin_tone": "1f6b6-1f3fb-200d-2640-fe0f", "family_woman_boy_medium_light_skin_tone": "1f469-1f3fc", "pouting_cat": "1f63e", "cow": "1f42e", "motor_scooter": "1f6f5", "hong_kong": "1f1ed-1f1f0", "family_man_girl_medium_dark_skin_tone": "1f468-1f3fe", "sailboat": "26f5-fe0f", "fiji": "1f1eb-1f1ef", "raised_hands_medium_light_skin_tone": "1f64c-1f3fc", "woman_office_worker_dark_skin_tone": "1f469-1f3ff", "family_man_woman_girl_girl_medium_light_skin_tone": "1f468-1f3fc", "arrow_up": "2b06-fe0f", "walking_woman_medium_light_skin_tone": "1f6b6-1f3fc-200d-2640-fe0f", "nose_medium_light_skin_tone": "1f443-1f3fc", "basketball_woman": "26f9-fe0f-200d-2640-fe0f", "+1_medium_light_skin_tone": "1f44d-1f3fc", "crossed_fingers_medium_skin_tone": "1f91e-1f3fd", "raised_back_of_hand_dark_skin_tone": "1f91a-1f3ff", "swimming_woman_medium_light_skin_tone": "1f3ca-1f3fc-200d-2640-fe0f", "construction_worker_woman": "1f477-200d-2640-fe0f", "rugby_football": "1f3c9", "micronesia": "1f1eb-1f1f2", "point_up_2_medium_light_skin_tone": "1f446-1f3fc", "running_man_dark_skin_tone": "1f3c3-1f3ff-200d-2640-fe0f", "woman_playing_handball_medium_light_skin_tone": "1f93e-1f3fc-200d-2640-fe0f", "speaker": "1f508", "jersey": "1f1ef-1f1ea", "laughing": "1f606", "pregnant_woman": "1f930", "haircut_woman": "1f487", "blue_car": "1f699", "microscope": "1f52c", "postbox": "1f4ee", "man_firefighter_dark_skin_tone": "1f468-1f3ff", "sunny": "2600-fe0f", "beginner": "1f530", "clap_medium_light_skin_tone": "1f44f-1f3fc", "man_with_turban_dark_skin_tone": "1f473-1f3ff-200d-2640-fe0f", "rotating_light": "1f6a8", "saudi_arabia": "1f1f8-1f1e6", "family_woman_woman_girl_girl_medium_skin_tone": "1f469-1f3fd", "family_woman_girl_boy_light_skin_tone": "1f469-1f3fb", "man_with_gua_pi_mao": "1f472", "electric_plug": "1f50c", "panama": "1f1f5-1f1e6", "family_woman_woman_girl_light_skin_tone": "1f469-1f3fb", "thinking": "1f914", "point_down": "1f447", "spider": "1f577", "cloud_with_lightning_and_rain": "26c8", "ice_skate": "26f8", "ok_man_medium_dark_skin_tone": "1f646-1f3fe-200d-2642-fe0f", "netherlands": "1f1f3-1f1f1", "family_man_woman_boy": "1f46a", "orange": "1f34a", "snowboarder": "1f3c2", "passenger_ship": "1f6f3", "arrows_counterclockwise": "1f504", "tractor": "1f69c", "gambia": "1f1ec-1f1f2", "middle_finger_dark_skin_tone": "1f595-1f3ff", "tipping_hand_woman_medium_dark_skin_tone": "1f481-1f3fe-200d-2640-fe0f", "family_man_man_girl_boy_medium_light_skin_tone": "1f468-1f3fc", "thumbsup": "1f44d", "couple": "1f46b", "pouch": "1f45d", "asterisk": "002a-fe0f-20e3", "anguilla": "1f1e6-1f1ee", "woman_cook_light_skin_tone": "1f469-1f3fb", "kissing_cat": "1f63d", "nose": "1f443", "point_left_medium_skin_tone": "1f448-1f3fd", "baby_chick": "1f424", "deciduous_tree": "1f333", "u7533": "1f238", "surfing_woman_dark_skin_tone": "1f3c4-1f3ff-200d-2640-fe0f", "woman_shrugging_medium_dark_skin_tone": "1f937-1f3fe-200d-2640-fe0f", "family_woman_woman_boy_boy_dark_skin_tone": "1f469-1f3ff", "cloud_with_rain": "1f327", "oden": "1f362", "botswana": "1f1e7-1f1fc", "greenland": "1f1ec-1f1f1", "man_office_worker_light_skin_tone": "1f468-1f3fb", "raising_hand_woman_medium_dark_skin_tone": "1f64b-1f3fe-200d-2640-fe0f", "family_man_man_girl_boy_medium_dark_skin_tone": "1f468-1f3fe", "school": "1f3eb", "woman_astronaut_light_skin_tone": "1f469-1f3fb", "woman_judge_medium_skin_tone": "1f469-1f3fd", "dancing_men": "1f46f-200d-2642-fe0f", "paperclips": "1f587", "underage": "1f51e", "ok_woman_dark_skin_tone": "1f646-1f3ff-200d-2640-fe0f", "man_playing_handball_dark_skin_tone": "1f93e-1f3ff-200d-2642-fe0f", "family_man_girl_girl": "1f468-200d-1f467-200d-1f467", "wind_face": "1f32c", "banana": "1f34c", "eight": "0038-fe0f-20e3", "man_technologist_medium_dark_skin_tone": "1f468-1f3fe", "man_office_worker_medium_skin_tone": "1f468-1f3fd", "walking_man_dark_skin_tone": "1f6b6-1f3ff-200d-2640-fe0f", "family_man_man_girl_girl_medium_skin_tone": "1f468-1f3fd", "snowman": "26c4-fe0f", "basketball_man": "26f9-fe0f", "information_source": "2139-fe0f", "cote_divoire": "1f1e8-1f1ee", "man_in_tuxedo_light_skin_tone": "1f935-1f3fb", "walking_woman_light_skin_tone": "1f6b6-1f3fb-200d-2640-fe0f", "woman_playing_water_polo_light_skin_tone": "1f93d-1f3fb-200d-2640-fe0f", "bird": "1f426", "o": "2b55-fe0f", "family_woman_girl_medium_skin_tone": "1f469-1f3fd", "rowing_woman_dark_skin_tone": "1f6a3-1f3ff-200d-2640-fe0f", "facepunch": "1f44a", "railway_car": "1f683", "wave_dark_skin_tone": "1f44b-1f3ff", "man_cook_medium_dark_skin_tone": "1f468-1f3fe", "prince_medium_light_skin_tone": "1f934-1f3fc", "cowboy_hat_face": "1f920", "handbag": "1f45c", "hourglass": "231b-fe0f", "albania": "1f1e6-1f1f1", "chile": "1f1e8-1f1f1", "woman_singer_medium_skin_tone": "1f469-1f3fd", "ear_medium_skin_tone": "1f442-1f3fd", "pouting_man_medium_skin_tone": "1f64e-1f3fd-200d-2642-fe0f", "surfing_man_medium_light_skin_tone": "1f3c4-1f3fc-200d-2640-fe0f", "eggplant": "1f346", "next_track_button": "23ed", "gabon": "1f1ec-1f1e6", "western_sahara": "1f1ea-1f1ed", "raised_hands_light_skin_tone": "1f64c-1f3fb", "older_woman_medium_light_skin_tone": "1f475-1f3fc", "joy_cat": "1f639", "feet": "1f43e", "partly_sunny": "26c5-fe0f", "pig_nose": "1f43d", "wc": "1f6be", "malaysia": "1f1f2-1f1fe", "girl_medium_light_skin_tone": "1f467-1f3fc", "man_office_worker_medium_dark_skin_tone": "1f468-1f3fe", "man_mechanic_medium_light_skin_tone": "1f468-1f3fc", "shamrock": "2618-fe0f", "tumbler_glass": "1f943", "palestinian_territories": "1f1f5-1f1f8", "kissing": "1f617", "city_sunset": "1f306", "pencil2": "270f-fe0f", "cool": "1f192", "australia": "1f1e6-1f1fa", "green_heart": "1f49a", "sparkle": "2747-fe0f", "ng_woman": "1f645", "high_heel": "1f460", "hamster": "1f439", "last_quarter_moon": "1f317", "stopwatch": "23f1", "date": "1f4c5", "nail_care_dark_skin_tone": "1f485-1f3ff", "santa_dark_skin_tone": "1f385-1f3ff", "astonished": "1f632", "mushroom": "1f344", "radio": "1f4fb", "hammer_and_wrench": "1f6e0", "arrow_down": "2b07-fe0f", "speech_balloon": "1f4ac", "couple_with_heart_man_man_medium_skin_tone": "1f468-1f3fd", "euro": "1f4b6", "es": "1f1ea-1f1f8", "woman_factory_worker_medium_light_skin_tone": "1f469-1f3fc", "pouting_woman_dark_skin_tone": "1f64e-1f3ff-200d-2640-fe0f", "massage_woman": "1f486", "spades": "2660-fe0f", "blonde_woman_dark_skin_tone": "1f471-1f3ff-200d-2640-fe0f", "man_farmer_medium_skin_tone": "1f468-1f3fd", "man_mechanic_medium_skin_tone": "1f468-1f3fd", "family_man_boy_dark_skin_tone": "1f468-1f3ff", "man_juggling_medium_light_skin_tone": "1f939-1f3fc-200d-2642-fe0f", "hearts": "2665-fe0f", "clock930": "1f564", "central_african_republic": "1f1e8-1f1eb", "boy_medium_skin_tone": "1f466-1f3fd", "pregnant_woman_medium_skin_tone": "1f930-1f3fd", "woman_facepalming_medium_light_skin_tone": "1f926-1f3fc-200d-2640-fe0f", "palm_tree": "1f334", "rose": "1f339", "beers": "1f37b", "red_car": "1f697", "no_entry": "26d4-fe0f", "candy": "1f36c", "fist_oncoming_medium_skin_tone": "1f44a-1f3fd", "rowing_woman_medium_skin_tone": "1f6a3-1f3fd-200d-2640-fe0f", "sake": "1f376", "oncoming_police_car": "1f694", "woman_teacher_medium_dark_skin_tone": "1f469-1f3fe", "family_man_woman_girl_girl_medium_skin_tone": "1f468-1f3fd", "kissing_closed_eyes": "1f61a", "pager": "1f4df", "pencil": "1f4dd", "copyright": "00a9-fe0f", "wave_medium_skin_tone": "1f44b-1f3fd", "loud_sound": "1f50a", "luxembourg": "1f1f1-1f1fa", "policewoman_dark_skin_tone": "1f46e-1f3ff-200d-2640-fe0f", "woman_cartwheeling_medium_skin_tone": "1f938-1f3fd-200d-2640-fe0f", "swimming_woman_medium_dark_skin_tone": "1f3ca-1f3fe-200d-2640-fe0f", "family_man_man_girl_boy": "1f468-200d-1f468-200d-1f467-200d-1f466", "police_car": "1f693", "mailbox_with_no_mail": "1f4ed", "middle_finger_light_skin_tone": "1f595-1f3fb", "pregnant_woman_medium_light_skin_tone": "1f930-1f3fc", "raising_hand_woman_medium_skin_tone": "1f64b-1f3fd-200d-2640-fe0f", "running": "1f3c3", "sun_with_face": "1f31e", "man_teacher_dark_skin_tone": "1f468-1f3ff", "family_man_woman_girl_girl_dark_skin_tone": "1f468-1f3ff", "izakaya_lantern": "1f3ee", "comoros": "1f1f0-1f1f2", "fist_oncoming_medium_dark_skin_tone": "1f44a-1f3fe", "man_singer": "1f468-200d-1f3a4", "mountain_bicyclist": "1f6b5", "point_down_light_skin_tone": "1f447-1f3fb", "family_man_woman_girl_boy_medium_dark_skin_tone": "1f468-1f3fe", "sob": "1f62d", "ophiuchus": "26ce", "greece": "1f1ec-1f1f7", "raised_back_of_hand_medium_skin_tone": "1f91a-1f3fd", "family_man_man_boy_light_skin_tone": "1f468-1f3fb", "woman_cartwheeling_light_skin_tone": "1f938-1f3fb-200d-2640-fe0f", "massage_woman_light_skin_tone": "1f486-1f3fb-200d-2640-fe0f", "fishing_pole_and_fish": "1f3a3", "two_hearts": "1f495", "armenia": "1f1e6-1f1f2", "south_africa": "1f1ff-1f1e6", "boy_light_skin_tone": "1f466-1f3fb", "man_in_tuxedo_medium_dark_skin_tone": "1f935-1f3fe", "kiribati": "1f1f0-1f1ee", "v_dark_skin_tone": "270c-1f3ff", "frowning_man_medium_light_skin_tone": "1f64d-1f3fc-200d-2642-fe0f", "family_woman_woman_girl_boy": "1f469-200d-1f469-200d-1f467-200d-1f466", "family_woman_girl_boy_medium_dark_skin_tone": "1f469-1f3fe", "leopard": "1f406", "fireworks": "1f386", "clock6": "1f555", "bowing_man_medium_light_skin_tone": "1f647-1f3fc-200d-2640-fe0f", "raising_hand": "1f64b", "family_man_woman_girl_girl_light_skin_tone": "1f468-1f3fb", "vulcan_salute_medium_light_skin_tone": "1f596-1f3fc", "guardswoman_medium_light_skin_tone": "1f482-1f3fc-200d-2640-fe0f", "muscle": "1f4aa", "full_moon": "1f315", "pisces": "2653-fe0f", "kosovo": "1f1fd-1f1f0", "fist_left_dark_skin_tone": "1f91b-1f3ff", "point_up_2_dark_skin_tone": "1f446-1f3ff", "man_technologist_dark_skin_tone": "1f468-1f3ff", "spoon": "1f944", "nigeria": "1f1f3-1f1ec", "raised_back_of_hand_medium_light_skin_tone": "1f91a-1f3fc", "blonde_woman_light_skin_tone": "1f471-1f3fb-200d-2640-fe0f", "man_dancing_light_skin_tone": "1f57a-1f3fb", "shrimp": "1f990", "mountain_biking_man": "1f6b5", "boat": "26f5-fe0f", "egypt": "1f1ea-1f1ec", "family_woman_woman_boy_light_skin_tone": "1f469-1f3fb", "man_playing_water_polo_light_skin_tone": "1f93d-1f3fb-200d-2642-fe0f", "family_man_man_boy_boy": "1f468-200d-1f468-200d-1f466-200d-1f466", "foggy": "1f301", "construction_worker_woman_medium_light_skin_tone": "1f477-1f3fc-200d-2640-fe0f", "princess_medium_skin_tone": "1f478-1f3fd", "man_dancing_medium_dark_skin_tone": "1f57a-1f3fe", "couple_with_heart_man_man_dark_skin_tone": "1f468-1f3ff", "carousel_horse": "1f3a0", "crayon": "1f58d", "niue": "1f1f3-1f1fa", "woman_office_worker_medium_skin_tone": "1f469-1f3fd", "swimming_man_medium_skin_tone": "1f3ca-1f3fd-200d-2640-fe0f", "pensive": "1f614", "fire": "1f525", "monorail": "1f69d", "guam": "1f1ec-1f1fa", "older_woman_light_skin_tone": "1f475-1f3fb", "man_facepalming_medium_light_skin_tone": "1f926-1f3fc-200d-2642-fe0f", "family_man_man_girl": "1f468-200d-1f468-200d-1f467", "hammer_and_pick": "2692", "space_invader": "1f47e", "waning_crescent_moon": "1f318", "love_letter": "1f48c", "star_and_crescent": "262a-fe0f", "man_with_turban_light_skin_tone": "1f473-1f3fb-200d-2640-fe0f", "tipping_hand_woman_light_skin_tone": "1f481-1f3fb-200d-2640-fe0f", "dress": "1f457", "rainbow": "1f308", "cheese": "1f9c0", "bento": "1f371", "gear": "2699-fe0f", "-1_medium_skin_tone": "1f44e-1f3fd", "family_man_girl_boy_dark_skin_tone": "1f468-1f3ff", "fish_cake": "1f365", "desert_island": "1f3dd", "crystal_ball": "1f52e", "lock": "1f512", "no_good_man_medium_skin_tone": "1f645-1f3fd-200d-2642-fe0f", "small_blue_diamond": "1f539", "fist_raised_medium_dark_skin_tone": "270a-1f3fe", "man_health_worker_medium_light_skin_tone": "1f468-1f3fc", "ok_man_medium_light_skin_tone": "1f646-1f3fc-200d-2642-fe0f", "man_cartwheeling_dark_skin_tone": "1f938-1f3ff-200d-2642-fe0f", "policeman": "1f46e", "closed_lock_with_key": "1f510", "koko": "1f201", "guardswoman": "1f482-200d-2640-fe0f", "mailbox": "1f4eb", "weight_lifting_woman_light_skin_tone": "1f3cb-1f3fb-200d-2640-fe0f", "drooling_face": "1f924", "motorway": "1f6e3", "orthodox_cross": "2626-fe0f", "peru": "1f1f5-1f1ea", "woman_firefighter_medium_light_skin_tone": "1f469-1f3fc", "atom_symbol": "269b-fe0f", "benin": "1f1e7-1f1ef", "montenegro": "1f1f2-1f1ea", "tonga": "1f1f9-1f1f4", "family_man_boy_boy_medium_skin_tone": "1f468-1f3fd", "man_mechanic_light_skin_tone": "1f468-1f3fb", "female_detective": "1f575-fe0f-200d-2640-fe0f", "closed_umbrella": "1f302", "cow2": "1f404", "ballot_box": "1f5f3", "construction_worker_man_dark_skin_tone": "1f477-1f3ff-200d-2640-fe0f", "woman_technologist_medium_dark_skin_tone": "1f469-1f3fe", "indonesia": "1f1ee-1f1e9", "woman_pilot_medium_light_skin_tone": "1f469-1f3fc", "family_man_man_boy_boy_medium_light_skin_tone": "1f468-1f3fc", "call_me_hand": "1f919", "sun_behind_small_cloud": "1f324", "national_park": "1f3de", "radio_button": "1f518", "selfie_medium_light_skin_tone": "1f933-1f3fc", "woman_firefighter": "1f469-200d-1f692", "metal_dark_skin_tone": "1f918-1f3ff", "older_woman": "1f475", "man_factory_worker_medium_skin_tone": "1f468-1f3fd", "pick": "26cf", "woman_student_medium_skin_tone": "1f469-1f3fd", "mountain_biking_woman_light_skin_tone": "1f6b5-1f3fb-200d-2640-fe0f", "flags": "1f38f", "black_nib": "2712-fe0f", "rwanda": "1f1f7-1f1fc", "surfing_man_light_skin_tone": "1f3c4-1f3fb-200d-2640-fe0f", "first_quarter_moon": "1f313", "oil_drum": "1f6e2", "heart_decoration": "1f49f", "jp": "1f1ef-1f1f5", "woman_pilot": "1f469-200d-2708-fe0f", "city_sunrise": "1f307", "leo": "264c-fe0f", "arrow_up_down": "2195-fe0f", "selfie_medium_skin_tone": "1f933-1f3fd", "surfing_man_medium_skin_tone": "1f3c4-1f3fd-200d-2640-fe0f", "ramen": "1f35c", "up": "1f199", "woman_medium_light_skin_tone": "1f469-1f3fc", "woman_artist": "1f469-200d-1f3a8", "football": "1f3c8", "shopping": "1f6cd", "small_red_triangle_down": "1f53b", "crossed_fingers_light_skin_tone": "1f91e-1f3fb", "woman_artist_medium_dark_skin_tone": "1f469-1f3fe", "milk_glass": "1f95b", "clapper": "1f3ac", "star_of_david": "2721-fe0f", "dominican_republic": "1f1e9-1f1f4", "woman_teacher_light_skin_tone": "1f469-1f3fb", "man_juggling_medium_skin_tone": "1f939-1f3fd-200d-2642-fe0f", "-1": "1f44e", "wedding": "1f492", "faroe_islands": "1f1eb-1f1f4", "raising_hand_man_medium_dark_skin_tone": "1f64b-1f3fe-200d-2642-fe0f", "gemini": "264a-fe0f", "st_helena": "1f1f8-1f1ed", "running_woman_medium_light_skin_tone": "1f3c3-1f3fc-200d-2640-fe0f", "biking_woman_light_skin_tone": "1f6b4-1f3fb-200d-2640-fe0f", "paperclip": "1f4ce", "wave_medium_light_skin_tone": "1f44b-1f3fc", "man_factory_worker_medium_dark_skin_tone": "1f468-1f3fe", "woman_cartwheeling_medium_dark_skin_tone": "1f938-1f3fe-200d-2640-fe0f", "clock12": "1f55b", "ru": "1f1f7-1f1fa", "clown_face": "1f921", "pizza": "1f355", "hole": "1f573", "incoming_envelope": "1f4e8", "yin_yang": "262f-fe0f", "warning": "26a0-fe0f", "family_man_man_girl_boy_dark_skin_tone": "1f468-1f3ff", "man_cartwheeling_medium_skin_tone": "1f938-1f3fd-200d-2642-fe0f", "ram": "1f40f", "cucumber": "1f952", "heartbeat": "1f493", "swaziland": "1f1f8-1f1ff", "nail_care_medium_dark_skin_tone": "1f485-1f3fe", "bath_medium_skin_tone": "1f6c0-1f3fd", "strawberry": "1f353", "peanuts": "1f95c", "field_hockey": "1f3d1", "cricket": "1f3cf", "woman_farmer_medium_dark_skin_tone": "1f469-1f3fe", "family_man_man_girl_girl_light_skin_tone": "1f468-1f3fb", "penguin": "1f427", "star": "2b50-fe0f", "woman_shrugging_light_skin_tone": "1f937-1f3fb-200d-2640-fe0f", "golfing_man_light_skin_tone": "1f3cc-1f3fb-200d-2640-fe0f", "innocent": "1f607", "mosque": "1f54c", "calendar": "1f4c6", "canada": "1f1e8-1f1e6", "rage4": "rage4", "woman_office_worker_medium_dark_skin_tone": "1f469-1f3fe", "poodle": "1f429", "grapes": "1f347", "love_hotel": "1f3e9", "vulcan_salute_medium_skin_tone": "1f596-1f3fd", "guardsman_medium_dark_skin_tone": "1f482-1f3fe-200d-2640-fe0f", "raising_hand_man_light_skin_tone": "1f64b-1f3fb-200d-2642-fe0f", "sleeping": "1f634", "nail_care": "1f485", "monkey": "1f412", "sao_tome_principe": "1f1f8-1f1f9", "dancer_medium_dark_skin_tone": "1f483-1f3fe", "classical_building": "1f3db", "swimming_woman_medium_skin_tone": "1f3ca-1f3fd-200d-2640-fe0f", "ok_hand": "1f44c", "rice_cracker": "1f358", "moyai": "1f5ff", "rage2": "rage2", "angel_light_skin_tone": "1f47c-1f3fb", "family_man_man_boy_boy_dark_skin_tone": "1f468-1f3ff", "smile_cat": "1f638", "angola": "1f1e6-1f1f4", "cameroon": "1f1e8-1f1f2", "man_student_medium_dark_skin_tone": "1f468-1f3fe", "weight_lifting_woman_medium_light_skin_tone": "1f3cb-1f3fc-200d-2640-fe0f", "waxing_crescent_moon": "1f312", "articulated_lorry": "1f69b", "pouting_woman_light_skin_tone": "1f64e-1f3fb-200d-2640-fe0f", "running_man_medium_dark_skin_tone": "1f3c3-1f3fe-200d-2640-fe0f", "couple_with_heart_woman_woman_light_skin_tone": "1f469-1f3fb", "horse_racing_light_skin_tone": "1f3c7-1f3fb", "raised_back_of_hand": "1f91a", "saxophone": "1f3b7", "right_anger_bubble": "1f5ef", "tokelau": "1f1f9-1f1f0", "no_good_woman_medium_light_skin_tone": "1f645-1f3fc-200d-2640-fe0f", "walking_woman_medium_skin_tone": "1f6b6-1f3fd-200d-2640-fe0f", "family_woman_girl_girl": "1f469-200d-1f467-200d-1f467", "cake": "1f370", "abcd": "1f521", "tuvalu": "1f1f9-1f1fb", "suspect": "suspect", "mattermost": "mattermost", "swimming_woman_light_skin_tone": "1f3ca-1f3fb-200d-2640-fe0f", "white_medium_square": "25fb-fe0f", "haircut_woman_medium_skin_tone": "1f487-1f3fd-200d-2640-fe0f", "massage_woman_dark_skin_tone": "1f486-1f3ff-200d-2640-fe0f", "family_man_woman_girl_light_skin_tone": "1f468-1f3fb", "turks_caicos_islands": "1f1f9-1f1e8", "point_left_dark_skin_tone": "1f448-1f3ff", "family_man_man_boy_medium_dark_skin_tone": "1f468-1f3fe", "hand": "270b", "coffee": "2615-fe0f", "somalia": "1f1f8-1f1f4", "mountain_biking_man_dark_skin_tone": "1f6b5-1f3ff-200d-2640-fe0f", "hatching_chick": "1f423", "pear": "1f350", "baby_bottle": "1f37c", "ribbon": "1f380", "st_kitts_nevis": "1f1f0-1f1f3", "radioactive": "2622-fe0f", "end": "1f51a", "hand_medium_skin_tone": "270b-1f3fd", "family_woman_woman_girl_medium_light_skin_tone": "1f469-1f3fc", "3rd_place_medal": "1f949", "fist_left_medium_dark_skin_tone": "1f91b-1f3fe", "bolivia": "1f1e7-1f1f4", "point_up_light_skin_tone": "261d-1f3fb", "cherries": "1f352", "inbox_tray": "1f4e5", "pitcairn_islands": "1f1f5-1f1f3", "rage1": "rage1", "man_farmer_medium_dark_skin_tone": "1f468-1f3fe", "woman_with_turban": "1f473-200d-2640-fe0f", "unicorn": "1f984", "butterfly": "1f98b", "watch": "231a-fe0f", "arrow_up_small": "1f53c", "triangular_flag_on_post": "1f6a9", "heart_eyes": "1f60d", "shallow_pan_of_food": "1f958", "broken_heart": "1f494", "family_man_boy_boy_dark_skin_tone": "1f468-1f3ff", "golfing_woman_dark_skin_tone": "1f3cc-1f3ff-200d-2640-fe0f", "bath_dark_skin_tone": "1f6c0-1f3ff", "selfie": "1f933", "congratulations": "3297-fe0f", "baby_medium_light_skin_tone": "1f476-1f3fc", "woman_health_worker_medium_skin_tone": "1f469-1f3fd", "man_juggling": "1f939-200d-2642-fe0f", "arrow_down_small": "1f53d", "writing_hand_medium_light_skin_tone": "270d-1f3fc", "blonde_woman": "1f471-200d-2640-fe0f", "massage": "1f486", "metro": "1f687", "bath": "1f6c0", "female_detective_light_skin_tone": "1f575-1f3fb-200d-2640-fe0f", "haircut_man_light_skin_tone": "1f487-1f3fb-200d-2642-fe0f", "bowing_woman_medium_dark_skin_tone": "1f647-1f3fe-200d-2640-fe0f", "family_woman_woman_boy_medium_dark_skin_tone": "1f469-1f3fe", "shell": "1f41a", "seychelles": "1f1f8-1f1e8", "tipping_hand_man_medium_skin_tone": "1f481-1f3fd-200d-2642-fe0f", "panda_face": "1f43c", "sint_maarten": "1f1f8-1f1fd", "face_with_head_bandage": "1f915", "checkered_flag": "1f3c1", "samoa": "1f1fc-1f1f8", "v_medium_skin_tone": "270c-1f3fd", "couple_with_heart_man_man": "1f468-200d-2764-fe0f-200d-1f468", "shaved_ice": "1f367", "badminton": "1f3f8", "clock530": "1f560", "man_playing_water_polo_medium_dark_skin_tone": "1f93d-1f3fe-200d-2642-fe0f", "bulgaria": "1f1e7-1f1ec", "hurtrealbad": "hurtrealbad", "fist_oncoming_dark_skin_tone": "1f44a-1f3ff", "bat": "1f987", "signal_strength": "1f4f6", "iran": "1f1ee-1f1f7", "construction_worker_woman_medium_dark_skin_tone": "1f477-1f3fe-200d-2640-fe0f", "kiwi_fruit": "1f95d", "2nd_place_medal": "1f948", "kaaba": "1f54b", "knife": "1f52a", "ok_hand_light_skin_tone": "1f44c-1f3fb", "angel_medium_dark_skin_tone": "1f47c-1f3fe", "spider_web": "1f578", "oncoming_taxi": "1f696", "bookmark": "1f516", "u6307": "1f22f-fe0f", "za": "1f1ff-1f1e6", "fist_raised_light_skin_tone": "270a-1f3fb", "mag_right": "1f50e", "guinea": "1f1ec-1f1f3", "family_woman_woman_girl_dark_skin_tone": "1f469-1f3ff", "man_playing_handball_medium_light_skin_tone": "1f93e-1f3fc-200d-2642-fe0f", "game_die": "1f3b2", "bullettrain_front": "1f685", "speedboat": "1f6a4", "hand_dark_skin_tone": "270b-1f3ff", "selfie_light_skin_tone": "1f933-1f3fb", "family_man_woman_boy_boy_dark_skin_tone": "1f468-1f3ff", "running_man": "1f3c3", "couplekiss_woman_woman": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", "woman_teacher": "1f469-200d-1f3eb", "running_shirt_with_sash": "1f3bd", "bowing_man_medium_skin_tone": "1f647-1f3fd-200d-2640-fe0f", "point_right_medium_dark_skin_tone": "1f449-1f3fe", "man_cartwheeling_medium_light_skin_tone": "1f938-1f3fc-200d-2642-fe0f", "pouting_man_light_skin_tone": "1f64e-1f3fb-200d-2642-fe0f", "biking_man_light_skin_tone": "1f6b4-1f3fb-200d-2640-fe0f", "oncoming_automobile": "1f698", "steam_locomotive": "1f682", "newspaper": "1f4f0", "antigua_barbuda": "1f1e6-1f1ec", "macau": "1f1f2-1f1f4", "niger": "1f1f3-1f1ea", "chicken": "1f414", "flashlight": "1f526", "family_man_woman_boy_boy_medium_skin_tone": "1f468-1f3fd", "mens": "1f6b9", "it": "1f1ee-1f1f9", "new_caledonia": "1f1f3-1f1e8", "pray_medium_light_skin_tone": "1f64f-1f3fc", "nose_medium_dark_skin_tone": "1f443-1f3fe", "man_facepalming_medium_dark_skin_tone": "1f926-1f3fe-200d-2642-fe0f", "poop": "1f4a9", "clap_light_skin_tone": "1f44f-1f3fb", "guardsman_medium_skin_tone": "1f482-1f3fd-200d-2640-fe0f", "woman_teacher_dark_skin_tone": "1f469-1f3ff", "grinning": "1f600", "aries": "2648-fe0f", "mrs_claus": "1f936", "green_book": "1f4d7", "middle_finger_medium_dark_skin_tone": "1f595-1f3fe", "rowing_woman_light_skin_tone": "1f6a3-1f3fb-200d-2640-fe0f", "tokyo_tower": "1f5fc", "printer": "1f5a8", "put_litter_in_its_place": "1f6ae", "suriname": "1f1f8-1f1f7", "woman_light_skin_tone": "1f469-1f3fb", "man_playing_water_polo_medium_light_skin_tone": "1f93d-1f3fc-200d-2642-fe0f", "dove": "1f54a", "latin_cross": "271d-fe0f", "exclamation": "2757-fe0f", "man_health_worker_dark_skin_tone": "1f468-1f3ff", "bride_with_veil_light_skin_tone": "1f470-1f3fb", "rowboat": "1f6a3", "world_map": "1f5fa", "sleeping_bed": "1f6cc", "haircut_man_dark_skin_tone": "1f487-1f3ff-200d-2642-fe0f", "surfing_man_dark_skin_tone": "1f3c4-1f3ff-200d-2640-fe0f", "couple_with_heart_woman_man": "1f491", "chart_with_upwards_trend": "1f4c8", "fist_right_dark_skin_tone": "1f91c-1f3ff", "raised_hand_with_fingers_splayed_medium_skin_tone": "1f590-1f3fd", "older_woman_medium_dark_skin_tone": "1f475-1f3fe", "couplekiss_woman_woman_medium_dark_skin_tone": "1f469-1f3fe", "hatched_chick": "1f425", "running_man_medium_skin_tone": "1f3c3-1f3fd-200d-2640-fe0f", "chocolate_bar": "1f36b", "grenada": "1f1ec-1f1e9", "man_farmer_dark_skin_tone": "1f468-1f3ff", "milky_way": "1f30c", "slovakia": "1f1f8-1f1f0", "selfie_dark_skin_tone": "1f933-1f3ff", "prince_medium_skin_tone": "1f934-1f3fd", "family_man_boy": "1f468-200d-1f466", "chains": "26d3", "british_virgin_islands": "1f1fb-1f1ec", "bow_and_arrow": "1f3f9", "ferry": "26f4", "o2": "1f17e-fe0f", "st_barthelemy": "1f1e7-1f1f1", "policewoman_medium_skin_tone": "1f46e-1f3fd-200d-2640-fe0f", "imp": "1f47f", "bathtub": "1f6c1", "anger": "1f4a2", "previous_track_button": "23ee", "pouting_woman_medium_light_skin_tone": "1f64e-1f3fc-200d-2640-fe0f", "rabbit2": "1f407", "newspaper_roll": "1f5de", "one": "0031-fe0f-20e3", "family_man_man_girl_medium_skin_tone": "1f468-1f3fd", "pouting_woman": "1f64e", "moneybag": "1f4b0", "guardsman_dark_skin_tone": "1f482-1f3ff-200d-2640-fe0f", "man_shrugging_medium_light_skin_tone": "1f937-1f3fc-200d-2642-fe0f", "smirk": "1f60f", "woman_farmer": "1f469-200d-1f33e", "ocean": "1f30a", "sweat_drops": "1f4a6", "x": "274c", "woman_judge_medium_dark_skin_tone": "1f469-1f3fe", "tropical_drink": "1f379", "brunei": "1f1e7-1f1f3", "woman_artist_medium_light_skin_tone": "1f469-1f3fc", "pregnant_woman_medium_dark_skin_tone": "1f930-1f3fe", "basketball_woman_light_skin_tone": "26f9-1f3fb-200d-2640-fe0f", "evergreen_tree": "1f332", "fax": "1f4e0", "woman_medium_dark_skin_tone": "1f469-1f3fe", "walking_woman_medium_dark_skin_tone": "1f6b6-1f3fe-200d-2640-fe0f", "lollipop": "1f36d", "bicyclist": "1f6b4", "bulb": "1f4a1", "computer": "1f4bb", "frowning_man_dark_skin_tone": "1f64d-1f3ff-200d-2642-fe0f", "guardsman_medium_light_skin_tone": "1f482-1f3fc-200d-2640-fe0f", "dancer_light_skin_tone": "1f483-1f3fb", "no_good_woman": "1f645", "cherry_blossom": "1f338", "woman_playing_water_polo": "1f93d-200d-2640-fe0f", "heavy_division_sign": "2797", "sri_lanka": "1f1f1-1f1f0", "-1_medium_light_skin_tone": "1f44e-1f3fc", "family_woman_girl_girl_dark_skin_tone": "1f469-1f3ff", "raised_hands": "1f64c", "sandal": "1f461", "rhinoceros": "1f98f", "swimming_man": "1f3ca", "scissors": "2702-fe0f", "horse_racing_dark_skin_tone": "1f3c7-1f3ff", "coffin": "26b0-fe0f", "clock1": "1f550", "eritrea": "1f1ea-1f1f7", "qatar": "1f1f6-1f1e6", "tanzania": "1f1f9-1f1ff", "pregnant_woman_light_skin_tone": "1f930-1f3fb", "cop": "1f46e", "tipping_hand_woman": "1f481", "estonia": "1f1ea-1f1ea", "man_singer_light_skin_tone": "1f468-1f3fb", "woman_judge_dark_skin_tone": "1f469-1f3ff", "business_suit_levitating_medium_skin_tone": "1f574-1f3fd", "blowfish": "1f421", "mountain_railway": "1f69e", "fast_forward": "23e9", "+1_medium_skin_tone": "1f44d-1f3fd", "goat": "1f410", "congo_kinshasa": "1f1e8-1f1e9", "point_down_dark_skin_tone": "1f447-1f3ff", "basketball_man_light_skin_tone": "26f9-1f3fb-200d-2640-fe0f", "woman_playing_handball_medium_dark_skin_tone": "1f93e-1f3fe-200d-2640-fe0f", "doughnut": "1f369", "musical_keyboard": "1f3b9", "couplekiss_woman_woman_medium_skin_tone": "1f469-1f3fd", "heavy_heart_exclamation": "2763-fe0f", "u6e80": "1f235", "woman_with_turban_medium_skin_tone": "1f473-1f3fd-200d-2640-fe0f", "horse_racing_medium_skin_tone": "1f3c7-1f3fd", "ear": "1f442", "canoe": "1f6f6", "andorra": "1f1e6-1f1e9", "ca": "1f1e8-1f1e6", "family_man_man_boy": "1f468-200d-1f468-200d-1f466", "ticket": "1f3ab", "station": "1f689", "large_blue_circle": "1f535", "palau": "1f1f5-1f1fc", "blush": "1f60a", "man_student": "1f468-200d-1f393", "woman_singer": "1f469-200d-1f3a4", "house_with_garden": "1f3e1", "smoking": "1f6ac", "b": "1f171-fe0f", "golfing_woman_light_skin_tone": "1f3cc-1f3fb-200d-2640-fe0f", "man_artist_light_skin_tone": "1f468-1f3fb", "unamused": "1f612", "japanese_ogre": "1f479", "film_projector": "1f4fd", "ballot_box_with_check": "2611-fe0f", "goberserk": "goberserk", "metal_medium_light_skin_tone": "1f918-1f3fc", "latvia": "1f1f1-1f1fb", "moldova": "1f1f2-1f1e9", "mask": "1f637", "bowing_man": "1f647", "man_shrugging": "1f937-200d-2642-fe0f", "ping_pong": "1f3d3", "trackball": "1f5b2", "six": "0036-fe0f-20e3", "point_up_medium_skin_tone": "261d-1f3fd", "hotel": "1f3e8", "bookmark_tabs": "1f4d1", "chart_with_downwards_trend": "1f4c9", "v_light_skin_tone": "270c-1f3fb", "tipping_hand_woman_medium_skin_tone": "1f481-1f3fd-200d-2640-fe0f", "heart_eyes_cat": "1f63b", "dancer": "1f483", "movie_camera": "1f3a5", "two": "0032-fe0f-20e3", "clap_medium_dark_skin_tone": "1f44f-1f3fe", "woman_astronaut_medium_skin_tone": "1f469-1f3fd", "frowning": "1f626", "cry": "1f622", "no_bell": "1f515", "hand_medium_dark_skin_tone": "270b-1f3fe", "ear_light_skin_tone": "1f442-1f3fb", "family_man_girl_boy": "1f468-200d-1f467-200d-1f466", "swimming_woman": "1f3ca-200d-2640-fe0f", "mountain_biking_woman": "1f6b5-200d-2640-fe0f", "mantelpiece_clock": "1f570", "bermuda": "1f1e7-1f1f2", "new_zealand": "1f1f3-1f1ff", "massage_man_medium_dark_skin_tone": "1f486-1f3fe-200d-2642-fe0f", "crown": "1f451", "biking_man": "1f6b4", "woman_playing_water_polo_medium_skin_tone": "1f93d-1f3fd-200d-2640-fe0f", "man_in_tuxedo_dark_skin_tone": "1f935-1f3ff", "no_entry_sign": "1f6ab", "hash": "0023-fe0f-20e3", "white_small_square": "25ab-fe0f", "iraq": "1f1ee-1f1f6", "switzerland": "1f1e8-1f1ed", "woman_mechanic_light_skin_tone": "1f469-1f3fb", "squirrel": "shipit", "woman_cook_medium_light_skin_tone": "1f469-1f3fc", "confounded": "1f616", "+1": "1f44d", "rowing_man": "1f6a3", "mailbox_closed": "1f4ea", "customs": "1f6c3", "mayotte": "1f1fe-1f1f9", "man_mechanic_medium_dark_skin_tone": "1f468-1f3fe", "man_artist_medium_skin_tone": "1f468-1f3fd", "man_playing_handball_medium_skin_tone": "1f93e-1f3fd-200d-2642-fe0f", "grimacing": "1f62c", "dart": "1f3af", "wave_medium_dark_skin_tone": "1f44b-1f3fe", "slightly_smiling_face": "1f642", "medal_sports": "1f3c5", "bank": "1f3e6", "man_student_medium_light_skin_tone": "1f468-1f3fc", "man_pilot_light_skin_tone": "1f468-1f3fb", "weight_lifting_woman_medium_skin_tone": "1f3cb-1f3fd-200d-2640-fe0f", "dash": "1f4a8", "volcano": "1f30b", "antarctica": "1f1e6-1f1f6", "woman_facepalming_light_skin_tone": "1f926-1f3fb-200d-2640-fe0f", "man_dancing_medium_light_skin_tone": "1f57a-1f3fc", "scream_cat": "1f640", "fog": "1f32b", "fist_oncoming_light_skin_tone": "1f44a-1f3fb", "man_dancing_medium_skin_tone": "1f57a-1f3fd", "burrito": "1f32f", "thought_balloon": "1f4ad", "massage_man_medium_light_skin_tone": "1f486-1f3fc-200d-2642-fe0f", "couple_with_heart_woman_woman_dark_skin_tone": "1f469-1f3ff", "writing_hand": "270d-fe0f", "zap": "26a1-fe0f", "recycle": "267b-fe0f", "policewoman_medium_light_skin_tone": "1f46e-1f3fc-200d-2640-fe0f", "frowning_woman_medium_skin_tone": "1f64d-1f3fd-200d-2640-fe0f", "massage_man_light_skin_tone": "1f486-1f3fb-200d-2642-fe0f", "woman_student": "1f469-200d-1f393", "surfing_woman": "1f3c4-200d-2640-fe0f", "sunrise": "1f305", "open_file_folder": "1f4c2", "diamonds": "2666-fe0f", "family_man_woman_girl_girl": "1f468-200d-1f469-200d-1f467-200d-1f467", "airplane": "2708-fe0f", "arrow_heading_down": "2935-fe0f", "uruguay": "1f1fa-1f1fe", "point_down_medium_dark_skin_tone": "1f447-1f3fe", "family_man_man_boy_dark_skin_tone": "1f468-1f3ff", "family_man_woman_girl_boy": "1f468-200d-1f469-200d-1f467-200d-1f466", "confetti_ball": "1f38a", "flower_playing_cards": "1f3b4", "algeria": "1f1e9-1f1ff", "man_teacher_medium_light_skin_tone": "1f468-1f3fc", "woman_artist_light_skin_tone": "1f469-1f3fb", "family_man_woman_girl_medium_skin_tone": "1f468-1f3fd", "nerd_face": "1f913", "eyes": "1f440", "boot": "1f462", "unlock": "1f513", "zzz": "1f4a4", "vatican_city": "1f1fb-1f1e6", "hot_pepper": "1f336", "slot_machine": "1f3b0", "sunrise_over_mountains": "1f304", "haircut_man_medium_skin_tone": "1f487-1f3fd-200d-2642-fe0f", "stuck_out_tongue": "1f61b", "point_up_medium_dark_skin_tone": "261d-1f3fe", "vulcan_salute_medium_dark_skin_tone": "1f596-1f3fe", "family": "1f46a", "key": "1f511", "myanmar": "1f1f2-1f1f2", "policeman_medium_light_skin_tone": "1f46e-1f3fc-200d-2640-fe0f", "man_shrugging_medium_dark_skin_tone": "1f937-1f3fe-200d-2642-fe0f", "woman_health_worker": "1f469-200d-2695-fe0f", "woman_judge": "1f469-200d-2696-fe0f", "japan": "1f5fe", "dominica": "1f1e9-1f1f2", "dragon": "1f409", "open_book": "1f4d6", "raising_hand_man": "1f64b-200d-2642-fe0f", "bikini": "1f459", "loudspeaker": "1f4e2", "woman_astronaut_medium_light_skin_tone": "1f469-1f3fc", "envelope_with_arrow": "1f4e9", "thailand": "1f1f9-1f1ed", "point_up_medium_light_skin_tone": "261d-1f3fc", "baby_medium_dark_skin_tone": "1f476-1f3fe", "man_scientist_medium_skin_tone": "1f468-1f3fd", "bowing_woman_medium_light_skin_tone": "1f647-1f3fc-200d-2640-fe0f", "construction_worker": "1f477", "nut_and_bolt": "1f529", "sparkling_heart": "1f496", "couplekiss_woman_woman_dark_skin_tone": "1f469-1f3ff", "elephant": "1f418", "bar_chart": "1f4ca", "nose_dark_skin_tone": "1f443-1f3ff", "stop_button": "23f9", "family_man_woman_boy_boy_light_skin_tone": "1f468-1f3fb", "family_man_girl_medium_light_skin_tone": "1f468-1f3fc", "relieved": "1f60c", "man_in_tuxedo": "1f935", "kick_scooter": "1f6f4", "statue_of_liberty": "1f5fd", "information_desk_person": "1f481", "sa": "1f202-fe0f", "abc": "1f524", "robot": "1f916", "cat": "1f431", "accept": "1f251", "upside_down_face": "1f643", "cloud": "2601-fe0f", "frowning_man_light_skin_tone": "1f64d-1f3fb-200d-2642-fe0f", "walking_man_medium_skin_tone": "1f6b6-1f3fd-200d-2640-fe0f", "sparkles": "2728", "u5272": "1f239", "globe_with_meridians": "1f310", "frowning_woman_medium_dark_skin_tone": "1f64d-1f3fe-200d-2640-fe0f", "grey_exclamation": "2755", "tm": "2122-fe0f", "massage_man_dark_skin_tone": "1f486-1f3ff-200d-2642-fe0f", "family_woman_woman_girl_boy_dark_skin_tone": "1f469-1f3ff", "paintbrush": "1f58c", "arrow_right_hook": "21aa-fe0f", "mauritania": "1f1f2-1f1f7", "man_scientist_light_skin_tone": "1f468-1f3fb", "woman_juggling_light_skin_tone": "1f939-1f3fb-200d-2640-fe0f", "ok_woman": "1f646", "snail": "1f40c", "hocho": "1f52a", "arrow_forward": "25b6-fe0f", "french_southern_territories": "1f1f9-1f1eb", "iphone": "1f4f1", "princess_medium_light_skin_tone": "1f478-1f3fc", "maple_leaf": "1f341", "open_hands": "1f450", "racing_car": "1f3ce", "pill": "1f48a", "cuba": "1f1e8-1f1fa", "fist_raised_dark_skin_tone": "270a-1f3ff", "blonde_man_dark_skin_tone": "1f471-1f3ff-200d-2640-fe0f", "family_woman_girl_boy_dark_skin_tone": "1f469-1f3ff", "fox_face": "1f98a", "man_playing_handball": "1f93e-200d-2642-fe0f", "bullettrain_side": "1f684", "black_small_square": "25aa-fe0f", "kazakhstan": "1f1f0-1f1ff", "vanuatu": "1f1fb-1f1fa", "older_man_medium_skin_tone": "1f474-1f3fd", "man_teacher": "1f468-200d-1f3eb", "family_man_man_boy_boy_medium_dark_skin_tone": "1f468-1f3fe", "back": "1f519", "point_up_2_medium_dark_skin_tone": "1f446-1f3fe", "woman_teacher_medium_light_skin_tone": "1f469-1f3fc", "family_woman_boy_boy_medium_dark_skin_tone": "1f469-1f3fe", "surfing_woman_medium_light_skin_tone": "1f3c4-1f3fc-200d-2640-fe0f", "portugal": "1f1f5-1f1f9", "construction_worker_woman_dark_skin_tone": "1f477-1f3ff-200d-2640-fe0f", "family_man_man_boy_medium_skin_tone": "1f468-1f3fd", "family_man_girl_dark_skin_tone": "1f468-1f3ff", "woman_mechanic": "1f469-200d-1f527", "arrow_heading_up": "2934-fe0f", "clock330": "1f55e", "malawi": "1f1f2-1f1fc", "ok_hand_medium_dark_skin_tone": "1f44c-1f3fe", "prince_dark_skin_tone": "1f934-1f3ff", "ice_hockey": "1f3d2", "pk": "1f1f5-1f1f0", "san_marino": "1f1f8-1f1f2", "point_left_light_skin_tone": "1f448-1f3fb", "woman_office_worker_medium_light_skin_tone": "1f469-1f3fc", "swimming_man_medium_light_skin_tone": "1f3ca-1f3fc-200d-2640-fe0f", "stuffed_flatbread": "1f959", "aerial_tramway": "1f6a1", "family_man_man_girl_girl_dark_skin_tone": "1f468-1f3ff", "family_woman_girl_girl_medium_dark_skin_tone": "1f469-1f3fe", "closed_book": "1f4d5", "family_woman_girl_boy_medium_light_skin_tone": "1f469-1f3fc", "family_man_man_girl_boy_medium_skin_tone": "1f468-1f3fd", "v": "270c-fe0f", "play_or_pause_button": "23ef", "el_salvador": "1f1f8-1f1fb", "woman_judge_medium_light_skin_tone": "1f469-1f3fc", "santa_medium_light_skin_tone": "1f385-1f3fc", "couplekiss_man_man_light_skin_tone": "1f468-1f3fb", "blonde_man_light_skin_tone": "1f471-1f3fb-200d-2640-fe0f", "fist_right": "1f91c", "man_with_turban": "1f473", "cancer": "264b-fe0f", "tunisia": "1f1f9-1f1f3", "open_hands_medium_light_skin_tone": "1f450-1f3fc", "call_me_hand_medium_dark_skin_tone": "1f919-1f3fe", "tired_face": "1f62b", "tongue": "1f445", "shower": "1f6bf", "british_indian_ocean_territory": "1f1ee-1f1f4", "man_firefighter_medium_light_skin_tone": "1f468-1f3fc", "couple_with_heart_woman_woman_medium_dark_skin_tone": "1f469-1f3fe", "crescent_moon": "1f319", "ecuador": "1f1ea-1f1e8", "french_polynesia": "1f1f5-1f1eb", "man_light_skin_tone": "1f468-1f3fb", "mountain_biking_woman_medium_skin_tone": "1f6b5-1f3fd-200d-2640-fe0f", "pakistan": "1f1f5-1f1f0", "open_hands_medium_dark_skin_tone": "1f450-1f3fe", "telephone": "260e-fe0f", "envelope": "2709-fe0f", "revolving_hearts": "1f49e", "mega": "1f4e3", "montserrat": "1f1f2-1f1f8", "uganda": "1f1fa-1f1ec", "tropical_fish": "1f420", "hibiscus": "1f33a", "rainbow_flag": "1f3f3-fe0f-200d-1f308", "bangladesh": "1f1e7-1f1e9", "shipit": "shipit", "no_good_man_dark_skin_tone": "1f645-1f3ff-200d-2642-fe0f", "no_mouth": "1f636", "man_farmer": "1f468-200d-1f33e", "speak_no_evil": "1f64a", "level_slider": "1f39a", "guatemala": "1f1ec-1f1f9", "woman_factory_worker": "1f469-200d-1f3ed", "fork_and_knife": "1f374", "belarus": "1f1e7-1f1fe", "family_woman_woman_girl_boy_medium_dark_skin_tone": "1f469-1f3fe", "yum": "1f60b", "helicopter": "1f681", "busstop": "1f68f", "policewoman_light_skin_tone": "1f46e-1f3fb-200d-2640-fe0f", "man_technologist_medium_skin_tone": "1f468-1f3fd", "man_with_gua_pi_mao_light_skin_tone": "1f472-1f3fb", "man_astronaut_dark_skin_tone": "1f468-1f3ff", "skull": "1f480", "smirk_cat": "1f63c", "jeans": "1f456", "flipper": "1f42c", "dizzy": "1f4ab", "cocktail": "1f378", "basketball_woman_medium_skin_tone": "26f9-1f3fd-200d-2640-fe0f", "v_medium_light_skin_tone": "270c-1f3fc", "secret": "3299-fe0f", "seven": "0037-fe0f-20e3", "ghana": "1f1ec-1f1ed", "guernsey": "1f1ec-1f1ec", "kyrgyzstan": "1f1f0-1f1ec", "godmode": "godmode", "female_detective_dark_skin_tone": "1f575-1f3ff-200d-2640-fe0f", "fallen_leaf": "1f342", "snowflake": "2744-fe0f", "raised_hand_with_fingers_splayed_medium_dark_skin_tone": "1f590-1f3fe", "woman_health_worker_medium_dark_skin_tone": "1f469-1f3fe", "man_shrugging_dark_skin_tone": "1f937-1f3ff-200d-2642-fe0f", "pout": "1f621", "stars": "1f320", "family_woman_girl_boy": "1f469-200d-1f467-200d-1f466", "gun": "1f52b", "woman_scientist_dark_skin_tone": "1f469-1f3ff", "basketball_woman_dark_skin_tone": "26f9-1f3ff-200d-2640-fe0f", "biking_woman_medium_light_skin_tone": "1f6b4-1f3fc-200d-2640-fe0f", "family_man_girl_boy_medium_dark_skin_tone": "1f468-1f3fe", "oncoming_bus": "1f68d", "seat": "1f4ba", "vhs": "1f4fc", "lithuania": "1f1f1-1f1f9", "v_medium_dark_skin_tone": "270c-1f3fe", "man_with_gua_pi_mao_medium_skin_tone": "1f472-1f3fd", "frowning_face": "2639-fe0f", "shit": "1f4a9", "ab": "1f18e", "couple_with_heart_woman_woman_medium_skin_tone": "1f469-1f3fd", "family_woman_woman_girl_girl": "1f469-200d-1f469-200d-1f467-200d-1f467", "potato": "1f954", "minidisc": "1f4bd", "libya": "1f1f1-1f1fe", "point_right_dark_skin_tone": "1f449-1f3ff", "man_artist": "1f468-200d-1f3a8", "pineapple": "1f34d", "spaghetti": "1f35d", "couch_and_lamp": "1f6cb", "free": "1f193", "jamaica": "1f1ef-1f1f2", "woman_astronaut_dark_skin_tone": "1f469-1f3ff", "man_mechanic": "1f468-200d-1f527", "curry": "1f35b", "small_orange_diamond": "1f538", "pray": "1f64f", "hotdog": "1f32d", "currency_exchange": "1f4b1", "-1_dark_skin_tone": "1f44e-1f3ff", "man_office_worker_dark_skin_tone": "1f468-1f3ff", "clock830": "1f563", "policeman_medium_skin_tone": "1f46e-1f3fd-200d-2640-fe0f", "grin": "1f601", "water_buffalo": "1f403", "older_man_dark_skin_tone": "1f474-1f3ff", "business_suit_levitating_medium_dark_skin_tone": "1f574-1f3fe", "couple_with_heart_man_man_medium_light_skin_tone": "1f468-1f3fc", "rowing_man_medium_light_skin_tone": "1f6a3-1f3fc-200d-2640-fe0f", "purse": "1f45b", "slovenia": "1f1f8-1f1ee", "tipping_hand_man_medium_light_skin_tone": "1f481-1f3fc-200d-2642-fe0f", "madagascar": "1f1f2-1f1ec", "south_georgia_south_sandwich_islands": "1f1ec-1f1f8", "punch": "1f44a", "man_pilot": "1f468-200d-2708-fe0f", "owl": "1f989", "croissant": "1f950", "email": "2709-fe0f", "outbox_tray": "1f4e4", "construction_worker_man_medium_light_skin_tone": "1f477-1f3fc-200d-2640-fe0f", "mrs_claus_medium_dark_skin_tone": "1f936-1f3fe", "family_man_woman_girl_boy_dark_skin_tone": "1f468-1f3ff", "file_cabinet": "1f5c4", "hungary": "1f1ed-1f1fa", "pray_medium_dark_skin_tone": "1f64f-1f3fe", "woman_mechanic_dark_skin_tone": "1f469-1f3ff", "angel_medium_skin_tone": "1f47c-1f3fd", "man_dancing": "1f57a", "pound": "1f4b7", "macedonia": "1f1f2-1f1f0", "man_facepalming_medium_skin_tone": "1f926-1f3fd-200d-2642-fe0f", "scroll": "1f4dc", "rescue_worker_helmet": "26d1", "desktop_computer": "1f5a5", "heavy_plus_sign": "2795", "man_with_turban_medium_skin_tone": "1f473-1f3fd-200d-2640-fe0f", "horse_racing": "1f3c7", "low_brightness": "1f505", "loop": "27bf", "man_with_turban_medium_dark_skin_tone": "1f473-1f3fe-200d-2640-fe0f", "champagne": "1f37e", "construction_worker_woman_light_skin_tone": "1f477-1f3fb-200d-2640-fe0f", "man_teacher_light_skin_tone": "1f468-1f3fb", "family_woman_woman_girl_girl_medium_light_skin_tone": "1f469-1f3fc", "footprints": "1f463", "cloud_with_snow": "1f328", "man_cook_medium_light_skin_tone": "1f468-1f3fc", "woman_mechanic_medium_light_skin_tone": "1f469-1f3fc", "point_up_2": "1f446", "circus_tent": "1f3aa", "serbia": "1f1f7-1f1f8", "fist_right_medium_dark_skin_tone": "1f91c-1f3fe", "weight_lifting_woman_dark_skin_tone": "1f3cb-1f3ff-200d-2640-fe0f", "musical_score": "1f3bc", "violin": "1f3bb", "card_file_box": "1f5c3", "tipping_hand_woman_dark_skin_tone": "1f481-1f3ff-200d-2640-fe0f", "man_facepalming_dark_skin_tone": "1f926-1f3ff-200d-2642-fe0f", "open_mouth": "1f62e", "left_right_arrow": "2194-fe0f", "no_good_man_light_skin_tone": "1f645-1f3fb-200d-2642-fe0f", "man_factory_worker": "1f468-200d-1f3ed", "man_judge": "1f468-200d-2696-fe0f", "negative_squared_cross_mark": "274e", "bowing_woman_medium_skin_tone": "1f647-1f3fd-200d-2640-fe0f", "family_woman_boy_boy_light_skin_tone": "1f469-1f3fb", "battery": "1f50b", "couplekiss_man_man_medium_light_skin_tone": "1f468-1f3fc", "clock5": "1f554", "white_flag": "1f3f3-fe0f", "guadeloupe": "1f1ec-1f1f5", "muscle_medium_dark_skin_tone": "1f4aa-1f3fe", "man_scientist_dark_skin_tone": "1f468-1f3ff", "business_suit_levitating_light_skin_tone": "1f574-1f3fb", "woman_office_worker": "1f469-200d-1f4bc", "gift": "1f381", "sound": "1f509", "clubs": "2663-fe0f", "woman_scientist_medium_light_skin_tone": "1f469-1f3fc", "female_detective_medium_skin_tone": "1f575-1f3fd-200d-2640-fe0f", "man_singer_medium_light_skin_tone": "1f468-1f3fc", "family_man_girl": "1f468-200d-1f467", "bee": "1f41d", "full_moon_with_face": "1f31d", "black_medium_square": "25fc-fe0f", "zambia": "1f1ff-1f1f2", "raised_hands_dark_skin_tone": "1f64c-1f3ff", "family_woman_woman_boy_boy_medium_skin_tone": "1f469-1f3fd", "bread": "1f35e", "clock11": "1f55a", "man_office_worker_medium_light_skin_tone": "1f468-1f3fc", "woman_firefighter_medium_skin_tone": "1f469-1f3fd", "man_dancing_dark_skin_tone": "1f57a-1f3ff", "family_man_boy_medium_dark_skin_tone": "1f468-1f3fe", "hugs": "1f917", "roll_eyes": "1f644", "raised_hand": "270b", "tangerine": "1f34a", "grey_question": "2754", "princess_light_skin_tone": "1f478-1f3fb", "motor_boat": "1f6e5", "passport_control": "1f6c2", "man_artist_medium_dark_skin_tone": "1f468-1f3fe", "golfing_man_medium_skin_tone": "1f3cc-1f3fd-200d-2640-fe0f", "shirt": "1f455", "whale": "1f433", "apple": "1f34e", "ethiopia": "1f1ea-1f1f9", "jordan": "1f1ef-1f1f4", "biking_woman_medium_dark_skin_tone": "1f6b4-1f3fe-200d-2640-fe0f", "family_woman_girl_girl_medium_skin_tone": "1f469-1f3fd", "turkey": "1f983", "snowman_with_snow": "2603-fe0f", "fist_left_medium_skin_tone": "1f91b-1f3fd", "woman_with_turban_light_skin_tone": "1f473-1f3fb-200d-2640-fe0f", "woman_pilot_dark_skin_tone": "1f469-1f3ff", "family_woman_woman_boy_medium_skin_tone": "1f469-1f3fd", "purple_heart": "1f49c", "black_heart": "1f5a4", "haircut_man": "1f487-200d-2642-fe0f", "arrow_lower_left": "2199-fe0f", "guinea_bissau": "1f1ec-1f1fc", "sudan": "1f1f8-1f1e9", "woman_scientist_light_skin_tone": "1f469-1f3fb", "bust_in_silhouette": "1f464", "walking": "1f6b6", "european_union": "1f1ea-1f1fa", "running_woman_dark_skin_tone": "1f3c3-1f3ff-200d-2640-fe0f", "om": "1f549", "rowing_man_medium_skin_tone": "1f6a3-1f3fd-200d-2640-fe0f", "ideograph_advantage": "1f250", "nepal": "1f1f3-1f1f5", "syria": "1f1f8-1f1fe", "man_pilot_medium_dark_skin_tone": "1f468-1f3fe", "princess_medium_dark_skin_tone": "1f478-1f3fe", "watermelon": "1f349", "left_luggage": "1f6c5", "us": "1f1fa-1f1f8", "point_left_medium_light_skin_tone": "1f448-1f3fc", "family_man_girl_boy_medium_skin_tone": "1f468-1f3fd", "biking_man_medium_dark_skin_tone": "1f6b4-1f3fe-200d-2640-fe0f", "keycap_ten": "1f51f", "man_medium_light_skin_tone": "1f468-1f3fc", "couple_with_heart_man_man_light_skin_tone": "1f468-1f3fb", "family_man_man_girl_boy_light_skin_tone": "1f468-1f3fb", "dog2": "1f415", "art": "1f3a8", "taxi": "1f695", "motorcycle": "1f3cd", "diamond_shape_with_a_dot_inside": "1f4a0", "writing_hand_light_skin_tone": "270d-1f3fb", "woman_playing_water_polo_medium_dark_skin_tone": "1f93d-1f3fe-200d-2640-fe0f", "martial_arts_uniform": "1f94b", "spiral_calendar": "1f5d3", "older_man_medium_dark_skin_tone": "1f474-1f3fe", "woman_artist_medium_skin_tone": "1f469-1f3fd", "no_good_man_medium_dark_skin_tone": "1f645-1f3fe-200d-2642-fe0f", "family_woman_woman_boy_medium_light_skin_tone": "1f469-1f3fc", "ship": "1f6a2", "bangbang": "203c-fe0f", "israel": "1f1ee-1f1f1", "rowing_man_medium_dark_skin_tone": "1f6a3-1f3fe-200d-2640-fe0f", "calling": "1f4f2", "scorpius": "264f-fe0f", "vulcan_salute_dark_skin_tone": "1f596-1f3ff", "woman_office_worker_light_skin_tone": "1f469-1f3fb", "man_judge_light_skin_tone": "1f468-1f3fb", "family_woman_woman_boy_boy_medium_dark_skin_tone": "1f469-1f3fe", "woman_playing_handball": "1f93e-200d-2640-fe0f", "bridge_at_night": "1f309", "stop_sign": "1f6d1", "8ball": "1f3b1", "orange_book": "1f4d9", "couplekiss_man_woman": "1f48f", "no_mobile_phones": "1f4f5", "pouting_man_dark_skin_tone": "1f64e-1f3ff-200d-2642-fe0f", "man_juggling_light_skin_tone": "1f939-1f3fb-200d-2642-fe0f", "cold_sweat": "1f630", "star2": "1f31f", "taco": "1f32e", "point_right_medium_light_skin_tone": "1f449-1f3fc", "selfie_medium_dark_skin_tone": "1f933-1f3fe", "family_woman_woman_girl_boy_light_skin_tone": "1f469-1f3fb", "hankey": "1f4a9", "monkey_face": "1f435", "sweden": "1f1f8-1f1ea", "crocodile": "1f40a", "last_quarter_moon_with_face": "1f31c", "comet": "2604-fe0f", "caribbean_netherlands": "1f1e7-1f1f6", "walking_man_medium_dark_skin_tone": "1f6b6-1f3fe-200d-2640-fe0f", "basketball_man_medium_light_skin_tone": "26f9-1f3fc-200d-2640-fe0f", "deer": "1f98c", "clock4": "1f553", "christmas_island": "1f1e8-1f1fd", "fist_right_medium_skin_tone": "1f91c-1f3fd", "man_cook_dark_skin_tone": "1f468-1f3ff", "family_man_man_girl_medium_light_skin_tone": "1f468-1f3fc", "whale2": "1f40b", "sagittarius": "2650-fe0f", "children_crossing": "1f6b8", "call_me_hand_dark_skin_tone": "1f919-1f3ff", "ok_woman_medium_dark_skin_tone": "1f646-1f3fe-200d-2640-fe0f", "man_firefighter": "1f468-200d-1f692", "rewind": "23ea", "guardswoman_light_skin_tone": "1f482-1f3fb-200d-2640-fe0f", "woman_technologist_light_skin_tone": "1f469-1f3fb", "woman_pilot_light_skin_tone": "1f469-1f3fb", "raising_hand_woman_light_skin_tone": "1f64b-1f3fb-200d-2640-fe0f", "bowing_man_light_skin_tone": "1f647-1f3fb-200d-2640-fe0f", "frowning_man_medium_skin_tone": "1f64d-1f3fd-200d-2642-fe0f", "shark": "1f988", "sun_behind_rain_cloud": "1f326", "dagger": "1f5e1", "musical_note": "1f3b5", "crossed_fingers_dark_skin_tone": "1f91e-1f3ff", "man_pilot_medium_skin_tone": "1f468-1f3fd", "family_woman_boy_medium_skin_tone": "1f469-1f3fd", "golfing_man_medium_light_skin_tone": "1f3cc-1f3fc-200d-2640-fe0f", "girl": "1f467", "family_man_woman_boy_boy": "1f468-200d-1f469-200d-1f466-200d-1f466", "biking_woman": "1f6b4-200d-2640-fe0f", "cl": "1f191", "raised_back_of_hand_medium_dark_skin_tone": "1f91a-1f3fe", "raising_hand_woman_medium_light_skin_tone": "1f64b-1f3fc-200d-2640-fe0f", "baby_medium_skin_tone": "1f476-1f3fd", "guardsman": "1f482", "woman_astronaut": "1f469-200d-1f680", "tophat": "1f3a9", "honduras": "1f1ed-1f1f3", "mexico": "1f1f2-1f1fd", "nauru": "1f1f3-1f1f7", "mrs_claus_medium_light_skin_tone": "1f936-1f3fc", "weary": "1f629", "womans_hat": "1f452", "person_fencing": "1f93a", "u6708": "1f237-fe0f", "a": "1f170-fe0f", "de": "1f1e9-1f1ea", "lebanon": "1f1f1-1f1e7", "puerto_rico": "1f1f5-1f1f7", "man_mechanic_dark_skin_tone": "1f468-1f3ff", "policeman_dark_skin_tone": "1f46e-1f3ff-200d-2640-fe0f", "kissing_smiling_eyes": "1f619", "avocado": "1f951", "six_pointed_star": "1f52f", "record_button": "23fa", "family_woman_woman_girl_girl_light_skin_tone": "1f469-1f3fb", "mountain_biking_man_medium_light_skin_tone": "1f6b5-1f3fc-200d-2640-fe0f", "couple_with_heart_man_man_medium_dark_skin_tone": "1f468-1f3fe", "family_man_girl_girl_light_skin_tone": "1f468-1f3fb", "post_office": "1f3e3", "telescope": "1f52d", "baby_symbol": "1f6bc", "capital_abcd": "1f520", "woman_singer_light_skin_tone": "1f469-1f3fb", "woman_facepalming_medium_dark_skin_tone": "1f926-1f3fe-200d-2640-fe0f", "ant": "1f41c", "house": "1f3e0", "shield": "1f6e1", "yellow_heart": "1f49b", "u55b6": "1f23a", "senegal": "1f1f8-1f1f3", "united_arab_emirates": "1f1e6-1f1ea", "no_good_woman_medium_skin_tone": "1f645-1f3fd-200d-2640-fe0f", "running_man_medium_light_skin_tone": "1f3c3-1f3fc-200d-2640-fe0f", "beetle": "1f41e", "bus": "1f68c", "flight_arrival": "1f6ec", "black_large_square": "2b1b-fe0f", "white_large_square": "2b1c-fe0f", "woman_technologist_medium_light_skin_tone": "1f469-1f3fc", "skier": "26f7", "ok_hand_medium_light_skin_tone": "1f44c-1f3fc", "rofl": "1f923", "hushed": "1f62f", "ng_man": "1f645-200d-2642-fe0f", "running_woman": "1f3c3-200d-2640-fe0f", "family_man_man_girl_girl": "1f468-200d-1f468-200d-1f467-200d-1f467", "gorilla": "1f98d", "horse_racing_medium_light_skin_tone": "1f3c7-1f3fc", "mountain_biking_man_medium_skin_tone": "1f6b5-1f3fd-200d-2640-fe0f", "disappointed": "1f61e", "dolphin": "1f42c", "green_apple": "1f34f", "honey_pot": "1f36f", "georgia": "1f1ec-1f1ea", "business_suit_levitating": "1f574", "camera": "1f4f7", "ledger": "1f4d2", "woman_cook_medium_skin_tone": "1f469-1f3fd", "bahamas": "1f1e7-1f1f8", "family_woman_woman_girl": "1f469-200d-1f469-200d-1f467", "man_factory_worker_light_skin_tone": "1f468-1f3fb", "golfing_woman_medium_dark_skin_tone": "1f3cc-1f3fe-200d-2640-fe0f", "family_woman_girl": "1f469-200d-1f467", "turtle": "1f422", "mauritius": "1f1f2-1f1fa", "family_man_girl_light_skin_tone": "1f468-1f3fb", "hospital": "1f3e5", "church": "26ea-fe0f", "wheel_of_dharma": "2638-fe0f", "mongolia": "1f1f2-1f1f3", "man_facepalming": "1f926-200d-2642-fe0f", "bed": "1f6cf", "man_artist_medium_light_skin_tone": "1f468-1f3fc", "santa_medium_dark_skin_tone": "1f385-1f3fe", "family_man_woman_girl_medium_light_skin_tone": "1f468-1f3fc", "large_blue_diamond": "1f537", "colombia": "1f1e8-1f1f4", "philippines": "1f1f5-1f1ed", "older_woman_dark_skin_tone": "1f475-1f3ff", "woman_scientist_medium_skin_tone": "1f469-1f3fd", "couplekiss_woman_woman_light_skin_tone": "1f469-1f3fb", "male_detective": "1f575-fe0f", "crossed_swords": "2694-fe0f", "notebook": "1f4d3", "nail_care_medium_light_skin_tone": "1f485-1f3fc", "blonde_man_medium_skin_tone": "1f471-1f3fd-200d-2640-fe0f", "tipping_hand_man_light_skin_tone": "1f481-1f3fb-200d-2642-fe0f", "sweat_smile": "1f605", "white_medium_small_square": "25fd-fe0f", "bowtie": "bowtie", "reminder_ribbon": "1f397", "clamp": "1f5dc", "balance_scale": "2696-fe0f", "postal_horn": "1f4ef", "swimming_woman_dark_skin_tone": "1f3ca-1f3ff-200d-2640-fe0f", "raised_hand_with_fingers_splayed": "1f590", "ok_man": "1f646-200d-2642-fe0f", "new": "1f195", "male_detective_medium_dark_skin_tone": "1f575-1f3fe-200d-2640-fe0f", "computer_mouse": "1f5b1", "hourglass_flowing_sand": "23f3", "bahrain": "1f1e7-1f1ed", "djibouti": "1f1e9-1f1ef", "zimbabwe": "1f1ff-1f1fc", "swimming_man_light_skin_tone": "1f3ca-1f3fb-200d-2640-fe0f", "interrobang": "2049-fe0f", "clock8": "1f557", "pancakes": "1f95e", "thermometer": "1f321", "label": "1f3f7", "denmark": "1f1e9-1f1f0", "raising_hand_man_medium_light_skin_tone": "1f64b-1f3fc-200d-2642-fe0f", "frowning_man_medium_dark_skin_tone": "1f64d-1f3fe-200d-2642-fe0f", "point_right": "1f449", "guitar": "1f3b8", "family_woman_woman_girl_medium_skin_tone": "1f469-1f3fd", "woman_juggling_medium_skin_tone": "1f939-1f3fd-200d-2640-fe0f", "man_health_worker": "1f468-200d-2695-fe0f", "stew": "1f372", "surfing_man": "1f3c4", "twisted_rightwards_arrows": "1f500", "timor_leste": "1f1f9-1f1f1", "weight_lifting_woman": "1f3cb-fe0f-200d-2640-fe0f", "amphora": "1f3fa", "heart": "2764-fe0f", "bowing_man_medium_dark_skin_tone": "1f647-1f3fe-200d-2640-fe0f"} diff --git a/vendor/github.com/mattermost/mattermost-server/model/emoji_search.go b/vendor/github.com/mattermost/mattermost-server/model/emoji_search.go new file mode 100644 index 00000000..3a768a57 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/emoji_search.go @@ -0,0 +1,25 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type EmojiSearch struct { + Term string `json:"term"` + PrefixOnly bool `json:"prefix_only"` +} + +func (es *EmojiSearch) ToJson() string { + b, _ := json.Marshal(es) + return string(b) +} + +func EmojiSearchFromJson(data io.Reader) *EmojiSearch { + var es *EmojiSearch + json.NewDecoder(data).Decode(&es) + return es +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/file.go b/vendor/github.com/mattermost/mattermost-server/model/file.go new file mode 100644 index 00000000..c7ffbf0b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/file.go @@ -0,0 +1,34 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image +) + +var ( + IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"} + IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"} +) + +type FileUploadResponse struct { + FileInfos []*FileInfo `json:"file_infos"` + ClientIds []string `json:"client_ids"` +} + +func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse { + var o *FileUploadResponse + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *FileUploadResponse) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/file_info.go b/vendor/github.com/mattermost/mattermost-server/model/file_info.go new file mode 100644 index 00000000..e0bbfcfc --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/file_info.go @@ -0,0 +1,170 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "bytes" + "encoding/json" + "image" + "image/gif" + "io" + "mime" + "net/http" + "path/filepath" + "strings" +) + +type FileInfo struct { + Id string `json:"id"` + CreatorId string `json:"user_id"` + PostId string `json:"post_id,omitempty"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + Path string `json:"-"` // not sent back to the client + ThumbnailPath string `json:"-"` // not sent back to the client + PreviewPath string `json:"-"` // not sent back to the client + Name string `json:"name"` + Extension string `json:"extension"` + Size int64 `json:"size"` + MimeType string `json:"mime_type"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + HasPreviewImage bool `json:"has_preview_image,omitempty"` +} + +func (info *FileInfo) ToJson() string { + b, _ := json.Marshal(info) + 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 + } +} + +func FileInfosToJson(infos []*FileInfo) string { + b, _ := json.Marshal(infos) + return string(b) +} + +func FileInfosFromJson(data io.Reader) []*FileInfo { + decoder := json.NewDecoder(data) + + var infos []*FileInfo + if err := decoder.Decode(&infos); err != nil { + return nil + } else { + return infos + } +} + +func (o *FileInfo) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + } + + if o.UpdateAt < o.CreateAt { + o.UpdateAt = o.CreateAt + } +} + +func (o *FileInfo) IsValid() *AppError { + if len(o.Id) != 26 { + return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.CreatorId) != 26 { + return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.PostId) != 0 && len(o.PostId) != 26 { + return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.Path == "" { + return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + return nil +} + +func (o *FileInfo) IsImage() bool { + return strings.HasPrefix(o.MimeType, "image") +} + +func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) { + info := &FileInfo{ + Name: name, + Size: int64(len(data)), + } + var err *AppError + + extension := strings.ToLower(filepath.Ext(name)) + info.MimeType = mime.TypeByExtension(extension) + + if extension != "" && extension[0] == '.' { + // The client expects a file extension without the leading period + info.Extension = extension[1:] + } else { + info.Extension = extension + } + + if info.IsImage() { + // Only set the width and height if it's actually an image that we can understand + if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil { + info.Width = config.Width + info.Height = config.Height + + if info.MimeType == "image/gif" { + // Just show the gif itself instead of a preview image for animated gifs + if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { + // Still return the rest of the info even though it doesn't appear to be an actual gif + info.HasPreviewImage = true + err = NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name, http.StatusBadRequest) + } else { + info.HasPreviewImage = len(gifConfig.Image) == 1 + } + } else { + info.HasPreviewImage = true + } + } + } + + return info, err +} + +func GetEtagForFileInfos(infos []*FileInfo) string { + if len(infos) == 0 { + return Etag() + } + + var maxUpdateAt int64 + + for _, info := range infos { + if info.UpdateAt > maxUpdateAt { + maxUpdateAt = info.UpdateAt + } + } + + return Etag(infos[0].PostId, maxUpdateAt) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/gitlab.go b/vendor/github.com/mattermost/mattermost-server/model/gitlab.go new file mode 100644 index 00000000..8777614c --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/gitlab.go @@ -0,0 +1,8 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +const ( + USER_AUTH_SERVICE_GITLAB = "gitlab" +) diff --git a/vendor/github.com/mattermost/mattermost-server/model/incoming_webhook.go b/vendor/github.com/mattermost/mattermost-server/model/incoming_webhook.go new file mode 100644 index 00000000..3856d22f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/incoming_webhook.go @@ -0,0 +1,216 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "regexp" +) + +const ( + DEFAULT_WEBHOOK_USERNAME = "webhook" +) + +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"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + ChannelLocked bool `json:"channel_locked"` +} + +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 []*SlackAttachment `json:"attachments"` + Type string `json:"type"` +} + +func (o *IncomingWebhook) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func IncomingWebhookFromJson(data io.Reader) *IncomingWebhook { + var o *IncomingWebhook + json.NewDecoder(data).Decode(&o) + return o +} + +func IncomingWebhookListToJson(l []*IncomingWebhook) string { + b, _ := json.Marshal(l) + return string(b) +} + +func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook { + var o []*IncomingWebhook + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *IncomingWebhook) IsValid() *AppError { + + if len(o.Id) != 26 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "", http.StatusBadRequest) + + } + + if o.CreateAt == 0 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.UserId) != 26 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ChannelId) != 26 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.TeamId) != 26 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.DisplayName) > 64 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Description) > 500 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Username) > 64 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.username.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.IconURL) > 1024 { + return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.icon_url.app_error", nil, "", http.StatusBadRequest) + } + + 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() +} + +// escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. +// Context: +// JSON strings are not supposed to contain control characters such as \n, \t, +// ... but some incoming webhooks might still send invalid JSON and we want to +// try to handle that. An example invalid JSON string from an incoming webhook +// might look like this (strings for both "text" and "fallback" attributes are +// invalid JSON strings because they contain unescaped newlines and tabs): +// `{ +// "text": "this is a test +// that contains a newline and tabs", +// "attachments": [ +// { +// "fallback": "Required plain-text summary of the attachment +// that contains a newline and tabs", +// "color": "#36a64f", +// ... +// "text": "Optional text that appears within the attachment +// that contains a newline and tabs", +// ... +// "thumb_url": "http://example.com/path/to/thumb.png" +// } +// ] +// }` +// This function will search for `"key": "value"` pairs, and escape \n, \t +// from the value. +func escapeControlCharsFromPayload(by []byte) []byte { + // we'll search for `"text": "..."` or `"fallback": "..."`, ... + keys := "text|fallback|pretext|author_name|title|value" + + // the regexp reads like this: + // (?s): this flag let . match \n (default is false) + // "(keys)": we search for the keys defined above + // \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs + // ": a double-quote + // (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote + // ": a double-quote + r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` + re := regexp.MustCompile(r) + + // the function that will escape \n and \t on the regexp matches + repl := func(b []byte) []byte { + if bytes.Contains(b, []byte("\n")) { + b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) + } + if bytes.Contains(b, []byte("\t")) { + b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) + } + + return b + } + + return re.ReplaceAllFunc(by, repl) +} + +func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { + decoder := json.NewDecoder(bytes.NewReader(by)) + var o IncomingWebhookRequest + err := decoder.Decode(&o) + if err == nil { + return &o, nil + } else { + return nil, err + } +} + +func IncomingWebhookRequestFromJson(data io.Reader) (*IncomingWebhookRequest, *AppError) { + buf := new(bytes.Buffer) + buf.ReadFrom(data) + by := buf.Bytes() + + // Try to decode the JSON data. Only if it fails, try to escape control + // characters from the strings contained in the JSON data. + o, err := decodeIncomingWebhookRequest(by) + if err != nil { + o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) + if err != nil { + return nil, NewAppError("IncomingWebhookRequestFromJson", "model.incoming_hook.parse_data.app_error", nil, err.Error(), http.StatusBadRequest) + } + } + + o.Attachments = StringifySlackFieldValue(o.Attachments) + + return o, nil +} + +func (o *IncomingWebhookRequest) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/initial_load.go b/vendor/github.com/mattermost/mattermost-server/model/initial_load.go new file mode 100644 index 00000000..3be68044 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/initial_load.go @@ -0,0 +1,30 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type InitialLoad struct { + User *User `json:"user"` + TeamMembers []*TeamMember `json:"team_members"` + Teams []*Team `json:"teams"` + Preferences Preferences `json:"preferences"` + ClientCfg map[string]string `json:"client_cfg"` + LicenseCfg map[string]string `json:"license_cfg"` + NoAccounts bool `json:"no_accounts"` +} + +func (me *InitialLoad) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func InitialLoadFromJson(data io.Reader) *InitialLoad { + var o *InitialLoad + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/job.go b/vendor/github.com/mattermost/mattermost-server/model/job.go new file mode 100644 index 00000000..c1661495 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/job.go @@ -0,0 +1,120 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "time" +) + +const ( + JOB_TYPE_DATA_RETENTION = "data_retention" + JOB_TYPE_MESSAGE_EXPORT = "message_export" + JOB_TYPE_ELASTICSEARCH_POST_INDEXING = "elasticsearch_post_indexing" + JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION = "elasticsearch_post_aggregation" + JOB_TYPE_LDAP_SYNC = "ldap_sync" + JOB_TYPE_MIGRATIONS = "migrations" + + JOB_STATUS_PENDING = "pending" + JOB_STATUS_IN_PROGRESS = "in_progress" + JOB_STATUS_SUCCESS = "success" + JOB_STATUS_ERROR = "error" + JOB_STATUS_CANCEL_REQUESTED = "cancel_requested" + JOB_STATUS_CANCELED = "canceled" +) + +type Job struct { + Id string `json:"id"` + Type string `json:"type"` + Priority int64 `json:"priority"` + CreateAt int64 `json:"create_at"` + StartAt int64 `json:"start_at"` + LastActivityAt int64 `json:"last_activity_at"` + Status string `json:"status"` + Progress int64 `json:"progress"` + Data map[string]string `json:"data"` +} + +func (j *Job) IsValid() *AppError { + if len(j.Id) != 26 { + return NewAppError("Job.IsValid", "model.job.is_valid.id.app_error", nil, "id="+j.Id, http.StatusBadRequest) + } + + if j.CreateAt == 0 { + return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest) + } + + switch j.Type { + case JOB_TYPE_DATA_RETENTION: + case JOB_TYPE_ELASTICSEARCH_POST_INDEXING: + case JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION: + case JOB_TYPE_LDAP_SYNC: + case JOB_TYPE_MESSAGE_EXPORT: + case JOB_TYPE_MIGRATIONS: + default: + return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest) + } + + switch j.Status { + case JOB_STATUS_PENDING: + case JOB_STATUS_IN_PROGRESS: + case JOB_STATUS_SUCCESS: + case JOB_STATUS_ERROR: + case JOB_STATUS_CANCEL_REQUESTED: + case JOB_STATUS_CANCELED: + default: + return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest) + } + + return nil +} + +func (js *Job) ToJson() string { + b, _ := json.Marshal(js) + return string(b) +} + +func JobFromJson(data io.Reader) *Job { + var job Job + if err := json.NewDecoder(data).Decode(&job); err == nil { + return &job + } else { + return nil + } +} + +func JobsToJson(jobs []*Job) string { + b, _ := json.Marshal(jobs) + return string(b) +} + +func JobsFromJson(data io.Reader) []*Job { + var jobs []*Job + if err := json.NewDecoder(data).Decode(&jobs); err == nil { + return jobs + } else { + return nil + } +} + +func (js *Job) DataToJson() string { + b, _ := json.Marshal(js.Data) + return string(b) +} + +type Worker interface { + Run() + Stop() + JobChannel() chan<- Job +} + +type Scheduler interface { + Name() string + JobType() string + Enabled(cfg *Config) bool + NextScheduleTime(cfg *Config, now time.Time, pendingJobs bool, lastSuccessfulJob *Job) *time.Time + ScheduleJob(cfg *Config, pendingJobs bool, lastSuccessfulJob *Job) (*Job, *AppError) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/ldap.go b/vendor/github.com/mattermost/mattermost-server/model/ldap.go new file mode 100644 index 00000000..9051c5a3 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/ldap.go @@ -0,0 +1,8 @@ +// Copyright (c) 2016-present 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/mattermost-server/model/license.go b/vendor/github.com/mattermost/mattermost-server/model/license.go new file mode 100644 index 00000000..c30fecf7 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/license.go @@ -0,0 +1,218 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" +) + +const ( + EXPIRED_LICENSE_ERROR = "api.license.add_license.expired.app_error" + INVALID_LICENSE_ERROR = "api.license.add_license.invalid.app_error" +) + +type LicenseRecord struct { + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + 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"` + GoogleOAuth *bool `json:"google_oauth"` + Office365OAuth *bool `json:"office365_oauth"` + Compliance *bool `json:"compliance"` + Cluster *bool `json:"cluster"` + Metrics *bool `json:"metrics"` + MHPNS *bool `json:"mhpns"` + SAML *bool `json:"saml"` + Elasticsearch *bool `json:"elastic_search"` + Announcement *bool `json:"announcement"` + ThemeManagement *bool `json:"theme_management"` + EmailNotificationContents *bool `json:"email_notification_contents"` + DataRetention *bool `json:"data_retention"` + MessageExport *bool `json:"message_export"` + CustomPermissionsSchemes *bool `json:"custom_permissions_schemes"` + CustomTermsOfService *bool `json:"custom_terms_of_service"` + + // after we enabled more features for webrtc we'll need to control them with this + FutureFeatures *bool `json:"future_features"` +} + +func (f *Features) ToMap() map[string]interface{} { + return map[string]interface{}{ + "ldap": *f.LDAP, + "mfa": *f.MFA, + "google": *f.GoogleOAuth, + "office365": *f.Office365OAuth, + "compliance": *f.Compliance, + "cluster": *f.Cluster, + "metrics": *f.Metrics, + "mhpns": *f.MHPNS, + "saml": *f.SAML, + "elastic_search": *f.Elasticsearch, + "email_notification_contents": *f.EmailNotificationContents, + "data_retention": *f.DataRetention, + "message_export": *f.MessageExport, + "custom_permissions_schemes": *f.CustomPermissionsSchemes, + "future": *f.FutureFeatures, + } +} + +func (f *Features) SetDefaults() { + if f.FutureFeatures == nil { + f.FutureFeatures = NewBool(true) + } + + if f.Users == nil { + f.Users = NewInt(0) + } + + if f.LDAP == nil { + f.LDAP = NewBool(*f.FutureFeatures) + } + + if f.MFA == nil { + f.MFA = NewBool(*f.FutureFeatures) + } + + if f.GoogleOAuth == nil { + f.GoogleOAuth = NewBool(*f.FutureFeatures) + } + + if f.Office365OAuth == nil { + f.Office365OAuth = NewBool(*f.FutureFeatures) + } + + if f.Compliance == nil { + f.Compliance = NewBool(*f.FutureFeatures) + } + + if f.Cluster == nil { + f.Cluster = NewBool(*f.FutureFeatures) + } + + if f.Metrics == nil { + f.Metrics = NewBool(*f.FutureFeatures) + } + + if f.MHPNS == nil { + f.MHPNS = NewBool(*f.FutureFeatures) + } + + if f.SAML == nil { + f.SAML = NewBool(*f.FutureFeatures) + } + + if f.Elasticsearch == nil { + f.Elasticsearch = NewBool(*f.FutureFeatures) + } + + if f.Announcement == nil { + f.Announcement = NewBool(true) + } + + if f.ThemeManagement == nil { + f.ThemeManagement = NewBool(true) + } + + if f.EmailNotificationContents == nil { + f.EmailNotificationContents = NewBool(*f.FutureFeatures) + } + + if f.DataRetention == nil { + f.DataRetention = NewBool(*f.FutureFeatures) + } + + if f.MessageExport == nil { + f.MessageExport = NewBool(*f.FutureFeatures) + } + + if f.CustomPermissionsSchemes == nil { + f.CustomPermissionsSchemes = NewBool(*f.FutureFeatures) + } + + if f.CustomTermsOfService == nil { + f.CustomTermsOfService = NewBool(*f.FutureFeatures) + } +} + +func (l *License) IsExpired() bool { + return l.ExpiresAt < GetMillis() +} + +func (l *License) IsStarted() bool { + return l.StartsAt < GetMillis() +} + +func (l *License) ToJson() string { + b, _ := json.Marshal(l) + return string(b) +} + +// NewTestLicense returns a license that expires in the future and has the given features. +func NewTestLicense(features ...string) *License { + ret := &License{ + ExpiresAt: GetMillis() + 90*24*60*60*1000, + Customer: &Customer{}, + Features: &Features{}, + } + ret.Features.SetDefaults() + + featureMap := map[string]bool{} + for _, feature := range features { + featureMap[feature] = true + } + featureJson, _ := json.Marshal(featureMap) + json.Unmarshal(featureJson, &ret.Features) + + return ret +} + +func LicenseFromJson(data io.Reader) *License { + var o *License + json.NewDecoder(data).Decode(&o) + return o +} + +func (lr *LicenseRecord) IsValid() *AppError { + if len(lr.Id) != 26 { + return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if lr.CreateAt == 0 { + return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) + } + + if len(lr.Bytes) == 0 || len(lr.Bytes) > 10000 { + return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (lr *LicenseRecord) PreSave() { + lr.CreateAt = GetMillis() +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/manifest.go b/vendor/github.com/mattermost/mattermost-server/model/manifest.go new file mode 100644 index 00000000..6a7df59f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/manifest.go @@ -0,0 +1,294 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v2" +) + +type PluginOption struct { + // The display name for the option. + DisplayName string `json:"display_name" yaml:"display_name"` + + // The string value for the option. + Value string `json:"value" yaml:"value"` +} + +type PluginSetting struct { + // The key that the setting will be assigned to in the configuration file. + Key string `json:"key" yaml:"key"` + + // The display name for the setting. + DisplayName string `json:"display_name" yaml:"display_name"` + + // The type of the setting. + // + // "bool" will result in a boolean true or false setting. + // + // "dropdown" will result in a string setting that allows the user to select from a list of + // pre-defined options. + // + // "generated" will result in a string setting that is set to a random, cryptographically secure + // string. + // + // "radio" will result in a string setting that allows the user to select from a short selection + // of pre-defined options. + // + // "text" will result in a string setting that can be typed in manually. + // + // "username" will result in a text setting that will autocomplete to a username. + Type string `json:"type" yaml:"type"` + + // The help text to display to the user. + HelpText string `json:"help_text" yaml:"help_text"` + + // The help text to display alongside the "Regenerate" button for settings of the "generated" type. + RegenerateHelpText string `json:"regenerate_help_text,omitempty" yaml:"regenerate_help_text,omitempty"` + + // The placeholder to display for "text", "generated" and "username" types when blank. + Placeholder string `json:"placeholder" yaml:"placeholder"` + + // The default value of the setting. + Default interface{} `json:"default" yaml:"default"` + + // For "radio" or "dropdown" settings, this is the list of pre-defined options that the user can choose + // from. + Options []*PluginOption `json:"options,omitempty" yaml:"options,omitempty"` +} + +type PluginSettingsSchema struct { + // Optional text to display above the settings. + Header string `json:"header" yaml:"header"` + + // Optional text to display below the settings. + Footer string `json:"footer" yaml:"footer"` + + // A list of setting definitions. + Settings []*PluginSetting `json:"settings" yaml:"settings"` +} + +// The plugin manifest defines the metadata required to load and present your plugin. The manifest +// file should be named plugin.json or plugin.yaml and placed in the top of your +// plugin bundle. +// +// Example plugin.yaml: +// +// id: com.mycompany.myplugin +// name: My Plugin +// description: This is my plugin. It does stuff. +// server: +// executable: myplugin +// settings_schema: +// settings: +// - key: enable_extra_thing +// type: bool +// display_name: Enable Extra Thing +// help_text: When true, an extra thing will be enabled! +// default: false +type Manifest struct { + // The id is a globally unique identifier that represents your plugin. Ids must be at least + // 3 characters, at most 190 characters and must match ^[a-zA-Z0-9-_\.]+$. + // Reverse-DNS notation using a name you control is a good option, e.g. "com.mycompany.myplugin". + Id string `json:"id" yaml:"id"` + + // The name to be displayed for the plugin. + Name string `json:"name,omitempty" yaml:"name,omitempty"` + + // A description of what your plugin is and does. + Description string `json:"description,omitempty" yaml:"description,omitempty"` + + // A version number for your plugin. Semantic versioning is recommended: http://semver.org + Version string `json:"version" yaml:"version"` + + // Server defines the server-side portion of your plugin. + Server *ManifestServer `json:"server,omitempty" yaml:"server,omitempty"` + + // Backend is a deprecated flag for defining the server-side portion of your plugin. Going forward, use Server instead. + Backend *ManifestServer `json:"backend,omitempty" yaml:"backend,omitempty"` + + // If your plugin extends the web app, you'll need to define webapp. + Webapp *ManifestWebapp `json:"webapp,omitempty" yaml:"webapp,omitempty"` + + // To allow administrators to configure your plugin via the Mattermost system console, you can + // provide your settings schema. + SettingsSchema *PluginSettingsSchema `json:"settings_schema,omitempty" yaml:"settings_schema,omitempty"` +} + +type ManifestServer struct { + // Executables are the paths to your executable binaries, specifying multiple entry points + // for different platforms when bundled together in a single plugin. + Executables *ManifestExecutables `json:"executables,omitempty" yaml:"executables,omitempty"` + + // Executable is the path to your executable binary. This should be relative to the root + // of your bundle and the location of the manifest file. + // + // On Windows, this file must have a ".exe" extension. + // + // If your plugin is compiled for multiple platforms, consider bundling them together + // and using the Executables field instead. + Executable string `json:"executable" yaml:"executable"` +} + +type ManifestExecutables struct { + // LinuxAmd64 is the path to your executable binary for the corresponding platform + LinuxAmd64 string `json:"linux-amd64,omitempty" yaml:"linux-amd64,omitempty"` + // DarwinAmd64 is the path to your executable binary for the corresponding platform + DarwinAmd64 string `json:"darwin-amd64,omitempty" yaml:"darwin-amd64,omitempty"` + // WindowsAmd64 is the path to your executable binary for the corresponding platform + // This file must have a ".exe" extension + WindowsAmd64 string `json:"windows-amd64,omitempty" yaml:"windows-amd64,omitempty"` +} + +type ManifestWebapp struct { + // The path to your webapp bundle. This should be relative to the root of your bundle and the + // location of the manifest file. + BundlePath string `json:"bundle_path" yaml:"bundle_path"` + + // BundleHash is the 64-bit FNV-1a hash of the webapp bundle, computed when the plugin is loaded + BundleHash []byte `json:"-"` +} + +func (m *Manifest) ToJson() string { + b, _ := json.Marshal(m) + return string(b) +} + +func ManifestListToJson(m []*Manifest) string { + b, _ := json.Marshal(m) + return string(b) +} + +func ManifestFromJson(data io.Reader) *Manifest { + var m *Manifest + json.NewDecoder(data).Decode(&m) + return m +} + +func ManifestListFromJson(data io.Reader) []*Manifest { + var manifests []*Manifest + json.NewDecoder(data).Decode(&manifests) + return manifests +} + +func (m *Manifest) HasClient() bool { + return m.Webapp != nil +} + +func (m *Manifest) ClientManifest() *Manifest { + cm := new(Manifest) + *cm = *m + cm.Name = "" + cm.Description = "" + cm.Server = nil + if cm.Webapp != nil { + cm.Webapp = new(ManifestWebapp) + *cm.Webapp = *m.Webapp + cm.Webapp.BundlePath = "/static/" + m.Id + "/" + fmt.Sprintf("%s_%x_bundle.js", m.Id, m.Webapp.BundleHash) + } + return cm +} + +// GetExecutableForRuntime returns the path to the executable for the given runtime architecture. +// +// If the manifest defines multiple executables, but none match, or if only a single executable +// is defined, the Executable field will be returned. This method does not guarantee that the +// resulting binary can actually execute on the given platform. +func (m *Manifest) GetExecutableForRuntime(goOs, goArch string) string { + server := m.Server + + // Support the deprecated backend parameter. + if server == nil { + server = m.Backend + } + + if server == nil { + return "" + } + + var executable string + if server.Executables != nil { + if goOs == "linux" && goArch == "amd64" { + executable = server.Executables.LinuxAmd64 + } else if goOs == "darwin" && goArch == "amd64" { + executable = server.Executables.DarwinAmd64 + } else if goOs == "windows" && goArch == "amd64" { + executable = server.Executables.WindowsAmd64 + } + } + + if executable == "" { + executable = server.Executable + } + + return executable +} + +func (m *Manifest) HasServer() bool { + return m.Server != nil || m.Backend != nil +} + +func (m *Manifest) HasWebapp() bool { + return m.Webapp != nil +} + +// FindManifest will find and parse the manifest in a given directory. +// +// In all cases other than a does-not-exist error, path is set to the path of the manifest file that was +// found. +// +// Manifests are JSON or YAML files named plugin.json, plugin.yaml, or plugin.yml. +func FindManifest(dir string) (manifest *Manifest, path string, err error) { + for _, name := range []string{"plugin.yml", "plugin.yaml"} { + path = filepath.Join(dir, name) + f, ferr := os.Open(path) + if ferr != nil { + if !os.IsNotExist(ferr) { + err = ferr + return + } + continue + } + b, ioerr := ioutil.ReadAll(f) + f.Close() + if ioerr != nil { + err = ioerr + return + } + var parsed Manifest + err = yaml.Unmarshal(b, &parsed) + if err != nil { + return + } + manifest = &parsed + manifest.Id = strings.ToLower(manifest.Id) + return + } + + path = filepath.Join(dir, "plugin.json") + f, ferr := os.Open(path) + if ferr != nil { + if os.IsNotExist(ferr) { + path = "" + } + err = ferr + return + } + defer f.Close() + var parsed Manifest + err = json.NewDecoder(f).Decode(&parsed) + if err != nil { + return + } + manifest = &parsed + manifest.Id = strings.ToLower(manifest.Id) + return +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/message_export.go b/vendor/github.com/mattermost/mattermost-server/model/message_export.go new file mode 100644 index 00000000..1cf764a6 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/message_export.go @@ -0,0 +1,27 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +type MessageExport struct { + TeamId *string + TeamName *string + TeamDisplayName *string + + ChannelId *string + ChannelName *string + ChannelDisplayName *string + ChannelType *string + + UserId *string + UserEmail *string + Username *string + + PostId *string + PostCreateAt *int64 + PostMessage *string + PostType *string + PostRootId *string + PostOriginalId *string + PostFileIds StringArray +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/mfa_secret.go b/vendor/github.com/mattermost/mattermost-server/model/mfa_secret.go new file mode 100644 index 00000000..23a903c8 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/mfa_secret.go @@ -0,0 +1,25 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type MfaSecret struct { + Secret string `json:"secret"` + QRCode string `json:"qr_code"` +} + +func (me *MfaSecret) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func MfaSecretFromJson(data io.Reader) *MfaSecret { + var me *MfaSecret + json.NewDecoder(data).Decode(&me) + return me +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/migration.go b/vendor/github.com/mattermost/mattermost-server/model/migration.go new file mode 100644 index 00000000..ead7acce --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/migration.go @@ -0,0 +1,8 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +const ( + MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2 = "migration_advanced_permissions_phase_2" +) diff --git a/vendor/github.com/mattermost/mattermost-server/model/oauth.go b/vendor/github.com/mattermost/mattermost-server/model/oauth.go new file mode 100644 index 00000000..6f662a5a --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/oauth.go @@ -0,0 +1,152 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "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" + OAUTH_ACTION_MOBILE = "mobile" + OAUTH_ACTION_CLIENT = "client" +) + +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"` + IconURL string `json:"icon_url"` + CallbackUrls StringArray `json:"callback_urls"` + Homepage string `json:"homepage"` + IsTrusted bool `json:"is_trusted"` +} + +// 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 NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "", http.StatusBadRequest) + } + + if a.CreateAt == 0 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if a.UpdateAt == 0 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if len(a.CreatorId) != 26 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if len(a.Name) == 0 || len(a.Name) > 64 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + for _, callback := range a.CallbackUrls { + if !IsValidHttpUrl(callback) { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest) + } + } + + if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(a.Description) > 512 { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + + if len(a.IconURL) > 0 { + if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { + return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) + } + } + + 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 +} + +// PreUpdate should be run before updating the app in the db. +func (a *OAuthApp) PreUpdate() { + a.UpdateAt = GetMillis() +} + +func (a *OAuthApp) ToJson() string { + b, _ := json.Marshal(a) + 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 +} + +func OAuthAppFromJson(data io.Reader) *OAuthApp { + var app *OAuthApp + json.NewDecoder(data).Decode(&app) + return app +} + +func OAuthAppListToJson(l []*OAuthApp) string { + b, _ := json.Marshal(l) + return string(b) +} + +func OAuthAppListFromJson(data io.Reader) []*OAuthApp { + var o []*OAuthApp + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/outgoing_webhook.go b/vendor/github.com/mattermost/mattermost-server/model/outgoing_webhook.go new file mode 100644 index 00000000..5f7a67d0 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/outgoing_webhook.go @@ -0,0 +1,264 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" +) + +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"` + TriggerWhen int `json:"trigger_when"` + CallbackURLs StringArray `json:"callback_urls"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + ContentType string `json:"content_type"` + Username string `json:"username"` + IconURL string `json:"icon_url"` +} + +type OutgoingWebhookPayload struct { + Token string `json:"token"` + TeamId string `json:"team_id"` + TeamDomain string `json:"team_domain"` + ChannelId string `json:"channel_id"` + ChannelName string `json:"channel_name"` + Timestamp int64 `json:"timestamp"` + UserId string `json:"user_id"` + UserName string `json:"user_name"` + PostId string `json:"post_id"` + Text string `json:"text"` + TriggerWord string `json:"trigger_word"` + FileIds string `json:"file_ids"` +} + +type OutgoingWebhookResponse struct { + Text *string `json:"text"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + Props StringInterface `json:"props"` + Attachments []*SlackAttachment `json:"attachments"` + Type string `json:"type"` + ResponseType string `json:"response_type"` +} + +const OUTGOING_HOOK_RESPONSE_TYPE_COMMENT = "comment" + +func (o *OutgoingWebhookPayload) ToJSON() string { + b, _ := json.Marshal(o) + return string(b) +} + +func (o *OutgoingWebhookPayload) ToFormValues() string { + v := url.Values{} + v.Set("token", o.Token) + v.Set("team_id", o.TeamId) + v.Set("team_domain", o.TeamDomain) + v.Set("channel_id", o.ChannelId) + v.Set("channel_name", o.ChannelName) + v.Set("timestamp", strconv.FormatInt(o.Timestamp/1000, 10)) + v.Set("user_id", o.UserId) + v.Set("user_name", o.UserName) + v.Set("post_id", o.PostId) + v.Set("text", o.Text) + v.Set("trigger_word", o.TriggerWord) + v.Set("file_ids", o.FileIds) + + return v.Encode() +} + +func (o *OutgoingWebhook) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func OutgoingWebhookFromJson(data io.Reader) *OutgoingWebhook { + var o *OutgoingWebhook + json.NewDecoder(data).Decode(&o) + return o +} + +func OutgoingWebhookListToJson(l []*OutgoingWebhook) string { + b, _ := json.Marshal(l) + return string(b) +} + +func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook { + var o []*OutgoingWebhook + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *OutgoingWebhookResponse) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func OutgoingWebhookResponseFromJson(data io.Reader) *OutgoingWebhookResponse { + var o *OutgoingWebhookResponse + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *OutgoingWebhook) IsValid() *AppError { + + if len(o.Id) != 26 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Token) != 26 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.CreatorId) != 26 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.TeamId) != 26 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.TriggerWords) != 0 { + for _, triggerWord := range o.TriggerWords { + if len(triggerWord) == 0 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest) + } + } + } + + if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest) + } + + for _, callback := range o.CallbackURLs { + if !IsValidHttpUrl(callback) { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest) + } + } + + if len(o.DisplayName) > 64 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Description) > 500 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ContentType) > 128 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest) + } + + if o.TriggerWhen > 1 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.Username) > 64 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.username.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.IconURL) > 1024 { + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.icon_url.app_error", nil, "", http.StatusBadRequest) + } + + 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) TriggerWordExactMatch(word string) bool { + if len(word) == 0 { + return false + } + + for _, trigger := range o.TriggerWords { + if trigger == word { + return true + } + } + + return false +} + +func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { + if len(word) == 0 { + return false + } + + for _, trigger := range o.TriggerWords { + if strings.HasPrefix(word, trigger) { + return true + } + } + + return false +} + +func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) { + if len(word) == 0 { + return + } + + if isExactMatch { + for _, trigger := range o.TriggerWords { + if trigger == word { + triggerWord = trigger + break + } + } + } else { + for _, trigger := range o.TriggerWords { + if strings.HasPrefix(word, trigger) { + triggerWord = trigger + break + } + } + } + + return triggerWord +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/permission.go b/vendor/github.com/mattermost/mattermost-server/model/permission.go new file mode 100644 index 00000000..737321cc --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/permission.go @@ -0,0 +1,466 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +const ( + PERMISSION_SCOPE_SYSTEM = "system_scope" + PERMISSION_SCOPE_TEAM = "team_scope" + PERMISSION_SCOPE_CHANNEL = "channel_scope" +) + +type Permission struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Scope string `json:"scope"` +} + +var PERMISSION_INVITE_USER *Permission +var PERMISSION_ADD_USER_TO_TEAM *Permission +var PERMISSION_USE_SLASH_COMMANDS *Permission +var PERMISSION_MANAGE_SLASH_COMMANDS *Permission +var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission +var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission +var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission +var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission +var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission +var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission +var PERMISSION_MANAGE_ROLES *Permission +var PERMISSION_MANAGE_TEAM_ROLES *Permission +var PERMISSION_MANAGE_CHANNEL_ROLES *Permission +var PERMISSION_CREATE_DIRECT_CHANNEL *Permission +var PERMISSION_CREATE_GROUP_CHANNEL *Permission +var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission +var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission +var PERMISSION_LIST_TEAM_CHANNELS *Permission +var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission +var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission +var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission +var PERMISSION_EDIT_OTHER_USERS *Permission +var PERMISSION_READ_CHANNEL *Permission +var PERMISSION_READ_PUBLIC_CHANNEL *Permission +var PERMISSION_ADD_REACTION *Permission +var PERMISSION_REMOVE_REACTION *Permission +var PERMISSION_REMOVE_OTHERS_REACTIONS *Permission +var PERMISSION_PERMANENT_DELETE_USER *Permission +var PERMISSION_UPLOAD_FILE *Permission +var PERMISSION_GET_PUBLIC_LINK *Permission +var PERMISSION_MANAGE_WEBHOOKS *Permission +var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission +var PERMISSION_MANAGE_OAUTH *Permission +var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission +var PERMISSION_MANAGE_EMOJIS *Permission +var PERMISSION_MANAGE_OTHERS_EMOJIS *Permission +var PERMISSION_CREATE_POST *Permission +var PERMISSION_CREATE_POST_PUBLIC *Permission +var PERMISSION_CREATE_POST_EPHEMERAL *Permission +var PERMISSION_EDIT_POST *Permission +var PERMISSION_EDIT_OTHERS_POSTS *Permission +var PERMISSION_DELETE_POST *Permission +var PERMISSION_DELETE_OTHERS_POSTS *Permission +var PERMISSION_REMOVE_USER_FROM_TEAM *Permission +var PERMISSION_CREATE_TEAM *Permission +var PERMISSION_MANAGE_TEAM *Permission +var PERMISSION_IMPORT_TEAM *Permission +var PERMISSION_VIEW_TEAM *Permission +var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission +var PERMISSION_MANAGE_JOBS *Permission +var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission +var PERMISSION_READ_USER_ACCESS_TOKEN *Permission +var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission + +// General permission that encompasses all system admin functions +// in the future this could be broken up to allow access to some +// admin functions but not others +var PERMISSION_MANAGE_SYSTEM *Permission + +var ALL_PERMISSIONS []*Permission + +func initializePermissions() { + PERMISSION_INVITE_USER = &Permission{ + "invite_user", + "authentication.permissions.team_invite_user.name", + "authentication.permissions.team_invite_user.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_ADD_USER_TO_TEAM = &Permission{ + "add_user_to_team", + "authentication.permissions.add_user_to_team.name", + "authentication.permissions.add_user_to_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_USE_SLASH_COMMANDS = &Permission{ + "use_slash_commands", + "authentication.permissions.team_use_slash_commands.name", + "authentication.permissions.team_use_slash_commands.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{ + "manage_slash_commands", + "authentication.permissions.manage_slash_commands.name", + "authentication.permissions.manage_slash_commands.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{ + "manage_others_slash_commands", + "authentication.permissions.manage_others_slash_commands.name", + "authentication.permissions.manage_others_slash_commands.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{ + "create_public_channel", + "authentication.permissions.create_public_channel.name", + "authentication.permissions.create_public_channel.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{ + "create_private_channel", + "authentication.permissions.create_private_channel.name", + "authentication.permissions.create_private_channel.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{ + "manage_public_channel_members", + "authentication.permissions.manage_public_channel_members.name", + "authentication.permissions.manage_public_channel_members.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{ + "manage_private_channel_members", + "authentication.permissions.manage_private_channel_members.name", + "authentication.permissions.manage_private_channel_members.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{ + "assign_system_admin_role", + "authentication.permissions.assign_system_admin_role.name", + "authentication.permissions.assign_system_admin_role.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_ROLES = &Permission{ + "manage_roles", + "authentication.permissions.manage_roles.name", + "authentication.permissions.manage_roles.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_TEAM_ROLES = &Permission{ + "manage_team_roles", + "authentication.permissions.manage_team_roles.name", + "authentication.permissions.manage_team_roles.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_CHANNEL_ROLES = &Permission{ + "manage_channel_roles", + "authentication.permissions.manage_channel_roles.name", + "authentication.permissions.manage_channel_roles.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_SYSTEM = &Permission{ + "manage_system", + "authentication.permissions.manage_system.name", + "authentication.permissions.manage_system.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{ + "create_direct_channel", + "authentication.permissions.create_direct_channel.name", + "authentication.permissions.create_direct_channel.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_GROUP_CHANNEL = &Permission{ + "create_group_channel", + "authentication.permissions.create_group_channel.name", + "authentication.permissions.create_group_channel.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{ + "manage_public_channel_properties", + "authentication.permissions.manage_public_channel_properties.name", + "authentication.permissions.manage_public_channel_properties.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{ + "manage_private_channel_properties", + "authentication.permissions.manage_private_channel_properties.name", + "authentication.permissions.manage_private_channel_properties.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_LIST_TEAM_CHANNELS = &Permission{ + "list_team_channels", + "authentication.permissions.list_team_channels.name", + "authentication.permissions.list_team_channels.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{ + "join_public_channels", + "authentication.permissions.join_public_channels.name", + "authentication.permissions.join_public_channels.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{ + "delete_public_channel", + "authentication.permissions.delete_public_channel.name", + "authentication.permissions.delete_public_channel.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{ + "delete_private_channel", + "authentication.permissions.delete_private_channel.name", + "authentication.permissions.delete_private_channel.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_EDIT_OTHER_USERS = &Permission{ + "edit_other_users", + "authentication.permissions.edit_other_users.name", + "authentication.permissions.edit_other_users.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_READ_CHANNEL = &Permission{ + "read_channel", + "authentication.permissions.read_channel.name", + "authentication.permissions.read_channel.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_READ_PUBLIC_CHANNEL = &Permission{ + "read_public_channel", + "authentication.permissions.read_public_channel.name", + "authentication.permissions.read_public_channel.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_ADD_REACTION = &Permission{ + "add_reaction", + "authentication.permissions.add_reaction.name", + "authentication.permissions.add_reaction.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_REMOVE_REACTION = &Permission{ + "remove_reaction", + "authentication.permissions.remove_reaction.name", + "authentication.permissions.remove_reaction.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_REMOVE_OTHERS_REACTIONS = &Permission{ + "remove_others_reactions", + "authentication.permissions.remove_others_reactions.name", + "authentication.permissions.remove_others_reactions.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_PERMANENT_DELETE_USER = &Permission{ + "permanent_delete_user", + "authentication.permissions.permanent_delete_user.name", + "authentication.permissions.permanent_delete_user.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_UPLOAD_FILE = &Permission{ + "upload_file", + "authentication.permissions.upload_file.name", + "authentication.permissions.upload_file.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_GET_PUBLIC_LINK = &Permission{ + "get_public_link", + "authentication.permissions.get_public_link.name", + "authentication.permissions.get_public_link.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_WEBHOOKS = &Permission{ + "manage_webhooks", + "authentication.permissions.manage_webhooks.name", + "authentication.permissions.manage_webhooks.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{ + "manage_others_webhooks", + "authentication.permissions.manage_others_webhooks.name", + "authentication.permissions.manage_others_webhooks.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OAUTH = &Permission{ + "manage_oauth", + "authentication.permissions.manage_oauth.name", + "authentication.permissions.manage_oauth.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ + "manage_system_wide_oauth", + "authentication.permissions.manage_system_wide_oauth.name", + "authentication.permissions.manage_system_wide_oauth.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_EMOJIS = &Permission{ + "manage_emojis", + "authentication.permissions.manage_emojis.name", + "authentication.permissions.manage_emojis.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_MANAGE_OTHERS_EMOJIS = &Permission{ + "manage_others_emojis", + "authentication.permissions.manage_others_emojis.name", + "authentication.permissions.manage_others_emojis.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_POST = &Permission{ + "create_post", + "authentication.permissions.create_post.name", + "authentication.permissions.create_post.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_CREATE_POST_PUBLIC = &Permission{ + "create_post_public", + "authentication.permissions.create_post_public.name", + "authentication.permissions.create_post_public.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_CREATE_POST_EPHEMERAL = &Permission{ + "create_post_ephemeral", + "authentication.permissions.create_post_ephemeral.name", + "authentication.permissions.create_post_ephemeral.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_EDIT_POST = &Permission{ + "edit_post", + "authentication.permissions.edit_post.name", + "authentication.permissions.edit_post.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_EDIT_OTHERS_POSTS = &Permission{ + "edit_others_posts", + "authentication.permissions.edit_others_posts.name", + "authentication.permissions.edit_others_posts.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_DELETE_POST = &Permission{ + "delete_post", + "authentication.permissions.delete_post.name", + "authentication.permissions.delete_post.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_DELETE_OTHERS_POSTS = &Permission{ + "delete_others_posts", + "authentication.permissions.delete_others_posts.name", + "authentication.permissions.delete_others_posts.description", + PERMISSION_SCOPE_CHANNEL, + } + PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{ + "remove_user_from_team", + "authentication.permissions.remove_user_from_team.name", + "authentication.permissions.remove_user_from_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_CREATE_TEAM = &Permission{ + "create_team", + "authentication.permissions.create_team.name", + "authentication.permissions.create_team.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_TEAM = &Permission{ + "manage_team", + "authentication.permissions.manage_team.name", + "authentication.permissions.manage_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_IMPORT_TEAM = &Permission{ + "import_team", + "authentication.permissions.import_team.name", + "authentication.permissions.import_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_VIEW_TEAM = &Permission{ + "view_team", + "authentication.permissions.view_team.name", + "authentication.permissions.view_team.description", + PERMISSION_SCOPE_TEAM, + } + PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{ + "list_users_without_team", + "authentication.permissions.list_users_without_team.name", + "authentication.permissions.list_users_without_team.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{ + "create_user_access_token", + "authentication.permissions.create_user_access_token.name", + "authentication.permissions.create_user_access_token.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{ + "read_user_access_token", + "authentication.permissions.read_user_access_token.name", + "authentication.permissions.read_user_access_token.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{ + "revoke_user_access_token", + "authentication.permissions.revoke_user_access_token.name", + "authentication.permissions.revoke_user_access_token.description", + PERMISSION_SCOPE_SYSTEM, + } + PERMISSION_MANAGE_JOBS = &Permission{ + "manage_jobs", + "authentication.permisssions.manage_jobs.name", + "authentication.permisssions.manage_jobs.description", + PERMISSION_SCOPE_SYSTEM, + } + + ALL_PERMISSIONS = []*Permission{ + PERMISSION_INVITE_USER, + PERMISSION_ADD_USER_TO_TEAM, + PERMISSION_USE_SLASH_COMMANDS, + PERMISSION_MANAGE_SLASH_COMMANDS, + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS, + PERMISSION_CREATE_PUBLIC_CHANNEL, + PERMISSION_CREATE_PRIVATE_CHANNEL, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS, + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS, + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE, + PERMISSION_MANAGE_ROLES, + PERMISSION_MANAGE_TEAM_ROLES, + PERMISSION_MANAGE_CHANNEL_ROLES, + PERMISSION_CREATE_DIRECT_CHANNEL, + PERMISSION_CREATE_GROUP_CHANNEL, + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES, + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES, + PERMISSION_LIST_TEAM_CHANNELS, + PERMISSION_JOIN_PUBLIC_CHANNELS, + PERMISSION_DELETE_PUBLIC_CHANNEL, + PERMISSION_DELETE_PRIVATE_CHANNEL, + PERMISSION_EDIT_OTHER_USERS, + PERMISSION_READ_CHANNEL, + PERMISSION_READ_PUBLIC_CHANNEL, + PERMISSION_ADD_REACTION, + PERMISSION_REMOVE_REACTION, + PERMISSION_REMOVE_OTHERS_REACTIONS, + PERMISSION_PERMANENT_DELETE_USER, + PERMISSION_UPLOAD_FILE, + PERMISSION_GET_PUBLIC_LINK, + PERMISSION_MANAGE_WEBHOOKS, + PERMISSION_MANAGE_OTHERS_WEBHOOKS, + PERMISSION_MANAGE_OAUTH, + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH, + PERMISSION_MANAGE_EMOJIS, + PERMISSION_MANAGE_OTHERS_EMOJIS, + PERMISSION_CREATE_POST, + PERMISSION_CREATE_POST_PUBLIC, + PERMISSION_CREATE_POST_EPHEMERAL, + PERMISSION_EDIT_POST, + PERMISSION_EDIT_OTHERS_POSTS, + PERMISSION_DELETE_POST, + PERMISSION_DELETE_OTHERS_POSTS, + PERMISSION_REMOVE_USER_FROM_TEAM, + PERMISSION_CREATE_TEAM, + PERMISSION_MANAGE_TEAM, + PERMISSION_IMPORT_TEAM, + PERMISSION_VIEW_TEAM, + PERMISSION_LIST_USERS_WITHOUT_TEAM, + PERMISSION_MANAGE_JOBS, + PERMISSION_CREATE_USER_ACCESS_TOKEN, + PERMISSION_READ_USER_ACCESS_TOKEN, + PERMISSION_REVOKE_USER_ACCESS_TOKEN, + PERMISSION_MANAGE_SYSTEM, + } +} + +func init() { + initializePermissions() +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/plugin_key_value.go b/vendor/github.com/mattermost/mattermost-server/model/plugin_key_value.go new file mode 100644 index 00000000..b7a7731c --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/plugin_key_value.go @@ -0,0 +1,32 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "net/http" + "unicode/utf8" +) + +const ( + KEY_VALUE_PLUGIN_ID_MAX_RUNES = 190 + KEY_VALUE_KEY_MAX_RUNES = 50 +) + +type PluginKeyValue struct { + PluginId string `json:"plugin_id"` + Key string `json:"key" db:"PKey"` + Value []byte `json:"value" db:"PValue"` +} + +func (kv *PluginKeyValue) IsValid() *AppError { + if len(kv.PluginId) == 0 || utf8.RuneCountInString(kv.PluginId) > KEY_VALUE_PLUGIN_ID_MAX_RUNES { + return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.plugin_id.app_error", map[string]interface{}{"Max": KEY_VALUE_KEY_MAX_RUNES, "Min": 0}, "key="+kv.Key, http.StatusBadRequest) + } + + if len(kv.Key) == 0 || utf8.RuneCountInString(kv.Key) > KEY_VALUE_KEY_MAX_RUNES { + return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.key.app_error", map[string]interface{}{"Max": KEY_VALUE_KEY_MAX_RUNES, "Min": 0}, "key="+kv.Key, http.StatusBadRequest) + } + + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/plugin_status.go b/vendor/github.com/mattermost/mattermost-server/model/plugin_status.go new file mode 100644 index 00000000..db276402 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/plugin_status.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + PluginStateNotRunning = 0 + PluginStateStarting = 1 // unused by server + PluginStateRunning = 2 + PluginStateFailedToStart = 3 + PluginStateFailedToStayRunning = 4 // unused by server + PluginStateStopping = 5 // unused by server +) + +// PluginStatus provides a cluster-aware view of installed plugins. +type PluginStatus struct { + PluginId string `json:"plugin_id"` + ClusterId string `json:"cluster_id"` + PluginPath string `json:"plugin_path"` + State int `json:"state"` + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` +} + +type PluginStatuses []*PluginStatus + +func (m *PluginStatuses) ToJson() string { + b, _ := json.Marshal(m) + return string(b) +} + +func PluginStatusesFromJson(data io.Reader) PluginStatuses { + var m PluginStatuses + json.NewDecoder(data).Decode(&m) + return m +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/plugins_response.go b/vendor/github.com/mattermost/mattermost-server/model/plugins_response.go new file mode 100644 index 00000000..177cfe6c --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/plugins_response.go @@ -0,0 +1,29 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type PluginInfo struct { + Manifest +} + +type PluginsResponse struct { + Active []*PluginInfo `json:"active"` + Inactive []*PluginInfo `json:"inactive"` +} + +func (m *PluginsResponse) ToJson() string { + b, _ := json.Marshal(m) + return string(b) +} + +func PluginsResponseFromJson(data io.Reader) *PluginsResponse { + var m *PluginsResponse + json.NewDecoder(data).Decode(&m) + return m +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/post.go b/vendor/github.com/mattermost/mattermost-server/model/post.go new file mode 100644 index 00000000..5d2438fc --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/post.go @@ -0,0 +1,587 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "sort" + "strings" + "unicode/utf8" + + "github.com/mattermost/mattermost-server/utils/markdown" +) + +const ( + POST_SYSTEM_MESSAGE_PREFIX = "system_" + POST_DEFAULT = "" + POST_SLACK_ATTACHMENT = "slack_attachment" + POST_SYSTEM_GENERIC = "system_generic" + POST_JOIN_LEAVE = "system_join_leave" // Deprecated, use POST_JOIN_CHANNEL or POST_LEAVE_CHANNEL instead + POST_JOIN_CHANNEL = "system_join_channel" + POST_LEAVE_CHANNEL = "system_leave_channel" + POST_JOIN_TEAM = "system_join_team" + POST_LEAVE_TEAM = "system_leave_team" + POST_AUTO_RESPONDER = "system_auto_responder" + POST_ADD_REMOVE = "system_add_remove" // Deprecated, use POST_ADD_TO_CHANNEL or POST_REMOVE_FROM_CHANNEL instead + POST_ADD_TO_CHANNEL = "system_add_to_channel" + POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel" + POST_MOVE_CHANNEL = "system_move_channel" + POST_ADD_TO_TEAM = "system_add_to_team" + POST_REMOVE_FROM_TEAM = "system_remove_from_team" + POST_HEADER_CHANGE = "system_header_change" + POST_DISPLAYNAME_CHANGE = "system_displayname_change" + POST_CONVERT_CHANNEL = "system_convert_channel" + POST_PURPOSE_CHANGE = "system_purpose_change" + POST_CHANNEL_DELETED = "system_channel_deleted" + POST_EPHEMERAL = "system_ephemeral" + POST_CHANGE_CHANNEL_PRIVACY = "system_change_chan_privacy" + POST_FILEIDS_MAX_RUNES = 150 + POST_FILENAMES_MAX_RUNES = 4000 + POST_HASHTAGS_MAX_RUNES = 1000 + POST_MESSAGE_MAX_RUNES_V1 = 4000 + POST_MESSAGE_MAX_BYTES_V2 = 65535 // Maximum size of a TEXT column in MySQL + POST_MESSAGE_MAX_RUNES_V2 = POST_MESSAGE_MAX_BYTES_V2 / 4 // Assume a worst-case representation + POST_PROPS_MAX_RUNES = 8000 + POST_PROPS_MAX_USER_RUNES = POST_PROPS_MAX_RUNES - 400 // Leave some room for system / pre-save modifications + POST_CUSTOM_TYPE_PREFIX = "custom_" + PROPS_ADD_CHANNEL_MEMBER = "add_channel_member" + POST_PROPS_ADDED_USER_ID = "addedUserId" + POST_PROPS_DELETE_BY = "deleteBy" + POST_ACTION_TYPE_BUTTON = "button" + POST_ACTION_TYPE_SELECT = "select" +) + +type Post struct { + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + EditAt int64 `json:"edit_at"` + DeleteAt int64 `json:"delete_at"` + IsPinned bool `json:"is_pinned"` + 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"` + + // MessageSource will contain the message as submitted by the user if Message has been modified + // by Mattermost for presentation (e.g if an image proxy is being used). It should be used to + // populate edit boxes if present. + MessageSource string `json:"message_source,omitempty" db:"-"` + + Type string `json:"type"` + Props StringInterface `json:"props"` + Hashtags string `json:"hashtags"` + Filenames StringArray `json:"filenames,omitempty"` // Deprecated, do not use this field any more + FileIds StringArray `json:"file_ids,omitempty"` + PendingPostId string `json:"pending_post_id" db:"-"` + HasReactions bool `json:"has_reactions,omitempty"` +} + +type PostEphemeral struct { + UserID string `json:"user_id"` + Post *Post `json:"post"` +} + +type PostPatch struct { + IsPinned *bool `json:"is_pinned"` + Message *string `json:"message"` + Props *StringInterface `json:"props"` + FileIds *StringArray `json:"file_ids"` + HasReactions *bool `json:"has_reactions"` +} + +type SearchParameter struct { + Terms *string `json:"terms"` + IsOrSearch *bool `json:"is_or_search"` + TimeZoneOffset *int `json:"time_zone_offset"` + Page *int `json:"page"` + PerPage *int `json:"per_page"` + IncludeDeletedChannels *bool `json:"include_deleted_channels"` +} + +func (o *PostPatch) WithRewrittenImageURLs(f func(string) string) *PostPatch { + copy := *o + if copy.Message != nil { + *copy.Message = RewriteImageURLs(*o.Message, f) + } + return © +} + +type PostForExport struct { + Post + TeamName string + ChannelName string + Username string + ReplyCount int +} + +type ReplyForExport struct { + Post + Username string +} + +type PostForIndexing struct { + Post + TeamId string `json:"team_id"` + ParentCreateAt *int64 `json:"parent_create_at"` +} + +type DoPostActionRequest struct { + SelectedOption string `json:"selected_option"` +} + +type PostAction struct { + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + DataSource string `json:"data_source"` + Options []*PostActionOptions `json:"options"` + Integration *PostActionIntegration `json:"integration,omitempty"` +} + +type PostActionOptions struct { + Text string `json:"text"` + Value string `json:"value"` +} + +type PostActionIntegration struct { + URL string `json:"url,omitempty"` + Context StringInterface `json:"context,omitempty"` +} + +type PostActionIntegrationRequest struct { + UserId string `json:"user_id"` + ChannelId string `json:"channel_id"` + TeamId string `json:"team_id"` + PostId string `json:"post_id"` + Type string `json:"type"` + DataSource string `json:"data_source"` + Context StringInterface `json:"context,omitempty"` +} + +type PostActionIntegrationResponse struct { + Update *Post `json:"update"` + EphemeralText string `json:"ephemeral_text"` +} + +func (o *Post) ToJson() string { + copy := *o + copy.StripActionIntegrations() + b, _ := json.Marshal(©) + return string(b) +} + +func (o *Post) ToUnsanitizedJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func PostFromJson(data io.Reader) *Post { + var o *Post + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *Post) Etag() string { + return Etag(o.Id, o.UpdateAt) +} + +func (o *Post) IsValid(maxPostSize int) *AppError { + + if len(o.Id) != 26 { + return NewAppError("Post.IsValid", "model.post.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("Post.IsValid", "model.post.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("Post.IsValid", "model.post.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.UserId) != 26 { + return NewAppError("Post.IsValid", "model.post.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ChannelId) != 26 { + return NewAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) + } + + if !(len(o.RootId) == 26 || len(o.RootId) == 0) { + return NewAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "", http.StatusBadRequest) + } + + if !(len(o.ParentId) == 26 || len(o.ParentId) == 0) { + return NewAppError("Post.IsValid", "model.post.is_valid.parent_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.ParentId) == 26 && len(o.RootId) == 0 { + return NewAppError("Post.IsValid", "model.post.is_valid.root_parent.app_error", nil, "", http.StatusBadRequest) + } + + if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) { + return NewAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "", http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.Message) > maxPostSize { + return NewAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.Hashtags) > POST_HASHTAGS_MAX_RUNES { + return NewAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + switch o.Type { + case + POST_DEFAULT, + POST_JOIN_LEAVE, + POST_AUTO_RESPONDER, + POST_ADD_REMOVE, + POST_JOIN_CHANNEL, + POST_LEAVE_CHANNEL, + POST_JOIN_TEAM, + POST_LEAVE_TEAM, + POST_ADD_TO_CHANNEL, + POST_REMOVE_FROM_CHANNEL, + POST_MOVE_CHANNEL, + POST_ADD_TO_TEAM, + POST_REMOVE_FROM_TEAM, + POST_SLACK_ATTACHMENT, + POST_HEADER_CHANGE, + POST_PURPOSE_CHANGE, + POST_DISPLAYNAME_CHANGE, + POST_CONVERT_CHANNEL, + POST_CHANNEL_DELETED, + POST_CHANGE_CHANNEL_PRIVACY: + default: + if !strings.HasPrefix(o.Type, POST_CUSTOM_TYPE_PREFIX) { + return NewAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type, http.StatusBadRequest) + } + } + + if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > POST_FILENAMES_MAX_RUNES { + return NewAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(ArrayToJson(o.FileIds)) > POST_FILEIDS_MAX_RUNES { + return NewAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > POST_PROPS_MAX_RUNES { + return NewAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + return nil +} + +func (o *Post) SanitizeProps() { + membersToSanitize := []string{ + PROPS_ADD_CHANNEL_MEMBER, + } + + for _, member := range membersToSanitize { + if _, ok := o.Props[member]; ok { + delete(o.Props, member) + } + } +} + +func (o *Post) PreSave() { + if o.Id == "" { + o.Id = NewId() + } + + o.OriginalId = "" + + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + } + + o.UpdateAt = o.CreateAt + o.PreCommit() +} + +func (o *Post) PreCommit() { + if o.Props == nil { + o.Props = make(map[string]interface{}) + } + + if o.Filenames == nil { + o.Filenames = []string{} + } + + if o.FileIds == nil { + o.FileIds = []string{} + } + + o.GenerateActionIds() +} + +func (o *Post) MakeNonNil() { + if o.Props == nil { + o.Props = make(map[string]interface{}) + } +} + +func (o *Post) AddProp(key string, value interface{}) { + + o.MakeNonNil() + + o.Props[key] = value +} + +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 +} + +func (p *Post) Patch(patch *PostPatch) { + if patch.IsPinned != nil { + p.IsPinned = *patch.IsPinned + } + + if patch.Message != nil { + p.Message = *patch.Message + } + + if patch.Props != nil { + p.Props = *patch.Props + } + + if patch.FileIds != nil { + p.FileIds = *patch.FileIds + } + + if patch.HasReactions != nil { + p.HasReactions = *patch.HasReactions + } +} + +func (o *PostPatch) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } + + return string(b) +} + +func PostPatchFromJson(data io.Reader) *PostPatch { + decoder := json.NewDecoder(data) + var post PostPatch + err := decoder.Decode(&post) + if err != nil { + return nil + } + + return &post +} + +func (o *SearchParameter) SearchParameterToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } + + return string(b) +} + +func SearchParameterFromJson(data io.Reader) *SearchParameter { + decoder := json.NewDecoder(data) + var searchParam SearchParameter + err := decoder.Decode(&searchParam) + if err != nil { + return nil + } + + return &searchParam +} + +func (o *Post) ChannelMentions() []string { + return ChannelMentions(o.Message) +} + +func (r *PostActionIntegrationRequest) ToJson() string { + b, _ := json.Marshal(r) + return string(b) +} + +func PostActionIntegrationRequesteFromJson(data io.Reader) *PostActionIntegrationRequest { + var o *PostActionIntegrationRequest + err := json.NewDecoder(data).Decode(&o) + if err != nil { + return nil + } + return o +} + +func (r *PostActionIntegrationResponse) ToJson() string { + b, _ := json.Marshal(r) + return string(b) +} + +func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse { + var o *PostActionIntegrationResponse + err := json.NewDecoder(data).Decode(&o) + if err != nil { + return nil + } + return o +} + +func (o *Post) Attachments() []*SlackAttachment { + if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok { + return attachments + } + var ret []*SlackAttachment + if attachments, ok := o.Props["attachments"].([]interface{}); ok { + for _, attachment := range attachments { + if enc, err := json.Marshal(attachment); err == nil { + var decoded SlackAttachment + if json.Unmarshal(enc, &decoded) == nil { + ret = append(ret, &decoded) + } + } + } + } + return ret +} + +func (o *Post) StripActionIntegrations() { + attachments := o.Attachments() + if o.Props["attachments"] != nil { + o.Props["attachments"] = attachments + } + for _, attachment := range attachments { + for _, action := range attachment.Actions { + action.Integration = nil + } + } +} + +func (o *Post) GetAction(id string) *PostAction { + for _, attachment := range o.Attachments() { + for _, action := range attachment.Actions { + if action.Id == id { + return action + } + } + } + return nil +} + +func (o *Post) GenerateActionIds() { + if o.Props["attachments"] != nil { + o.Props["attachments"] = o.Attachments() + } + if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok { + for _, attachment := range attachments { + for _, action := range attachment.Actions { + if action.Id == "" { + action.Id = NewId() + } + } + } + } +} + +var markdownDestinationEscaper = strings.NewReplacer( + `\`, `\\`, + `<`, `\<`, + `>`, `\>`, + `(`, `\(`, + `)`, `\)`, +) + +// WithRewrittenImageURLs returns a new shallow copy of the post where the message has been +// rewritten via RewriteImageURLs. +func (o *Post) WithRewrittenImageURLs(f func(string) string) *Post { + copy := *o + copy.Message = RewriteImageURLs(o.Message, f) + if copy.MessageSource == "" && copy.Message != o.Message { + copy.MessageSource = o.Message + } + return © +} + +func (o *PostEphemeral) ToUnsanitizedJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func DoPostActionRequestFromJson(data io.Reader) *DoPostActionRequest { + var o *DoPostActionRequest + json.NewDecoder(data).Decode(&o) + return o +} + +// RewriteImageURLs takes a message and returns a copy that has all of the image URLs replaced +// according to the function f. For each image URL, f will be invoked, and the resulting markdown +// will contain the URL returned by that invocation instead. +// +// Image URLs are destination URLs used in inline images or reference definitions that are used +// anywhere in the input markdown as an image. +func RewriteImageURLs(message string, f func(string) string) string { + if !strings.Contains(message, "![") { + return message + } + + var ranges []markdown.Range + + markdown.Inspect(message, func(blockOrInline interface{}) bool { + switch v := blockOrInline.(type) { + case *markdown.ReferenceImage: + ranges = append(ranges, v.ReferenceDefinition.RawDestination) + case *markdown.InlineImage: + ranges = append(ranges, v.RawDestination) + default: + return true + } + return true + }) + + if ranges == nil { + return message + } + + sort.Slice(ranges, func(i, j int) bool { + return ranges[i].Position < ranges[j].Position + }) + + copyRanges := make([]markdown.Range, 0, len(ranges)) + urls := make([]string, 0, len(ranges)) + resultLength := len(message) + + start := 0 + for i, r := range ranges { + switch { + case i == 0: + case r.Position != ranges[i-1].Position: + start = ranges[i-1].End + default: + continue + } + original := message[r.Position:r.End] + replacement := markdownDestinationEscaper.Replace(f(markdown.Unescape(original))) + resultLength += len(replacement) - len(original) + copyRanges = append(copyRanges, markdown.Range{Position: start, End: r.Position}) + urls = append(urls, replacement) + } + + result := make([]byte, resultLength) + + offset := 0 + for i, r := range copyRanges { + offset += copy(result[offset:], message[r.Position:r.End]) + offset += copy(result[offset:], urls[i]) + } + copy(result[offset:], message[ranges[len(ranges)-1].End:]) + + return string(result) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/post_list.go b/vendor/github.com/mattermost/mattermost-server/model/post_list.go new file mode 100644 index 00000000..27c22e7b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/post_list.go @@ -0,0 +1,138 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "sort" +) + +type PostList struct { + Order []string `json:"order"` + Posts map[string]*Post `json:"posts"` +} + +func NewPostList() *PostList { + return &PostList{ + Order: make([]string, 0), + Posts: make(map[string]*Post), + } +} + +func (o *PostList) WithRewrittenImageURLs(f func(string) string) *PostList { + copy := *o + copy.Posts = make(map[string]*Post) + for id, post := range o.Posts { + copy.Posts[id] = post.WithRewrittenImageURLs(f) + } + return © +} + +func (o *PostList) StripActionIntegrations() { + posts := o.Posts + o.Posts = make(map[string]*Post) + for id, post := range posts { + pcopy := *post + pcopy.StripActionIntegrations() + o.Posts[id] = &pcopy + } +} + +func (o *PostList) ToJson() string { + copy := *o + copy.StripActionIntegrations() + b, err := json.Marshal(©) + 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) SortByCreateAt() { + sort.Slice(o.Order, func(i, j int) bool { + return o.Posts[o.Order[i]].CreateAt > o.Posts[o.Order[j]].CreateAt + }) +} + +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 + } else if v.UpdateAt == t && v.Id > id { + t = v.UpdateAt + id = v.Id + } + } + + orderId := "" + if len(o.Order) > 0 { + orderId = o.Order[0] + } + + return Etag(orderId, 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 { + var o *PostList + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/post_search_results.go b/vendor/github.com/mattermost/mattermost-server/model/post_search_results.go new file mode 100644 index 00000000..2317f183 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/post_search_results.go @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type PostSearchMatches map[string][]string + +type PostSearchResults struct { + *PostList + Matches PostSearchMatches `json:"matches"` +} + +func MakePostSearchResults(posts *PostList, matches PostSearchMatches) *PostSearchResults { + return &PostSearchResults{ + posts, + matches, + } +} + +func (o *PostSearchResults) ToJson() string { + copy := *o + copy.PostList.StripActionIntegrations() + b, err := json.Marshal(©) + if err != nil { + return "" + } else { + return string(b) + } +} + +func PostSearchResultsFromJson(data io.Reader) *PostSearchResults { + var o *PostSearchResults + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/preference.go b/vendor/github.com/mattermost/mattermost-server/model/preference.go new file mode 100644 index 00000000..6f13c38e --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/preference.go @@ -0,0 +1,118 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "regexp" + "strings" + "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_FLAGGED_POST = "flagged_post" + PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel" + PREFERENCE_CATEGORY_SIDEBAR_SETTINGS = "sidebar_settings" + + PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" + PREFERENCE_NAME_CHANNEL_DISPLAY_MODE = "channel_display_mode" + PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews" + PREFERENCE_NAME_MESSAGE_DISPLAY = "message_display" + PREFERENCE_NAME_NAME_FORMAT = "name_format" + PREFERENCE_NAME_USE_MILITARY_TIME = "use_military_time" + + PREFERENCE_CATEGORY_THEME = "theme" + // the name for theme props is the team id + + PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app" + // the name for oauth_app is the client_id and value is the current scope + + PREFERENCE_CATEGORY_LAST = "last" + PREFERENCE_NAME_LAST_CHANNEL = "channel" + PREFERENCE_NAME_LAST_TEAM = "team" + + PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" + PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval" + + PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS = "30" // the "immediate" setting is actually 30s + PREFERENCE_EMAIL_INTERVAL_BATCHING_SECONDS = "900" // fifteen minutes is 900 seconds +) + +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, _ := json.Marshal(o) + return string(b) +} + +func PreferenceFromJson(data io.Reader) *Preference { + var o *Preference + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *Preference) IsValid() *AppError { + if len(o.UserId) != 26 { + return NewAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest) + } + + if len(o.Category) == 0 || len(o.Category) > 32 { + return NewAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category, http.StatusBadRequest) + } + + if len(o.Name) > 32 { + return NewAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name, http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.Value) > 2000 { + return NewAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value, http.StatusBadRequest) + } + + if o.Category == PREFERENCE_CATEGORY_THEME { + var unused map[string]string + if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil { + return NewAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value, http.StatusBadRequest) + } + } + + return nil +} + +func (o *Preference) PreUpdate() { + if o.Category == PREFERENCE_CATEGORY_THEME { + // decode the value of theme (a map of strings to string) and eliminate any invalid values + var props map[string]string + if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil { + // just continue, the invalid preference value should get caught by IsValid before saving + return + } + + colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) + + // blank out any invalid theme values + for name, value := range props { + if name == "image" || name == "type" || name == "codeTheme" { + continue + } + + if !colorPattern.MatchString(value) { + props[name] = "#ffffff" + } + } + + if b, err := json.Marshal(props); err == nil { + o.Value = string(b) + } + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/preferences.go b/vendor/github.com/mattermost/mattermost-server/model/preferences.go new file mode 100644 index 00000000..172e1aa8 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/preferences.go @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(o) + 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/mattermost-server/model/push_notification.go b/vendor/github.com/mattermost/mattermost-server/model/push_notification.go new file mode 100644 index 00000000..5268a98f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/push_notification.go @@ -0,0 +1,70 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "strings" +) + +const ( + PUSH_NOTIFY_APPLE = "apple" + PUSH_NOTIFY_ANDROID = "android" + PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn" + PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn" + + PUSH_TYPE_MESSAGE = "message" + PUSH_TYPE_CLEAR = "clear" + PUSH_MESSAGE_V2 = "v2" + + // The category is set to handle a set of interactive Actions + // with the push notifications + CATEGORY_CAN_REPLY = "CAN_REPLY" + + MHPNS = "https://push.mattermost.com" +) + +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"` + TeamId string `json:"team_id"` + ChannelId string `json:"channel_id"` + PostId string `json:"post_id"` + RootId string `json:"root_id"` + ChannelName string `json:"channel_name"` + Type string `json:"type"` + SenderId string `json:"sender_id"` + OverrideUsername string `json:"override_username"` + OverrideIconUrl string `json:"override_icon_url"` + FromWebhook string `json:"from_webhook"` + Version string `json:"version"` +} + +func (me *PushNotification) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) { + + index := strings.Index(deviceId, ":") + + if index > -1 { + me.Platform = deviceId[:index] + me.DeviceId = deviceId[index+1:] + } +} + +func PushNotificationFromJson(data io.Reader) *PushNotification { + var me *PushNotification + json.NewDecoder(data).Decode(&me) + return me +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/push_response.go b/vendor/github.com/mattermost/mattermost-server/model/push_response.go new file mode 100644 index 00000000..1434a2b1 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/push_response.go @@ -0,0 +1,54 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + PUSH_STATUS = "status" + PUSH_STATUS_OK = "OK" + PUSH_STATUS_FAIL = "FAIL" + PUSH_STATUS_REMOVE = "REMOVE" + PUSH_STATUS_ERROR_MSG = "error" +) + +type PushResponse map[string]string + +func NewOkPushResponse() PushResponse { + m := make(map[string]string) + m[PUSH_STATUS] = PUSH_STATUS_OK + return m +} + +func NewRemovePushResponse() PushResponse { + m := make(map[string]string) + m[PUSH_STATUS] = PUSH_STATUS_REMOVE + return m +} + +func NewErrorPushResponse(message string) PushResponse { + m := make(map[string]string) + m[PUSH_STATUS] = PUSH_STATUS_FAIL + m[PUSH_STATUS_ERROR_MSG] = message + return m +} + +func (me *PushResponse) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func PushResponseFromJson(data io.Reader) PushResponse { + decoder := json.NewDecoder(data) + + var objmap PushResponse + if err := decoder.Decode(&objmap); err != nil { + return make(map[string]string) + } else { + return objmap + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/reaction.go b/vendor/github.com/mattermost/mattermost-server/model/reaction.go new file mode 100644 index 00000000..c1b9c499 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/reaction.go @@ -0,0 +1,76 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "regexp" +) + +type Reaction struct { + UserId string `json:"user_id"` + PostId string `json:"post_id"` + EmojiName string `json:"emoji_name"` + CreateAt int64 `json:"create_at"` +} + +func (o *Reaction) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func ReactionFromJson(data io.Reader) *Reaction { + var o Reaction + + if err := json.NewDecoder(data).Decode(&o); err != nil { + return nil + } else { + return &o + } +} + +func ReactionsToJson(o []*Reaction) string { + b, _ := json.Marshal(o) + return string(b) +} + +func ReactionsFromJson(data io.Reader) []*Reaction { + var o []*Reaction + + if err := json.NewDecoder(data).Decode(&o); err != nil { + return nil + } else { + return o + } +} + +func (o *Reaction) IsValid() *AppError { + if len(o.UserId) != 26 { + return NewAppError("Reaction.IsValid", "model.reaction.is_valid.user_id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest) + } + + if len(o.PostId) != 26 { + return NewAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId, http.StatusBadRequest) + } + + validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`) + + if len(o.EmojiName) == 0 || len(o.EmojiName) > EMOJI_NAME_MAX_LENGTH || !validName.MatchString(o.EmojiName) { + return NewAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName, http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("Reaction.IsValid", "model.reaction.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (o *Reaction) PreSave() { + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/role.go b/vendor/github.com/mattermost/mattermost-server/model/role.go new file mode 100644 index 00000000..27b32ed6 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/role.go @@ -0,0 +1,363 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "strings" +) + +const ( + SYSTEM_USER_ROLE_ID = "system_user" + SYSTEM_ADMIN_ROLE_ID = "system_admin" + SYSTEM_POST_ALL_ROLE_ID = "system_post_all" + SYSTEM_POST_ALL_PUBLIC_ROLE_ID = "system_post_all_public" + SYSTEM_USER_ACCESS_TOKEN_ROLE_ID = "system_user_access_token" + + TEAM_USER_ROLE_ID = "team_user" + TEAM_ADMIN_ROLE_ID = "team_admin" + TEAM_POST_ALL_ROLE_ID = "team_post_all" + TEAM_POST_ALL_PUBLIC_ROLE_ID = "team_post_all_public" + + CHANNEL_USER_ROLE_ID = "channel_user" + CHANNEL_ADMIN_ROLE_ID = "channel_admin" + + ROLE_NAME_MAX_LENGTH = 64 + ROLE_DISPLAY_NAME_MAX_LENGTH = 128 + ROLE_DESCRIPTION_MAX_LENGTH = 1024 +) + +type Role struct { + Id string `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + Permissions []string `json:"permissions"` + SchemeManaged bool `json:"scheme_managed"` + BuiltIn bool `json:"built_in"` +} + +type RolePatch struct { + Permissions *[]string `json:"permissions"` +} + +func (role *Role) ToJson() string { + b, _ := json.Marshal(role) + return string(b) +} + +func RoleFromJson(data io.Reader) *Role { + var role *Role + json.NewDecoder(data).Decode(&role) + return role +} + +func RoleListToJson(r []*Role) string { + b, _ := json.Marshal(r) + return string(b) +} + +func RoleListFromJson(data io.Reader) []*Role { + var roles []*Role + json.NewDecoder(data).Decode(&roles) + return roles +} + +func (r *RolePatch) ToJson() string { + b, _ := json.Marshal(r) + return string(b) +} + +func RolePatchFromJson(data io.Reader) *RolePatch { + var rolePatch *RolePatch + json.NewDecoder(data).Decode(&rolePatch) + return rolePatch +} + +func (o *Role) Patch(patch *RolePatch) { + if patch.Permissions != nil { + o.Permissions = *patch.Permissions + } +} + +// Returns an array of permissions that are in either role.Permissions +// or patch.Permissions, but not both. +func PermissionsChangedByPatch(role *Role, patch *RolePatch) []string { + var result []string + + if patch.Permissions == nil { + return result + } + + roleMap := make(map[string]bool) + patchMap := make(map[string]bool) + + for _, permission := range role.Permissions { + roleMap[permission] = true + } + + for _, permission := range *patch.Permissions { + patchMap[permission] = true + } + + for _, permission := range role.Permissions { + if !patchMap[permission] { + result = append(result, permission) + } + } + + for _, permission := range *patch.Permissions { + if !roleMap[permission] { + result = append(result, permission) + } + } + + return result +} + +func (role *Role) IsValid() bool { + if len(role.Id) != 26 { + return false + } + + return role.IsValidWithoutId() +} + +func (role *Role) IsValidWithoutId() bool { + if !IsValidRoleName(role.Name) { + return false + } + + if len(role.DisplayName) == 0 || len(role.DisplayName) > ROLE_DISPLAY_NAME_MAX_LENGTH { + return false + } + + if len(role.Description) > ROLE_DESCRIPTION_MAX_LENGTH { + return false + } + + for _, permission := range role.Permissions { + permissionValidated := false + for _, p := range ALL_PERMISSIONS { + if permission == p.Id { + permissionValidated = true + break + } + } + + if !permissionValidated { + return false + } + } + + return true +} + +func IsValidRoleName(roleName string) bool { + if len(roleName) <= 0 || len(roleName) > ROLE_NAME_MAX_LENGTH { + return false + } + + if strings.TrimLeft(roleName, "abcdefghijklmnopqrstuvwxyz0123456789_") != "" { + return false + } + + return true +} + +func MakeDefaultRoles() map[string]*Role { + roles := make(map[string]*Role) + + roles[CHANNEL_USER_ROLE_ID] = &Role{ + Name: "channel_user", + DisplayName: "authentication.roles.channel_user.name", + Description: "authentication.roles.channel_user.description", + Permissions: []string{ + PERMISSION_READ_CHANNEL.Id, + PERMISSION_ADD_REACTION.Id, + PERMISSION_REMOVE_REACTION.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + PERMISSION_UPLOAD_FILE.Id, + PERMISSION_GET_PUBLIC_LINK.Id, + PERMISSION_CREATE_POST.Id, + PERMISSION_USE_SLASH_COMMANDS.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[CHANNEL_ADMIN_ROLE_ID] = &Role{ + Name: "channel_admin", + DisplayName: "authentication.roles.channel_admin.name", + Description: "authentication.roles.channel_admin.description", + Permissions: []string{ + PERMISSION_MANAGE_CHANNEL_ROLES.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[TEAM_USER_ROLE_ID] = &Role{ + Name: "team_user", + DisplayName: "authentication.roles.team_user.name", + Description: "authentication.roles.team_user.description", + Permissions: []string{ + PERMISSION_LIST_TEAM_CHANNELS.Id, + PERMISSION_JOIN_PUBLIC_CHANNELS.Id, + PERMISSION_READ_PUBLIC_CHANNEL.Id, + PERMISSION_VIEW_TEAM.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[TEAM_POST_ALL_ROLE_ID] = &Role{ + Name: "team_post_all", + DisplayName: "authentication.roles.team_post_all.name", + Description: "authentication.roles.team_post_all.description", + Permissions: []string{ + PERMISSION_CREATE_POST.Id, + }, + SchemeManaged: false, + BuiltIn: true, + } + + roles[TEAM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ + Name: "team_post_all_public", + DisplayName: "authentication.roles.team_post_all_public.name", + Description: "authentication.roles.team_post_all_public.description", + Permissions: []string{ + PERMISSION_CREATE_POST_PUBLIC.Id, + }, + SchemeManaged: false, + BuiltIn: true, + } + + roles[TEAM_ADMIN_ROLE_ID] = &Role{ + Name: "team_admin", + DisplayName: "authentication.roles.team_admin.name", + Description: "authentication.roles.team_admin.description", + Permissions: []string{ + PERMISSION_REMOVE_USER_FROM_TEAM.Id, + PERMISSION_MANAGE_TEAM.Id, + PERMISSION_IMPORT_TEAM.Id, + PERMISSION_MANAGE_TEAM_ROLES.Id, + PERMISSION_MANAGE_CHANNEL_ROLES.Id, + PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + PERMISSION_MANAGE_SLASH_COMMANDS.Id, + PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, + PERMISSION_MANAGE_WEBHOOKS.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[SYSTEM_USER_ROLE_ID] = &Role{ + Name: "system_user", + DisplayName: "authentication.roles.global_user.name", + Description: "authentication.roles.global_user.description", + Permissions: []string{ + PERMISSION_CREATE_DIRECT_CHANNEL.Id, + PERMISSION_CREATE_GROUP_CHANNEL.Id, + PERMISSION_PERMANENT_DELETE_USER.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[SYSTEM_POST_ALL_ROLE_ID] = &Role{ + Name: "system_post_all", + DisplayName: "authentication.roles.system_post_all.name", + Description: "authentication.roles.system_post_all.description", + Permissions: []string{ + PERMISSION_CREATE_POST.Id, + }, + SchemeManaged: false, + BuiltIn: true, + } + + roles[SYSTEM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ + Name: "system_post_all_public", + DisplayName: "authentication.roles.system_post_all_public.name", + Description: "authentication.roles.system_post_all_public.description", + Permissions: []string{ + PERMISSION_CREATE_POST_PUBLIC.Id, + }, + SchemeManaged: false, + BuiltIn: true, + } + + roles[SYSTEM_USER_ACCESS_TOKEN_ROLE_ID] = &Role{ + Name: "system_user_access_token", + DisplayName: "authentication.roles.system_user_access_token.name", + Description: "authentication.roles.system_user_access_token.description", + Permissions: []string{ + PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + PERMISSION_READ_USER_ACCESS_TOKEN.Id, + PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + }, + SchemeManaged: false, + BuiltIn: true, + } + + roles[SYSTEM_ADMIN_ROLE_ID] = &Role{ + Name: "system_admin", + DisplayName: "authentication.roles.global_admin.name", + Description: "authentication.roles.global_admin.description", + // System admins can do anything channel and team admins can do + // plus everything members of teams and channels can do to all teams + // and channels on the system + Permissions: append( + append( + append( + append( + []string{ + PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, + PERMISSION_MANAGE_SYSTEM.Id, + PERMISSION_MANAGE_ROLES.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, + PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + PERMISSION_DELETE_PUBLIC_CHANNEL.Id, + PERMISSION_CREATE_PUBLIC_CHANNEL.Id, + PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, + PERMISSION_DELETE_PRIVATE_CHANNEL.Id, + PERMISSION_CREATE_PRIVATE_CHANNEL.Id, + PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, + PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, + PERMISSION_EDIT_OTHER_USERS.Id, + PERMISSION_EDIT_OTHERS_POSTS.Id, + PERMISSION_MANAGE_OAUTH.Id, + PERMISSION_INVITE_USER.Id, + PERMISSION_DELETE_POST.Id, + PERMISSION_DELETE_OTHERS_POSTS.Id, + PERMISSION_CREATE_TEAM.Id, + PERMISSION_ADD_USER_TO_TEAM.Id, + PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, + PERMISSION_MANAGE_JOBS.Id, + PERMISSION_CREATE_POST_PUBLIC.Id, + PERMISSION_CREATE_POST_EPHEMERAL.Id, + PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, + PERMISSION_READ_USER_ACCESS_TOKEN.Id, + PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, + PERMISSION_REMOVE_OTHERS_REACTIONS.Id, + }, + roles[TEAM_USER_ROLE_ID].Permissions..., + ), + roles[CHANNEL_USER_ROLE_ID].Permissions..., + ), + roles[TEAM_ADMIN_ROLE_ID].Permissions..., + ), + roles[CHANNEL_ADMIN_ROLE_ID].Permissions..., + ), + SchemeManaged: true, + BuiltIn: true, + } + + return roles +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/saml.go b/vendor/github.com/mattermost/mattermost-server/model/saml.go new file mode 100644 index 00000000..528ac45c --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/saml.go @@ -0,0 +1,37 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + USER_AUTH_SERVICE_SAML = "saml" + USER_AUTH_SERVICE_SAML_TEXT = "With SAML" +) + +type SamlAuthRequest struct { + Base64AuthRequest string + URL string + RelayState string +} + +type SamlCertificateStatus struct { + IdpCertificateFile bool `json:"idp_certificate_file"` + PrivateKeyFile bool `json:"private_key_file"` + PublicCertificateFile bool `json:"public_certificate_file"` +} + +func (s *SamlCertificateStatus) ToJson() string { + b, _ := json.Marshal(s) + return string(b) +} + +func SamlCertificateStatusFromJson(data io.Reader) *SamlCertificateStatus { + var status *SamlCertificateStatus + json.NewDecoder(data).Decode(&status) + return status +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/scheduled_task.go b/vendor/github.com/mattermost/mattermost-server/model/scheduled_task.go new file mode 100644 index 00000000..f3529ded --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/scheduled_task.go @@ -0,0 +1,77 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "fmt" + "time" +) + +type TaskFunc func() + +type ScheduledTask struct { + Name string `json:"name"` + Interval time.Duration `json:"interval"` + Recurring bool `json:"recurring"` + function func() + cancel chan struct{} + cancelled chan struct{} +} + +func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask { + return createTask(name, function, timeToExecution, false) +} + +func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask { + return createTask(name, function, interval, true) +} + +func createTask(name string, function TaskFunc, interval time.Duration, recurring bool) *ScheduledTask { + task := &ScheduledTask{ + Name: name, + Interval: interval, + Recurring: recurring, + function: function, + cancel: make(chan struct{}), + cancelled: make(chan struct{}), + } + + go func() { + defer close(task.cancelled) + + ticker := time.NewTicker(interval) + defer func() { + ticker.Stop() + }() + + for { + select { + case <-ticker.C: + function() + case <-task.cancel: + return + } + + if !task.Recurring { + break + } + } + }() + + return task +} + +func (task *ScheduledTask) Cancel() { + close(task.cancel) + <-task.cancelled +} + +func (task *ScheduledTask) String() string { + return fmt.Sprintf( + "%s\nInterval: %s\nRecurring: %t\n", + task.Name, + task.Interval.String(), + task.Recurring, + ) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/scheme.go b/vendor/github.com/mattermost/mattermost-server/model/scheme.go new file mode 100644 index 00000000..0c38b560 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/scheme.go @@ -0,0 +1,208 @@ +// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "regexp" +) + +const ( + SCHEME_DISPLAY_NAME_MAX_LENGTH = 128 + SCHEME_NAME_MAX_LENGTH = 64 + SCHEME_DESCRIPTION_MAX_LENGTH = 1024 + SCHEME_SCOPE_TEAM = "team" + SCHEME_SCOPE_CHANNEL = "channel" +) + +type Scheme struct { + Id string `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + Scope string `json:"scope"` + DefaultTeamAdminRole string `json:"default_team_admin_role"` + DefaultTeamUserRole string `json:"default_team_user_role"` + DefaultChannelAdminRole string `json:"default_channel_admin_role"` + DefaultChannelUserRole string `json:"default_channel_user_role"` +} + +type SchemePatch struct { + Name *string `json:"name"` + DisplayName *string `json:"display_name"` + Description *string `json:"description"` +} + +type SchemeIDPatch struct { + SchemeID *string `json:"scheme_id"` +} + +// SchemeConveyor is used for importing and exporting a Scheme and its associated Roles. +type SchemeConveyor struct { + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + Scope string `json:"scope"` + TeamAdmin string `json:"default_team_admin_role"` + TeamUser string `json:"default_team_user_role"` + ChannelAdmin string `json:"default_channel_admin_role"` + ChannelUser string `json:"default_channel_user_role"` + Roles []*Role `json:"roles"` +} + +func (sc *SchemeConveyor) Scheme() *Scheme { + return &Scheme{ + DisplayName: sc.DisplayName, + Name: sc.Name, + Description: sc.Description, + Scope: sc.Scope, + DefaultTeamAdminRole: sc.TeamAdmin, + DefaultTeamUserRole: sc.TeamUser, + DefaultChannelAdminRole: sc.ChannelAdmin, + DefaultChannelUserRole: sc.ChannelUser, + } +} + +type SchemeRoles struct { + SchemeAdmin bool `json:"scheme_admin"` + SchemeUser bool `json:"scheme_user"` +} + +func (scheme *Scheme) ToJson() string { + b, _ := json.Marshal(scheme) + return string(b) +} + +func SchemeFromJson(data io.Reader) *Scheme { + var scheme *Scheme + json.NewDecoder(data).Decode(&scheme) + return scheme +} + +func SchemesToJson(schemes []*Scheme) string { + b, _ := json.Marshal(schemes) + return string(b) +} + +func SchemesFromJson(data io.Reader) []*Scheme { + var schemes []*Scheme + if err := json.NewDecoder(data).Decode(&schemes); err == nil { + return schemes + } else { + return nil + } +} + +func (scheme *Scheme) IsValid() bool { + if len(scheme.Id) != 26 { + return false + } + + return scheme.IsValidForCreate() +} + +func (scheme *Scheme) IsValidForCreate() bool { + if len(scheme.DisplayName) == 0 || len(scheme.DisplayName) > SCHEME_DISPLAY_NAME_MAX_LENGTH { + return false + } + + if !IsValidSchemeName(scheme.Name) { + return false + } + + if len(scheme.Description) > SCHEME_DESCRIPTION_MAX_LENGTH { + return false + } + + switch scheme.Scope { + case SCHEME_SCOPE_TEAM, SCHEME_SCOPE_CHANNEL: + default: + return false + } + + if !IsValidRoleName(scheme.DefaultChannelAdminRole) { + return false + } + + if !IsValidRoleName(scheme.DefaultChannelUserRole) { + return false + } + + if scheme.Scope == SCHEME_SCOPE_TEAM { + if !IsValidRoleName(scheme.DefaultTeamAdminRole) { + return false + } + + if !IsValidRoleName(scheme.DefaultTeamUserRole) { + return false + } + } + + if scheme.Scope == SCHEME_SCOPE_CHANNEL { + if len(scheme.DefaultTeamAdminRole) != 0 { + return false + } + + if len(scheme.DefaultTeamUserRole) != 0 { + return false + } + } + + return true +} + +func (scheme *Scheme) Patch(patch *SchemePatch) { + if patch.DisplayName != nil { + scheme.DisplayName = *patch.DisplayName + } + if patch.Name != nil { + scheme.Name = *patch.Name + } + if patch.Description != nil { + scheme.Description = *patch.Description + } +} + +func (patch *SchemePatch) ToJson() string { + b, _ := json.Marshal(patch) + return string(b) +} + +func SchemePatchFromJson(data io.Reader) *SchemePatch { + var patch *SchemePatch + json.NewDecoder(data).Decode(&patch) + return patch +} + +func SchemeIDFromJson(data io.Reader) *string { + var p *SchemeIDPatch + json.NewDecoder(data).Decode(&p) + return p.SchemeID +} + +func (p *SchemeIDPatch) ToJson() string { + b, _ := json.Marshal(p) + return string(b) +} + +func IsValidSchemeName(name string) bool { + re := regexp.MustCompile(fmt.Sprintf("^[a-z0-9_]{2,%d}$", SCHEME_NAME_MAX_LENGTH)) + return re.MatchString(name) +} + +func (schemeRoles *SchemeRoles) ToJson() string { + b, _ := json.Marshal(schemeRoles) + return string(b) +} + +func SchemeRolesFromJson(data io.Reader) *SchemeRoles { + var schemeRoles *SchemeRoles + json.NewDecoder(data).Decode(&schemeRoles) + return schemeRoles +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/search_params.go b/vendor/github.com/mattermost/mattermost-server/model/search_params.go new file mode 100644 index 00000000..65358066 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/search_params.go @@ -0,0 +1,228 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "regexp" + "strings" + "time" +) + +var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) +var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) + +type SearchParams struct { + Terms string + IsHashtag bool + InChannels []string + FromUsers []string + AfterDate string + BeforeDate string + OnDate string + OrTerms bool + IncludeDeletedChannels bool + TimeZoneOffset int +} + +// Returns the epoch timestamp of the start of the day specified by SearchParams.AfterDate +func (p *SearchParams) GetAfterDateMillis() int64 { + date, err := time.Parse("2006-01-02", PadDateStringZeros(p.AfterDate)) + if err != nil { + date = time.Now() + } + + // travel forward 1 day + oneDay := time.Hour * 24 + afterDate := date.Add(oneDay) + return GetStartOfDayMillis(afterDate, p.TimeZoneOffset) +} + +// Returns the epoch timestamp of the end of the day specified by SearchParams.BeforeDate +func (p *SearchParams) GetBeforeDateMillis() int64 { + date, err := time.Parse("2006-01-02", PadDateStringZeros(p.BeforeDate)) + if err != nil { + return 0 + } + + // travel back 1 day + oneDay := time.Hour * -24 + beforeDate := date.Add(oneDay) + return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset) +} + +// Returns the epoch timestamps of the start and end of the day specified by SearchParams.OnDate +func (p *SearchParams) GetOnDateMillis() (int64, int64) { + date, err := time.Parse("2006-01-02", PadDateStringZeros(p.OnDate)) + if err != nil { + return 0, 0 + } + + return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset) +} + +var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on"} + +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, strings.Fields(text[location:i])...) + foundQuote = true + location = i + } + } + } + + words = append(words, strings.Fields(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 = searchTermPuncStart.ReplaceAllString(word, "") + word = searchTermPuncEnd.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, timeZoneOffset int) []*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{} + afterDate := "" + beforeDate := "" + onDate := "" + + 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) + } else if flag == "after" { + afterDate = value + } else if flag == "before" { + beforeDate = value + } else if flag == "on" { + onDate = value + } + } + + paramsList := []*SearchParams{} + + if len(plainTerms) > 0 { + paramsList = append(paramsList, &SearchParams{ + Terms: plainTerms, + IsHashtag: false, + InChannels: inChannels, + FromUsers: fromUsers, + AfterDate: afterDate, + BeforeDate: beforeDate, + OnDate: onDate, + TimeZoneOffset: timeZoneOffset, + }) + } + + if len(hashtagTerms) > 0 { + paramsList = append(paramsList, &SearchParams{ + Terms: hashtagTerms, + IsHashtag: true, + InChannels: inChannels, + FromUsers: fromUsers, + AfterDate: afterDate, + BeforeDate: beforeDate, + OnDate: onDate, + TimeZoneOffset: timeZoneOffset, + }) + } + + // 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 || len(afterDate) != 0 || len(beforeDate) != 0 || len(onDate) != 0) { + paramsList = append(paramsList, &SearchParams{ + Terms: "", + IsHashtag: false, + InChannels: inChannels, + FromUsers: fromUsers, + AfterDate: afterDate, + BeforeDate: beforeDate, + OnDate: onDate, + TimeZoneOffset: timeZoneOffset, + }) + } + + return paramsList +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/security_bulletin.go b/vendor/github.com/mattermost/mattermost-server/model/security_bulletin.go new file mode 100644 index 00000000..958b9c9e --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/security_bulletin.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(me) + return string(b) +} + +func SecurityBulletinFromJson(data io.Reader) *SecurityBulletin { + var o *SecurityBulletin + json.NewDecoder(data).Decode(&o) + return o +} + +func (me SecurityBulletins) ToJson() string { + if b, err := json.Marshal(me); err != nil { + return "[]" + } else { + return string(b) + } +} + +func SecurityBulletinsFromJson(data io.Reader) SecurityBulletins { + var o SecurityBulletins + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/session.go b/vendor/github.com/mattermost/mattermost-server/model/session.go new file mode 100644 index 00000000..d59e9b18 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/session.go @@ -0,0 +1,164 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "strings" +) + +const ( + SESSION_COOKIE_TOKEN = "MMAUTHTOKEN" + SESSION_COOKIE_USER = "MMUSERID" + SESSION_CACHE_SIZE = 35000 + SESSION_PROP_PLATFORM = "platform" + SESSION_PROP_OS = "os" + SESSION_PROP_BROWSER = "browser" + SESSION_PROP_TYPE = "type" + SESSION_PROP_USER_ACCESS_TOKEN_ID = "user_access_token_id" + SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken" + SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes + SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years +) + +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"` + DeviceId string `json:"device_id"` + Roles string `json:"roles"` + IsOAuth bool `json:"is_oauth"` + Props StringMap `json:"props"` + TeamMembers []*TeamMember `json:"team_members" db:"-"` +} + +func (me *Session) DeepCopy() *Session { + copySession := *me + + if me.Props != nil { + copySession.Props = CopyStringMap(me.Props) + } + + if me.TeamMembers != nil { + copySession.TeamMembers = make([]*TeamMember, len(me.TeamMembers)) + for index, tm := range me.TeamMembers { + copySession.TeamMembers[index] = new(TeamMember) + *copySession.TeamMembers[index] = *tm + } + } + + return ©Session +} + +func (me *Session) ToJson() string { + b, _ := json.Marshal(me) + return string(b) +} + +func SessionFromJson(data io.Reader) *Session { + var me *Session + json.NewDecoder(data).Decode(&me) + return me +} + +func (me *Session) PreSave() { + if me.Id == "" { + me.Id = NewId() + } + + if me.Token == "" { + 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) { + if me.CreateAt == 0 { + me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) + } else { + me.ExpiresAt = me.CreateAt + (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 (me *Session) GetTeamByTeamId(teamId string) *TeamMember { + for _, team := range me.TeamMembers { + if team.TeamId == teamId { + return team + } + } + + return nil +} + +func (me *Session) IsMobileApp() bool { + return len(me.DeviceId) > 0 +} + +func (me *Session) GetUserRoles() []string { + return strings.Fields(me.Roles) +} + +func (me *Session) GenerateCSRF() string { + token := NewId() + me.AddProp("csrf", token) + return token +} + +func (me *Session) GetCSRF() string { + if me.Props == nil { + return "" + } + + return me.Props["csrf"] +} + +func SessionsToJson(o []*Session) string { + if b, err := json.Marshal(o); err != nil { + return "[]" + } else { + return string(b) + } +} + +func SessionsFromJson(data io.Reader) []*Session { + var o []*Session + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/slack_attachment.go b/vendor/github.com/mattermost/mattermost-server/model/slack_attachment.go new file mode 100644 index 00000000..827bf35b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/slack_attachment.go @@ -0,0 +1,84 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "fmt" + "regexp" +) + +var linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) + +type SlackAttachment struct { + Id int64 `json:"id"` + Fallback string `json:"fallback"` + Color string `json:"color"` + Pretext string `json:"pretext"` + AuthorName string `json:"author_name"` + AuthorLink string `json:"author_link"` + AuthorIcon string `json:"author_icon"` + Title string `json:"title"` + TitleLink string `json:"title_link"` + Text string `json:"text"` + Fields []*SlackAttachmentField `json:"fields"` + ImageURL string `json:"image_url"` + ThumbURL string `json:"thumb_url"` + Footer string `json:"footer"` + FooterIcon string `json:"footer_icon"` + Timestamp interface{} `json:"ts"` // This is either a string or an int64 + Actions []*PostAction `json:"actions,omitempty"` +} + +type SlackAttachmentField struct { + Title string `json:"title"` + Value interface{} `json:"value"` + Short bool `json:"short"` +} + +func StringifySlackFieldValue(a []*SlackAttachment) []*SlackAttachment { + var nonNilAttachments []*SlackAttachment + for _, attachment := range a { + if attachment == nil { + continue + } + nonNilAttachments = append(nonNilAttachments, attachment) + + var nonNilFields []*SlackAttachmentField + for _, field := range attachment.Fields { + if field == nil { + continue + } + nonNilFields = append(nonNilFields, field) + + if field.Value != nil { + // Ensure the value is set to a string if it is set + field.Value = fmt.Sprintf("%v", field.Value) + } + } + attachment.Fields = nonNilFields + } + return nonNilAttachments +} + +// This method only parses and processes the attachments, +// all else should be set in the post which is passed +func ParseSlackAttachment(post *Post, attachments []*SlackAttachment) { + post.Type = POST_SLACK_ATTACHMENT + + for _, attachment := range attachments { + attachment.Text = ParseSlackLinksToMarkdown(attachment.Text) + attachment.Pretext = ParseSlackLinksToMarkdown(attachment.Pretext) + + for _, field := range attachment.Fields { + if value, ok := field.Value.(string); ok { + field.Value = ParseSlackLinksToMarkdown(value) + } + } + } + post.AddProp("attachments", attachments) +} + +func ParseSlackLinksToMarkdown(text string) string { + return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/status.go b/vendor/github.com/mattermost/mattermost-server/model/status.go new file mode 100644 index 00000000..7888c60a --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/status.go @@ -0,0 +1,80 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +const ( + STATUS_OUT_OF_OFFICE = "ooo" + STATUS_OFFLINE = "offline" + STATUS_AWAY = "away" + STATUS_DND = "dnd" + STATUS_ONLINE = "online" + STATUS_CACHE_SIZE = SESSION_CACHE_SIZE + STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds + STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes +) + +type Status struct { + UserId string `json:"user_id"` + Status string `json:"status"` + Manual bool `json:"manual"` + LastActivityAt int64 `json:"last_activity_at"` + ActiveChannel string `json:"active_channel,omitempty" db:"-"` +} + +func (o *Status) ToJson() string { + tempChannelId := o.ActiveChannel + o.ActiveChannel = "" + b, _ := json.Marshal(o) + o.ActiveChannel = tempChannelId + return string(b) +} + +func (o *Status) ToClusterJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func StatusFromJson(data io.Reader) *Status { + var o *Status + json.NewDecoder(data).Decode(&o) + return o +} + +func StatusListToJson(u []*Status) string { + activeChannels := make([]string, len(u)) + for index, s := range u { + activeChannels[index] = s.ActiveChannel + s.ActiveChannel = "" + } + + b, _ := json.Marshal(u) + + for index, s := range u { + s.ActiveChannel = activeChannels[index] + } + + return string(b) +} + +func StatusListFromJson(data io.Reader) []*Status { + var statuses []*Status + json.NewDecoder(data).Decode(&statuses) + return statuses +} + +func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} { + interfaceMap := map[string]interface{}{} + for _, s := range statusMap { + // Omitted statues mean offline + if s.Status != STATUS_OFFLINE { + interfaceMap[s.UserId] = s.Status + } + } + return interfaceMap +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/suggest_command.go b/vendor/github.com/mattermost/mattermost-server/model/suggest_command.go new file mode 100644 index 00000000..44f46bf7 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/suggest_command.go @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present 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, _ := json.Marshal(o) + return string(b) +} + +func SuggestCommandFromJson(data io.Reader) *SuggestCommand { + var o *SuggestCommand + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/switch_request.go b/vendor/github.com/mattermost/mattermost-server/model/switch_request.go new file mode 100644 index 00000000..2a522f49 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/switch_request.go @@ -0,0 +1,53 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type SwitchRequest struct { + CurrentService string `json:"current_service"` + NewService string `json:"new_service"` + Email string `json:"email"` + Password string `json:"password"` + NewPassword string `json:"new_password"` + MfaCode string `json:"mfa_code"` + LdapLoginId string `json:"ldap_id"` +} + +func (o *SwitchRequest) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func SwitchRequestFromJson(data io.Reader) *SwitchRequest { + var o *SwitchRequest + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *SwitchRequest) EmailToOAuth() bool { + return o.CurrentService == USER_AUTH_SERVICE_EMAIL && + (o.NewService == USER_AUTH_SERVICE_SAML || + o.NewService == USER_AUTH_SERVICE_GITLAB || + o.NewService == SERVICE_GOOGLE || + o.NewService == SERVICE_OFFICE365) +} + +func (o *SwitchRequest) OAuthToEmail() bool { + return (o.CurrentService == USER_AUTH_SERVICE_SAML || + o.CurrentService == USER_AUTH_SERVICE_GITLAB || + o.CurrentService == SERVICE_GOOGLE || + o.CurrentService == SERVICE_OFFICE365) && o.NewService == USER_AUTH_SERVICE_EMAIL +} + +func (o *SwitchRequest) EmailToLdap() bool { + return o.CurrentService == USER_AUTH_SERVICE_EMAIL && o.NewService == USER_AUTH_SERVICE_LDAP +} + +func (o *SwitchRequest) LdapToEmail() bool { + return o.CurrentService == USER_AUTH_SERVICE_LDAP && o.NewService == USER_AUTH_SERVICE_EMAIL +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/system.go b/vendor/github.com/mattermost/mattermost-server/model/system.go new file mode 100644 index 00000000..4228516d --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/system.go @@ -0,0 +1,47 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "math/big" +) + +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" + SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey" + SYSTEM_INSTALLATION_DATE_KEY = "InstallationDate" +) + +type System struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func (o *System) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func SystemFromJson(data io.Reader) *System { + var o *System + json.NewDecoder(data).Decode(&o) + return o +} + +type SystemAsymmetricSigningKey struct { + ECDSAKey *SystemECDSAKey `json:"ecdsa_key,omitempty"` +} + +type SystemECDSAKey struct { + Curve string `json:"curve"` + X *big.Int `json:"x"` + Y *big.Int `json:"y"` + D *big.Int `json:"d,omitempty"` +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/team.go b/vendor/github.com/mattermost/mattermost-server/model/team.go new file mode 100644 index 00000000..eadd0522 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/team.go @@ -0,0 +1,296 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "strings" + "unicode/utf8" +) + +const ( + TEAM_OPEN = "O" + TEAM_INVITE = "I" + TEAM_ALLOWED_DOMAINS_MAX_LENGTH = 500 + TEAM_COMPANY_NAME_MAX_LENGTH = 64 + TEAM_DESCRIPTION_MAX_LENGTH = 255 + TEAM_DISPLAY_NAME_MAX_RUNES = 64 + TEAM_EMAIL_MAX_LENGTH = 128 + TEAM_NAME_MAX_LENGTH = 64 + TEAM_NAME_MIN_LENGTH = 2 +) + +type Team struct { + 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"` + Description string `json:"description"` + 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"` + LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"` + SchemeId *string `json:"scheme_id"` +} + +type TeamPatch struct { + DisplayName *string `json:"display_name"` + Description *string `json:"description"` + CompanyName *string `json:"company_name"` + AllowedDomains *string `json:"allowed_domains"` + InviteId *string `json:"invite_id"` + AllowOpenInvite *bool `json:"allow_open_invite"` +} + +type TeamForExport struct { + Team + SchemeName *string +} + +type Invites struct { + Invites []map[string]string `json:"invites"` +} + +func InvitesFromJson(data io.Reader) *Invites { + var o *Invites + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *Invites) ToEmailList() []string { + emailList := make([]string, len(o.Invites)) + for _, invite := range o.Invites { + emailList = append(emailList, invite["email"]) + } + return emailList +} + +func (o *Invites) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func (o *Team) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func TeamFromJson(data io.Reader) *Team { + var o *Team + json.NewDecoder(data).Decode(&o) + return o +} + +func TeamMapToJson(u map[string]*Team) string { + b, _ := json.Marshal(u) + return string(b) +} + +func TeamMapFromJson(data io.Reader) map[string]*Team { + var teams map[string]*Team + json.NewDecoder(data).Decode(&teams) + return teams +} + +func TeamListToJson(t []*Team) string { + b, _ := json.Marshal(t) + return string(b) +} + +func TeamListFromJson(data io.Reader) []*Team { + var teams []*Team + json.NewDecoder(data).Decode(&teams) + return teams +} + +func (o *Team) Etag() string { + return Etag(o.Id, o.UpdateAt) +} + +func (o *Team) IsValid() *AppError { + + if len(o.Id) != 26 { + return NewAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if o.CreateAt == 0 { + return NewAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if o.UpdateAt == 0 { + return NewAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.Email) > TEAM_EMAIL_MAX_LENGTH { + return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.Email) > 0 && !IsValidEmail(o.Email) { + return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > TEAM_DISPLAY_NAME_MAX_RUNES { + return NewAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.Name) > TEAM_NAME_MAX_LENGTH { + return NewAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.Description) > TEAM_DESCRIPTION_MAX_LENGTH { + return NewAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if IsReservedTeamName(o.Name) { + return NewAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if !IsValidTeamName(o.Name) { + return NewAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) { + return NewAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.CompanyName) > TEAM_COMPANY_NAME_MAX_LENGTH { + return NewAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + if len(o.AllowedDomains) > TEAM_ALLOWED_DOMAINS_MAX_LENGTH { + return NewAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id, http.StatusBadRequest) + } + + 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) { + return false + } + + if len(s) < TEAM_NAME_MIN_LENGTH { + 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) Sanitize() { + o.Email = "" +} + +func (t *Team) Patch(patch *TeamPatch) { + if patch.DisplayName != nil { + t.DisplayName = *patch.DisplayName + } + + if patch.Description != nil { + t.Description = *patch.Description + } + + if patch.CompanyName != nil { + t.CompanyName = *patch.CompanyName + } + + if patch.AllowedDomains != nil { + t.AllowedDomains = *patch.AllowedDomains + } + + if patch.InviteId != nil { + t.InviteId = *patch.InviteId + } + + if patch.AllowOpenInvite != nil { + t.AllowOpenInvite = *patch.AllowOpenInvite + } +} + +func (t *TeamPatch) ToJson() string { + b, err := json.Marshal(t) + if err != nil { + return "" + } + + return string(b) +} + +func TeamPatchFromJson(data io.Reader) *TeamPatch { + decoder := json.NewDecoder(data) + var team TeamPatch + err := decoder.Decode(&team) + if err != nil { + return nil + } + + return &team +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/team_member.go b/vendor/github.com/mattermost/mattermost-server/model/team_member.go new file mode 100644 index 00000000..3bae3d7e --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/team_member.go @@ -0,0 +1,102 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" + "strings" +) + +type TeamMember struct { + TeamId string `json:"team_id"` + UserId string `json:"user_id"` + Roles string `json:"roles"` + DeleteAt int64 `json:"delete_at"` + SchemeUser bool `json:"scheme_user"` + SchemeAdmin bool `json:"scheme_admin"` + ExplicitRoles string `json:"explicit_roles"` +} + +type TeamUnread struct { + TeamId string `json:"team_id"` + MsgCount int64 `json:"msg_count"` + MentionCount int64 `json:"mention_count"` +} + +type TeamMemberForExport struct { + TeamMember + TeamName string +} + +func (o *TeamMember) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func (o *TeamUnread) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func TeamMemberFromJson(data io.Reader) *TeamMember { + var o *TeamMember + json.NewDecoder(data).Decode(&o) + return o +} + +func TeamUnreadFromJson(data io.Reader) *TeamUnread { + var o *TeamUnread + json.NewDecoder(data).Decode(&o) + return o +} + +func TeamMembersToJson(o []*TeamMember) string { + if b, err := json.Marshal(o); err != nil { + return "[]" + } else { + return string(b) + } +} + +func TeamMembersFromJson(data io.Reader) []*TeamMember { + var o []*TeamMember + json.NewDecoder(data).Decode(&o) + return o +} + +func TeamsUnreadToJson(o []*TeamUnread) string { + if b, err := json.Marshal(o); err != nil { + return "[]" + } else { + return string(b) + } +} + +func TeamsUnreadFromJson(data io.Reader) []*TeamUnread { + var o []*TeamUnread + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *TeamMember) IsValid() *AppError { + + if len(o.TeamId) != 26 { + return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(o.UserId) != 26 { + return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (o *TeamMember) PreUpdate() { +} + +func (o *TeamMember) GetRoles() []string { + return strings.Fields(o.Roles) +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/team_search.go b/vendor/github.com/mattermost/mattermost-server/model/team_search.go new file mode 100644 index 00000000..e0676022 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/team_search.go @@ -0,0 +1,35 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type TeamSearch struct { + Term string `json:"term"` +} + +// ToJson convert a TeamSearch to json string +func (c *TeamSearch) ToJson() string { + b, err := json.Marshal(c) + if err != nil { + return "" + } + + return string(b) +} + +// TeamSearchFromJson decodes the input and returns a TeamSearch +func TeamSearchFromJson(data io.Reader) *TeamSearch { + decoder := json.NewDecoder(data) + var cs TeamSearch + err := decoder.Decode(&cs) + if err == nil { + return &cs + } + + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/team_stats.go b/vendor/github.com/mattermost/mattermost-server/model/team_stats.go new file mode 100644 index 00000000..0d688b80 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/team_stats.go @@ -0,0 +1,26 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type TeamStats struct { + TeamId string `json:"team_id"` + TotalMemberCount int64 `json:"total_member_count"` + ActiveMemberCount int64 `json:"active_member_count"` +} + +func (o *TeamStats) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func TeamStatsFromJson(data io.Reader) *TeamStats { + var o *TeamStats + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/terms_of_service.go b/vendor/github.com/mattermost/mattermost-server/model/terms_of_service.go new file mode 100644 index 00000000..c99a7856 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/terms_of_service.go @@ -0,0 +1,70 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "unicode/utf8" +) + +// we only ever need the latest version of terms of service +const TERMS_OF_SERVICE_CACHE_SIZE = 1 + +type TermsOfService struct { + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + UserId string `json:"user_id"` + Text string `json:"text"` +} + +func (t *TermsOfService) IsValid() *AppError { + if len(t.Id) != 26 { + return InvalidTermsOfServiceError("id", "") + } + + if t.CreateAt == 0 { + return InvalidTermsOfServiceError("create_at", t.Id) + } + + if len(t.UserId) != 26 { + return InvalidTermsOfServiceError("user_id", t.Id) + } + + if utf8.RuneCountInString(t.Text) > POST_MESSAGE_MAX_RUNES_V2 { + return InvalidTermsOfServiceError("text", t.Id) + } + + return nil +} + +func (t *TermsOfService) ToJson() string { + b, _ := json.Marshal(t) + return string(b) +} + +func TermsOfServiceFromJson(data io.Reader) *TermsOfService { + var termsOfService *TermsOfService + json.NewDecoder(data).Decode(&termsOfService) + return termsOfService +} + +func InvalidTermsOfServiceError(fieldName string, termsOfServiceId string) *AppError { + id := fmt.Sprintf("model.terms_of_service.is_valid.%s.app_error", fieldName) + details := "" + if termsOfServiceId != "" { + details = "terms_of_service_id=" + termsOfServiceId + } + return NewAppError("TermsOfServiceStore.IsValid", id, map[string]interface{}{"MaxLength": POST_MESSAGE_MAX_RUNES_V2}, details, http.StatusBadRequest) +} + +func (t *TermsOfService) PreSave() { + if t.Id == "" { + t.Id = NewId() + } + + t.CreateAt = GetMillis() +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/timezone.go b/vendor/github.com/mattermost/mattermost-server/model/timezone.go new file mode 100644 index 00000000..420b9d2e --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/timezone.go @@ -0,0 +1,628 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type SupportedTimezones []string + +func TimezonesToJson(timezoneList []string) string { + b, _ := json.Marshal(timezoneList) + return string(b) +} + +func TimezonesFromJson(data io.Reader) SupportedTimezones { + var timezones SupportedTimezones + json.NewDecoder(data).Decode(&timezones) + return timezones +} + +func DefaultUserTimezone() map[string]string { + defaultTimezone := make(map[string]string) + defaultTimezone["useAutomaticTimezone"] = "true" + defaultTimezone["automaticTimezone"] = "" + defaultTimezone["manualTimezone"] = "" + + return defaultTimezone +} + +var DefaultSupportedTimezones = []string{ + "Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Asmera", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Buenos_Aires", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Catamarca", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Coral_Harbour", + "America/Cordoba", + "America/Costa_Rica", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Nelson", + "America/Fort_Wayne", + "America/Fortaleza", + "America/Glace_Bay", + "America/Godthab", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indianapolis", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Jujuy", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Knox_IN", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Louisville", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Mendoza", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Rosario", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/South_Pole", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Ashkhabad", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Calcutta", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Chungking", + "Asia/Colombo", + "Asia/Dacca", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Katmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macao", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qyzylorda", + "Asia/Rangoon", + "Asia/Riyadh", + "Asia/Saigon", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimbu", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar", + "Asia/Ulan_Bator", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faeroe", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/ACT", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/LHI", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/NSW", + "Australia/North", + "Australia/Perth", + "Australia/Queensland", + "Australia/South", + "Australia/Sydney", + "Australia/Tasmania", + "Australia/Victoria", + "Australia/West", + "Australia/Yancowinna", + "Brazil/Acre", + "Brazil/DeNoronha", + "Brazil/East", + "Brazil/West", + "CET", + "CST6CDT", + "Canada/Atlantic", + "Canada/Central", + "Canada/East-Saskatchewan", + "Canada/Eastern", + "Canada/Mountain", + "Canada/Newfoundland", + "Canada/Pacific", + "Canada/Saskatchewan", + "Canada/Yukon", + "Chile/Continental", + "Chile/EasterIsland", + "Cuba", + "EET", + "EST", + "EST5EDT", + "Egypt", + "Eire", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kiev", + "Europe/Kirov", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Ulyanovsk", + "Europe/Uzhgorod", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zaporozhye", + "Europe/Zurich", + "Factory", + "GB", + "GB-Eire", + "GMT", + "GMT+0", + "GMT-0", + "GMT0", + "Greenwich", + "HST", + "Hongkong", + "Iceland", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Iran", + "Israel", + "Jamaica", + "Japan", + "Kwajalein", + "Libya", + "MET", + "MST", + "MST7MDT", + "Mexico/BajaNorte", + "Mexico/BajaSur", + "Mexico/General", + "NZ", + "NZ-CHAT", + "Navajo", + "PRC", + "PST8PDT", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Enderbury", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Ponape", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Truk", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "Poland", + "Portugal", + "ROC", + "ROK", + "Singapore", + "Turkey", + "UCT", + "US/Alaska", + "US/Aleutian", + "US/Arizona", + "US/Central", + "US/East-Indiana", + "US/Eastern", + "US/Hawaii", + "US/Indiana-Starke", + "US/Michigan", + "US/Mountain", + "US/Pacific", + "US/Pacific-New", + "US/Samoa", + "UTC", + "Universal", + "W-SU", + "WET", + "Zulu", +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/token.go b/vendor/github.com/mattermost/mattermost-server/model/token.go new file mode 100644 index 00000000..a4d10c7f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/token.go @@ -0,0 +1,40 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import "net/http" + +const ( + TOKEN_SIZE = 64 + MAX_TOKEN_EXIPRY_TIME = 1000 * 60 * 60 * 24 // 24 hour + TOKEN_TYPE_OAUTH = "oauth" +) + +type Token struct { + Token string + CreateAt int64 + Type string + Extra string +} + +func NewToken(tokentype, extra string) *Token { + return &Token{ + Token: NewRandomString(TOKEN_SIZE), + CreateAt: GetMillis(), + Type: tokentype, + Extra: extra, + } +} + +func (t *Token) IsValid() *AppError { + if len(t.Token) != TOKEN_SIZE { + return NewAppError("Token.IsValid", "model.token.is_valid.size", nil, "", http.StatusInternalServerError) + } + + if t.CreateAt == 0 { + return NewAppError("Token.IsValid", "model.token.is_valid.expiry", nil, "", http.StatusInternalServerError) + } + + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/user.go b/vendor/github.com/mattermost/mattermost-server/model/user.go new file mode 100644 index 00000000..51f54c1b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/user.go @@ -0,0 +1,641 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "strings" + "unicode/utf8" + + "golang.org/x/crypto/bcrypt" +) + +const ( + ME = "me" + USER_NOTIFY_ALL = "all" + USER_NOTIFY_MENTION = "mention" + USER_NOTIFY_NONE = "none" + DESKTOP_NOTIFY_PROP = "desktop" + DESKTOP_SOUND_NOTIFY_PROP = "desktop_sound" + MARK_UNREAD_NOTIFY_PROP = "mark_unread" + PUSH_NOTIFY_PROP = "push" + PUSH_STATUS_NOTIFY_PROP = "push_status" + EMAIL_NOTIFY_PROP = "email" + CHANNEL_MENTIONS_NOTIFY_PROP = "channel" + COMMENTS_NOTIFY_PROP = "comments" + MENTION_KEYS_NOTIFY_PROP = "mention_keys" + COMMENTS_NOTIFY_NEVER = "never" + COMMENTS_NOTIFY_ROOT = "root" + COMMENTS_NOTIFY_ANY = "any" + + DEFAULT_LOCALE = "en" + USER_AUTH_SERVICE_EMAIL = "email" + + USER_EMAIL_MAX_LENGTH = 128 + USER_NICKNAME_MAX_RUNES = 64 + USER_POSITION_MAX_RUNES = 128 + USER_FIRST_NAME_MAX_RUNES = 64 + USER_LAST_NAME_MAX_RUNES = 64 + USER_AUTH_DATA_MAX_LENGTH = 128 + USER_NAME_MAX_LENGTH = 64 + USER_NAME_MIN_LENGTH = 1 + USER_PASSWORD_MAX_LENGTH = 72 +) + +type User struct { + Id string `json:"id"` + CreateAt int64 `json:"create_at,omitempty"` + UpdateAt int64 `json:"update_at,omitempty"` + DeleteAt int64 `json:"delete_at"` + 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"` + Position string `json:"position"` + Roles string `json:"roles"` + AllowMarketing bool `json:"allow_marketing,omitempty"` + Props StringMap `json:"props,omitempty"` + NotifyProps StringMap `json:"notify_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"` + Timezone StringMap `json:"timezone"` + MfaActive bool `json:"mfa_active,omitempty"` + MfaSecret string `json:"mfa_secret,omitempty"` + LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"` + AcceptedTermsOfServiceId string `json:"accepted_terms_of_service_id,omitempty"` // TODO remove this field when new TOS user action table is created +} + +type UserPatch struct { + Username *string `json:"username"` + Nickname *string `json:"nickname"` + FirstName *string `json:"first_name"` + LastName *string `json:"last_name"` + Position *string `json:"position"` + Email *string `json:"email"` + Props StringMap `json:"props,omitempty"` + NotifyProps StringMap `json:"notify_props,omitempty"` + Locale *string `json:"locale"` + Timezone StringMap `json:"timezone"` +} + +type UserAuth struct { + Password string `json:"password,omitempty"` + AuthData *string `json:"auth_data,omitempty"` + AuthService string `json:"auth_service,omitempty"` +} + +func (u *User) DeepCopy() *User { + copyUser := *u + if u.AuthData != nil { + copyUser.AuthData = NewString(*u.AuthData) + } + if u.Props != nil { + copyUser.Props = CopyStringMap(u.Props) + } + if u.NotifyProps != nil { + copyUser.NotifyProps = CopyStringMap(u.NotifyProps) + } + if u.Timezone != nil { + copyUser.Timezone = CopyStringMap(u.Timezone) + } + return ©User +} + +// 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 InvalidUserError("id", "") + } + + if u.CreateAt == 0 { + return InvalidUserError("create_at", u.Id) + } + + if u.UpdateAt == 0 { + return InvalidUserError("update_at", u.Id) + } + + if !IsValidUsername(u.Username) { + return InvalidUserError("username", u.Id) + } + + if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 || !IsValidEmail(u.Email) { + return InvalidUserError("email", u.Id) + } + + if utf8.RuneCountInString(u.Nickname) > USER_NICKNAME_MAX_RUNES { + return InvalidUserError("nickname", u.Id) + } + + if utf8.RuneCountInString(u.Position) > USER_POSITION_MAX_RUNES { + return InvalidUserError("position", u.Id) + } + + if utf8.RuneCountInString(u.FirstName) > USER_FIRST_NAME_MAX_RUNES { + return InvalidUserError("first_name", u.Id) + } + + if utf8.RuneCountInString(u.LastName) > USER_LAST_NAME_MAX_RUNES { + return InvalidUserError("last_name", u.Id) + } + + if u.AuthData != nil && len(*u.AuthData) > USER_AUTH_DATA_MAX_LENGTH { + return InvalidUserError("auth_data", u.Id) + } + + if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 { + return InvalidUserError("auth_data_type", u.Id) + } + + if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 { + return InvalidUserError("auth_data_pwd", u.Id) + } + + if len(u.Password) > USER_PASSWORD_MAX_LENGTH { + return InvalidUserError("password_limit", u.Id) + } + + return nil +} + +func InvalidUserError(fieldName string, userId string) *AppError { + id := fmt.Sprintf("model.user.is_valid.%s.app_error", fieldName) + details := "" + if userId != "" { + details = "user_id=" + userId + } + return NewAppError("User.IsValid", id, nil, details, http.StatusBadRequest) +} + +func NormalizeUsername(username string) string { + return strings.ToLower(username) +} + +func NormalizeEmail(email string) string { + return strings.ToLower(email) +} + +// 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() + } + + if u.AuthData != nil && *u.AuthData == "" { + u.AuthData = nil + } + + u.Username = NormalizeUsername(u.Username) + u.Email = NormalizeEmail(u.Email) + + 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 u.Timezone == nil { + u.Timezone = DefaultUserTimezone() + } + + 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 = NormalizeUsername(u.Username) + u.Email = NormalizeEmail(u.Email) + u.UpdateAt = GetMillis() + + if u.AuthData != nil && *u.AuthData == "" { + u.AuthData = nil + } + + 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["push"] = USER_NOTIFY_MENTION + u.NotifyProps["desktop"] = USER_NOTIFY_MENTION + u.NotifyProps["desktop_sound"] = "true" + u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username + u.NotifyProps["channel"] = "true" + u.NotifyProps["push_status"] = STATUS_AWAY + u.NotifyProps["comments"] = "never" + u.NotifyProps["first_name"] = "false" +} + +func (user *User) UpdateMentionKeysFromUsername(oldUsername string) { + nonUsernameKeys := []string{} + splitKeys := strings.Split(user.NotifyProps["mention_keys"], ",") + for _, key := range splitKeys { + if key != oldUsername && key != "@"+oldUsername { + nonUsernameKeys = append(nonUsernameKeys, key) + } + } + + user.NotifyProps["mention_keys"] = user.Username + ",@" + user.Username + if len(nonUsernameKeys) > 0 { + user.NotifyProps["mention_keys"] += "," + strings.Join(nonUsernameKeys, ",") + } +} + +func (u *User) Patch(patch *UserPatch) { + if patch.Username != nil { + u.Username = *patch.Username + } + + if patch.Nickname != nil { + u.Nickname = *patch.Nickname + } + + if patch.FirstName != nil { + u.FirstName = *patch.FirstName + } + + if patch.LastName != nil { + u.LastName = *patch.LastName + } + + if patch.Position != nil { + u.Position = *patch.Position + } + + if patch.Email != nil { + u.Email = *patch.Email + } + + if patch.Props != nil { + u.Props = patch.Props + } + + if patch.NotifyProps != nil { + u.NotifyProps = patch.NotifyProps + } + + if patch.Locale != nil { + u.Locale = *patch.Locale + } + + if patch.Timezone != nil { + u.Timezone = patch.Timezone + } +} + +// ToJson convert a User to a json string +func (u *User) ToJson() string { + b, _ := json.Marshal(u) + return string(b) +} + +func (u *UserPatch) ToJson() string { + b, _ := json.Marshal(u) + return string(b) +} + +func (u *UserAuth) ToJson() string { + b, _ := json.Marshal(u) + return string(b) +} + +// Generate a valid strong etag so the browser can cache the results +func (u *User) Etag(showFullName, showEmail bool) string { + return Etag(u.Id, u.UpdateAt, showFullName, showEmail) +} + +// Remove any private data from the user object +func (u *User) Sanitize(options map[string]bool) { + u.Password = "" + u.AuthData = NewString("") + u.MfaSecret = "" + + 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 + } + if len(options) != 0 && !options["authservice"] { + u.AuthService = "" + } +} + +func (u *User) ClearNonProfileFields() { + u.Password = "" + u.AuthData = NewString("") + u.MfaSecret = "" + u.EmailVerified = false + u.AllowMarketing = false + u.NotifyProps = StringMap{} + u.LastPasswordUpdate = 0 + u.FailedAttempts = 0 +} + +func (u *User) SanitizeProfile(options map[string]bool) { + u.ClearNonProfileFields() + + u.Sanitize(options) +} + +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) AddNotifyProp(key string, value string) { + u.MakeNonNil() + + u.NotifyProps[key] = value +} + +func (u *User) GetFullName() string { + if len(u.FirstName) > 0 && len(u.LastName) > 0 { + return u.FirstName + " " + u.LastName + } else if len(u.FirstName) > 0 { + return u.FirstName + } else if len(u.LastName) > 0 { + return u.LastName + } else { + return "" + } +} + +func (u *User) GetDisplayName(nameFormat string) string { + displayName := u.Username + + if nameFormat == SHOW_NICKNAME_FULLNAME { + if len(u.Nickname) > 0 { + displayName = u.Nickname + } else if fullName := u.GetFullName(); len(fullName) > 0 { + displayName = fullName + } + } else if nameFormat == SHOW_FULLNAME { + if fullName := u.GetFullName(); len(fullName) > 0 { + displayName = fullName + } + } + + return displayName +} + +func (u *User) GetRoles() []string { + return strings.Fields(u.Roles) +} + +func (u *User) GetRawRoles() string { + return u.Roles +} + +func IsValidUserRoles(userRoles string) bool { + + roles := strings.Fields(userRoles) + + for _, r := range roles { + if !IsValidRoleName(r) { + return false + } + } + + // Exclude just the system_admin role explicitly to prevent mistakes + if len(roles) == 1 && roles[0] == "system_admin" { + return false + } + + return true +} + +// Make sure you acually want to use this function. In context.go there are functions to check permissions +// 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 permissions +// 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 { + return u.AuthService != "" && u.AuthService != USER_AUTH_SERVICE_EMAIL +} + +func (u *User) IsOAuthUser() bool { + return u.AuthService == USER_AUTH_SERVICE_GITLAB +} + +func (u *User) IsLDAPUser() bool { + return u.AuthService == USER_AUTH_SERVICE_LDAP +} + +func (u *User) IsSAMLUser() bool { + return u.AuthService == USER_AUTH_SERVICE_SAML +} + +func (u *User) GetPreferredTimezone() string { + if u.Timezone["useAutomaticTimezone"] == "true" { + return u.Timezone["automaticTimezone"] + } + + return u.Timezone["manualTimezone"] +} + +// UserFromJson will decode the input and return a User +func UserFromJson(data io.Reader) *User { + var user *User + json.NewDecoder(data).Decode(&user) + return user +} + +func UserPatchFromJson(data io.Reader) *UserPatch { + var user *UserPatch + json.NewDecoder(data).Decode(&user) + return user +} + +func UserAuthFromJson(data io.Reader) *UserAuth { + var user *UserAuth + json.NewDecoder(data).Decode(&user) + return user +} + +func UserMapToJson(u map[string]*User) string { + b, _ := json.Marshal(u) + return string(b) +} + +func UserMapFromJson(data io.Reader) map[string]*User { + var users map[string]*User + json.NewDecoder(data).Decode(&users) + return users +} + +func UserListToJson(u []*User) string { + b, _ := json.Marshal(u) + return string(b) +} + +func UserListFromJson(data io.Reader) []*User { + var users []*User + json.NewDecoder(data).Decode(&users) + return users +} + +// 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 || len(hash) == 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", + "matterbot", + "system", +} + +func IsValidUsername(s string) bool { + if len(s) < USER_NAME_MIN_LENGTH || len(s) > USER_NAME_MAX_LENGTH { + 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 = NormalizeUsername(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 +} + +func IsValidUserNotifyLevel(notifyLevel string) bool { + return notifyLevel == CHANNEL_NOTIFY_ALL || + notifyLevel == CHANNEL_NOTIFY_MENTION || + notifyLevel == CHANNEL_NOTIFY_NONE +} + +func IsValidPushStatusNotifyLevel(notifyLevel string) bool { + return notifyLevel == STATUS_ONLINE || + notifyLevel == STATUS_AWAY || + notifyLevel == STATUS_OFFLINE +} + +func IsValidCommentsNotifyLevel(notifyLevel string) bool { + return notifyLevel == COMMENTS_NOTIFY_ANY || + notifyLevel == COMMENTS_NOTIFY_ROOT || + notifyLevel == COMMENTS_NOTIFY_NEVER +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/user_access_token.go b/vendor/github.com/mattermost/mattermost-server/model/user_access_token.go new file mode 100644 index 00000000..bffd9fcb --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/user_access_token.go @@ -0,0 +1,65 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + "net/http" +) + +type UserAccessToken struct { + Id string `json:"id"` + Token string `json:"token,omitempty"` + UserId string `json:"user_id"` + Description string `json:"description"` + IsActive bool `json:"is_active"` +} + +func (t *UserAccessToken) IsValid() *AppError { + if len(t.Id) != 26 { + return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.id.app_error", nil, "", http.StatusBadRequest) + } + + if len(t.Token) != 26 { + return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.token.app_error", nil, "", http.StatusBadRequest) + } + + if len(t.UserId) != 26 { + return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) + } + + if len(t.Description) > 255 { + return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.description.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func (t *UserAccessToken) PreSave() { + t.Id = NewId() + t.IsActive = true +} + +func (t *UserAccessToken) ToJson() string { + b, _ := json.Marshal(t) + return string(b) +} + +func UserAccessTokenFromJson(data io.Reader) *UserAccessToken { + var t *UserAccessToken + json.NewDecoder(data).Decode(&t) + return t +} + +func UserAccessTokenListToJson(t []*UserAccessToken) string { + b, _ := json.Marshal(t) + return string(b) +} + +func UserAccessTokenListFromJson(data io.Reader) []*UserAccessToken { + var t []*UserAccessToken + json.NewDecoder(data).Decode(&t) + return t +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/user_access_token_search.go b/vendor/github.com/mattermost/mattermost-server/model/user_access_token_search.go new file mode 100644 index 00000000..1b0146ed --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/user_access_token_search.go @@ -0,0 +1,35 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type UserAccessTokenSearch struct { + Term string `json:"term"` +} + +// ToJson convert a UserAccessTokenSearch to json string +func (c *UserAccessTokenSearch) ToJson() string { + b, err := json.Marshal(c) + if err != nil { + return "" + } + + return string(b) +} + +// UserAccessTokenSearchJson decodes the input and returns a UserAccessTokenSearch +func UserAccessTokenSearchFromJson(data io.Reader) *UserAccessTokenSearch { + decoder := json.NewDecoder(data) + var cs UserAccessTokenSearch + err := decoder.Decode(&cs) + if err == nil { + return &cs + } + + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/user_autocomplete.go b/vendor/github.com/mattermost/mattermost-server/model/user_autocomplete.go new file mode 100644 index 00000000..b5edb45b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/user_autocomplete.go @@ -0,0 +1,61 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type UserAutocompleteInChannel struct { + InChannel []*User `json:"in_channel"` + OutOfChannel []*User `json:"out_of_channel"` +} + +type UserAutocompleteInTeam struct { + InTeam []*User `json:"in_team"` +} + +type UserAutocomplete struct { + Users []*User `json:"users"` + OutOfChannel []*User `json:"out_of_channel,omitempty"` +} + +func (o *UserAutocomplete) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func UserAutocompleteFromJson(data io.Reader) *UserAutocomplete { + decoder := json.NewDecoder(data) + autocomplete := new(UserAutocomplete) + err := decoder.Decode(&autocomplete) + if err == nil { + return autocomplete + } else { + return nil + } +} + +func (o *UserAutocompleteInChannel) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel { + var o *UserAutocompleteInChannel + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *UserAutocompleteInTeam) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam { + var o *UserAutocompleteInTeam + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/user_search.go b/vendor/github.com/mattermost/mattermost-server/model/user_search.go new file mode 100644 index 00000000..94596bdc --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/user_search.go @@ -0,0 +1,32 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type UserSearch struct { + Term string `json:"term"` + TeamId string `json:"team_id"` + NotInTeamId string `json:"not_in_team_id"` + InChannelId string `json:"in_channel_id"` + NotInChannelId string `json:"not_in_channel_id"` + AllowInactive bool `json:"allow_inactive"` + WithoutTeam bool `json:"without_team"` +} + +// ToJson convert a User to a json string +func (u *UserSearch) ToJson() string { + b, _ := json.Marshal(u) + return string(b) +} + +// UserSearchFromJson will decode the input and return a User +func UserSearchFromJson(data io.Reader) *UserSearch { + var us *UserSearch + json.NewDecoder(data).Decode(&us) + return us +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/users_stats.go b/vendor/github.com/mattermost/mattermost-server/model/users_stats.go new file mode 100644 index 00000000..49c882e3 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/users_stats.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type UsersStats struct { + TotalUsersCount int64 `json:"total_users_count"` +} + +func (o *UsersStats) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func UsersStatsFromJson(data io.Reader) *UsersStats { + var o *UsersStats + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/utils.go b/vendor/github.com/mattermost/mattermost-server/model/utils.go new file mode 100644 index 00000000..172b7824 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/utils.go @@ -0,0 +1,566 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "bytes" + "crypto/rand" + "encoding/base32" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/mail" + "net/url" + "regexp" + "strconv" + "strings" + "time" + "unicode" + + goi18n "github.com/nicksnyder/go-i18n/i18n" + "github.com/pborman/uuid" +) + +const ( + LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz" + UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + NUMBERS = "0123456789" + SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~" +) + +type StringInterface map[string]interface{} +type StringMap map[string]string +type StringArray []string + +var translateFunc goi18n.TranslateFunc = nil + +func AppErrorInit(t goi18n.TranslateFunc) { + translateFunc = t +} + +type AppError struct { + Id string `json:"id"` + Message string `json:"message"` // Message to be display to the end user without debugging information + DetailedError string `json:"detailed_error"` // Internal error string to help the developer + RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header + StatusCode int `json:"status_code,omitempty"` // The http status code + Where string `json:"-"` // The function where it happened in the form of Struct.Func + IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific + params map[string]interface{} +} + +func (er *AppError) Error() string { + return er.Where + ": " + er.Message + ", " + er.DetailedError +} + +func (er *AppError) Translate(T goi18n.TranslateFunc) { + if T == nil { + er.Message = er.Id + return + } + + if er.params == nil { + er.Message = T(er.Id) + } else { + er.Message = T(er.Id, er.params) + } +} + +func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string { + if er.params == nil { + return T(er.Id) + } else { + return T(er.Id, er.params) + } +} + +func (er *AppError) ToJson() string { + b, _ := json.Marshal(er) + return string(b) +} + +// AppErrorFromJson will decode the input and return an AppError +func AppErrorFromJson(data io.Reader) *AppError { + str := "" + bytes, rerr := ioutil.ReadAll(data) + if rerr != nil { + str = rerr.Error() + } else { + str = string(bytes) + } + + decoder := json.NewDecoder(strings.NewReader(str)) + var er AppError + err := decoder.Decode(&er) + if err == nil { + return &er + } else { + return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError) + } +} + +func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError { + ap := &AppError{} + ap.Id = id + ap.params = params + ap.Message = id + ap.Where = where + ap.DetailedError = details + ap.StatusCode = status + ap.IsOAuth = false + ap.Translate(translateFunc) + 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 convenience method to get milliseconds since epoch. +func GetMillis() int64 { + return time.Now().UnixNano() / int64(time.Millisecond) +} + +// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time. +func GetMillisForTime(thisTime time.Time) int64 { + return thisTime.UnixNano() / int64(time.Millisecond) +} + +// PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format +func PadDateStringZeros(dateString string) string { + parts := strings.Split(dateString, "-") + for index, part := range parts { + if len(part) == 1 { + parts[index] = "0" + part + } + } + dateString = strings.Join(parts[:], "-") + return dateString +} + +// GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day +func GetStartOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 { + localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset) + resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, localSearchTimeZone) + return GetMillisForTime(resultTime) +} + +// GetEndOfDayMillis is a convenience method to get milliseconds since epoch for provided date's end of day +func GetEndOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 { + localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset) + resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 23, 59, 59, 999999999, localSearchTimeZone) + return GetMillisForTime(resultTime) +} + +func CopyStringMap(originalMap map[string]string) map[string]string { + copyMap := make(map[string]string) + for k, v := range originalMap { + copyMap[k] = v + } + return copyMap +} + +// MapToJson converts a map to a json string +func MapToJson(objmap map[string]string) string { + b, _ := json.Marshal(objmap) + return string(b) +} + +// MapToJson converts a map to a json string +func MapBoolToJson(objmap map[string]bool) string { + b, _ := json.Marshal(objmap) + 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 + } +} + +// MapFromJson will decode the key/value pair map +func MapBoolFromJson(data io.Reader) map[string]bool { + decoder := json.NewDecoder(data) + + var objmap map[string]bool + if err := decoder.Decode(&objmap); err != nil { + return make(map[string]bool) + } else { + return objmap + } +} + +func ArrayToJson(objmap []string) string { + b, _ := json.Marshal(objmap) + 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 ArrayFromInterface(data interface{}) []string { + stringArray := []string{} + + dataArray, ok := data.([]interface{}) + if !ok { + return stringArray + } + + for _, v := range dataArray { + if str, ok := v.(string); ok { + stringArray = append(stringArray, str) + } + } + + return stringArray +} + +func StringInterfaceToJson(objmap map[string]interface{}) string { + b, _ := json.Marshal(objmap) + 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 StringToJson(s string) string { + b, _ := json.Marshal(s) + return string(b) +} + +func StringFromJson(data io.Reader) string { + decoder := json.NewDecoder(data) + + var s string + if err := decoder.Decode(&s); err != nil { + return "" + } else { + return s + } +} + +func GetServerIpAddress() string { + if addrs, err := net.InterfaceAddrs(); err != nil { + return "" + } else { + for _, addr := range addrs { + + if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() { + if ip.IP.To4() != nil { + return ip.IP.String() + } + } + } + } + + return "" +} + +func IsLower(s string) bool { + return strings.ToLower(s) == s +} + +func IsValidEmail(email string) bool { + if !IsLower(email) { + return false + } + + if addr, err := mail.ParseAddress(email); err != nil { + return false + } else if addr.Name != "" { + // mail.ParseAddress accepts input of the form "Billy Bob " which we don't allow + return false + } + + return true +} + +var reservedName = []string{ + "signup", + "login", + "admin", + "channel", + "post", + "api", + "oauth", +} + +func IsValidChannelIdentifier(s string) bool { + + if !IsValidAlphaNumHyphenUnderscore(s, true) { + return false + } + + if len(s) < CHANNEL_NAME_MIN_LENGTH { + return false + } + + return true +} + +func IsValidAlphaNum(s string) bool { + validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) + + return validAlphaNum.MatchString(s) +} + +func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { + if withFormat { + validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) + return validAlphaNumHyphenUnderscore.MatchString(s) + } + + validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) + return validSimpleAlphaNumHyphenUnderscore.MatchString(s) +} + +func Etag(parts ...interface{}) string { + + etag := CurrentVersion + + for _, part := range parts { + etag += fmt.Sprintf(".%v", part) + } + + return etag +} + +var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) +var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) +var hashtagStart = regexp.MustCompile(`^#{2,}`) +var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) + +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, "", "", -1) + post = strings.Replace(post, "", "", -1) + return post +} + +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 IsValidTurnOrStunServer(rawUri string) bool { + if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { + return false + } + + if _, err := url.ParseRequestURI(rawUri); err != nil { + return false + } + + return true +} + +func IsSafeLink(link *string) bool { + if link != nil { + if IsValidHttpUrl(*link) { + return true + } else if strings.HasPrefix(*link, "/") { + return true + } else { + return false + } + } + + return true +} + +func IsValidWebsocketUrl(rawUrl string) bool { + if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { + return false + } + + if _, err := url.ParseRequestURI(rawUrl); err != nil { + return false + } + + return true +} + +func IsValidTrueOrFalseString(value string) bool { + return value == "true" || value == "false" +} + +func IsValidNumberString(value string) bool { + if _, err := strconv.Atoi(value); err != nil { + return false + } + + return true +} + +func IsValidId(value string) bool { + if len(value) != 26 { + return false + } + + for _, r := range value { + if !unicode.IsLetter(r) && !unicode.IsNumber(r) { + return false + } + } + + return true +} + +// Copied from https://golang.org/src/net/dnsclient.go#L119 +func IsDomainName(s string) bool { + // See RFC 1035, RFC 3696. + // Presentation format has dots before every label except the first, and the + // terminal empty label is optional here because we assume fully-qualified + // (absolute) input. We must therefore reserve space for the first and last + // labels' length octets in wire format, where they are necessary and the + // maximum total length is 255. + // So our _effective_ maximum is 253, but 254 is not rejected if the last + // character is a dot. + l := len(s) + if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { + return false + } + + last := byte('.') + ok := false // Ok once we've seen a letter. + partlen := 0 + for i := 0; i < len(s); i++ { + c := s[i] + switch { + default: + return false + case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': + ok = true + partlen++ + case '0' <= c && c <= '9': + // fine + partlen++ + case c == '-': + // Byte before dash cannot be dot. + if last == '.' { + return false + } + partlen++ + case c == '.': + // Byte before dot cannot be dot, dash. + if last == '.' || last == '-' { + return false + } + if partlen > 63 || partlen == 0 { + return false + } + partlen = 0 + } + last = c + } + if last == '-' || partlen > 63 { + return false + } + + return ok +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/version.go b/vendor/github.com/mattermost/mattermost-server/model/version.go new file mode 100644 index 00000000..1b09b0ad --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/version.go @@ -0,0 +1,156 @@ +// Copyright (c) 2015-present 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 versions including any patches. +// It should be maintained in chronological order with most current +// release at the front of the list. +var versions = []string{ + "5.5.0", + "5.4.0", + "5.3.0", + "5.2.0", + "5.1.0", + "5.0.0", + "4.10.0", + "4.9.0", + "4.8.1", + "4.8.0", + "4.7.2", + "4.7.1", + "4.7.0", + "4.6.0", + "4.5.0", + "4.4.0", + "4.3.0", + "4.2.0", + "4.1.0", + "4.0.0", + "3.10.0", + "3.9.0", + "3.8.0", + "3.7.0", + "3.6.0", + "3.5.0", + "3.4.0", + "3.3.0", + "3.2.0", + "3.1.0", + "3.0.0", + "2.2.0", + "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 BuildHashEnterprise 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 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/mattermost/mattermost-server/model/webrtc.go b/vendor/github.com/mattermost/mattermost-server/model/webrtc.go new file mode 100644 index 00000000..59797a5b --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/webrtc.go @@ -0,0 +1,39 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" +) + +type WebrtcInfoResponse struct { + Token string `json:"token"` + GatewayUrl string `json:"gateway_url"` + StunUri string `json:"stun_uri,omitempty"` + TurnUri string `json:"turn_uri,omitempty"` + TurnPassword string `json:"turn_password,omitempty"` + TurnUsername string `json:"turn_username,omitempty"` +} + +type GatewayResponse struct { + Status string `json:"janus"` +} + +func GatewayResponseFromJson(data io.Reader) *GatewayResponse { + var o *GatewayResponse + json.NewDecoder(data).Decode(&o) + return o +} + +func (o *WebrtcInfoResponse) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func WebrtcInfoResponseFromJson(data io.Reader) *WebrtcInfoResponse { + var o *WebrtcInfoResponse + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/websocket_client.go b/vendor/github.com/mattermost/mattermost-server/model/websocket_client.go new file mode 100644 index 00000000..4e6c1d8c --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/websocket_client.go @@ -0,0 +1,195 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/gorilla/websocket" +) + +const ( + SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB + PING_TIMEOUT_BUFFER_SECONDS = 5 +) + +type WebSocketClient struct { + Url string // The location of the server like "ws://localhost:8065" + ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3" + ConnectUrl string // The websocket URL to connect to like "ws://localhost:8065/api/v3/path/to/websocket" + Conn *websocket.Conn // The WebSocket connection + AuthToken string // The token used to open the WebSocket + Sequence int64 // The ever-incrementing sequence attached to each WebSocket action + PingTimeoutChannel chan bool // The channel used to signal ping timeouts + EventChannel chan *WebSocketEvent + ResponseChannel chan *WebSocketResponse + ListenError *AppError + pingTimeoutTimer *time.Timer +} + +// NewWebSocketClient constructs a new WebSocket client with convenience +// methods for talking to the server. +func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { + return NewWebSocketClientWithDialer(websocket.DefaultDialer, url, authToken) +} + +// NewWebSocketClientWithDialer constructs a new WebSocket client with convenience +// methods for talking to the server using a custom dialer. +func NewWebSocketClientWithDialer(dialer *websocket.Dialer, url, authToken string) (*WebSocketClient, *AppError) { + conn, _, err := dialer.Dial(url+API_URL_SUFFIX+"/websocket", nil) + if err != nil { + return nil, NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + client := &WebSocketClient{ + url, + url + API_URL_SUFFIX, + url + API_URL_SUFFIX + "/websocket", + conn, + authToken, + 1, + make(chan bool, 1), + make(chan *WebSocketEvent, 100), + make(chan *WebSocketResponse, 100), + nil, + nil, + } + + client.configurePingHandling() + + client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) + + return client, nil +} + +// NewWebSocketClient4 constructs a new WebSocket client with convenience +// methods for talking to the server. Uses the v4 endpoint. +func NewWebSocketClient4(url, authToken string) (*WebSocketClient, *AppError) { + return NewWebSocketClient4WithDialer(websocket.DefaultDialer, url, authToken) +} + +// NewWebSocketClient4WithDialer constructs a new WebSocket client with convenience +// methods for talking to the server using a custom dialer. Uses the v4 endpoint. +func NewWebSocketClient4WithDialer(dialer *websocket.Dialer, url, authToken string) (*WebSocketClient, *AppError) { + return NewWebSocketClientWithDialer(dialer, url, authToken) +} + +func (wsc *WebSocketClient) Connect() *AppError { + return wsc.ConnectWithDialer(websocket.DefaultDialer) +} + +func (wsc *WebSocketClient) ConnectWithDialer(dialer *websocket.Dialer) *AppError { + var err error + wsc.Conn, _, err = dialer.Dial(wsc.ConnectUrl, nil) + if err != nil { + return NewAppError("Connect", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + wsc.configurePingHandling() + + wsc.EventChannel = make(chan *WebSocketEvent, 100) + wsc.ResponseChannel = make(chan *WebSocketResponse, 100) + + wsc.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": wsc.AuthToken}) + + return nil +} + +func (wsc *WebSocketClient) Close() { + wsc.Conn.Close() +} + +func (wsc *WebSocketClient) Listen() { + go func() { + defer func() { + wsc.Conn.Close() + close(wsc.EventChannel) + close(wsc.ResponseChannel) + }() + + for { + var rawMsg json.RawMessage + var err error + if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { + if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { + wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + return + } + + var event WebSocketEvent + if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { + wsc.EventChannel <- &event + continue + } + + var response WebSocketResponse + if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { + wsc.ResponseChannel <- &response + continue + } + + } + }() +} + +func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { + req := &WebSocketRequest{} + req.Seq = wsc.Sequence + req.Action = action + req.Data = data + + wsc.Sequence++ + + wsc.Conn.WriteJSON(req) +} + +// UserTyping will push a user_typing event out to all connected users +// who are in the specified channel +func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { + data := map[string]interface{}{ + "channel_id": channelId, + "parent_id": parentId, + } + + wsc.SendMessage("user_typing", data) +} + +// GetStatuses will return a map of string statuses using user id as the key +func (wsc *WebSocketClient) GetStatuses() { + wsc.SendMessage("get_statuses", nil) +} + +// GetStatusesByIds will fetch certain user statuses based on ids and return +// a map of string statuses using user id as the key +func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) { + data := map[string]interface{}{ + "user_ids": userIds, + } + wsc.SendMessage("get_statuses_by_ids", data) +} + +func (wsc *WebSocketClient) configurePingHandling() { + wsc.Conn.SetPingHandler(wsc.pingHandler) + wsc.pingTimeoutTimer = time.NewTimer(time.Second * (60 + PING_TIMEOUT_BUFFER_SECONDS)) + go wsc.pingWatchdog() +} + +func (wsc *WebSocketClient) pingHandler(appData string) error { + if !wsc.pingTimeoutTimer.Stop() { + <-wsc.pingTimeoutTimer.C + } + + wsc.pingTimeoutTimer.Reset(time.Second * (60 + PING_TIMEOUT_BUFFER_SECONDS)) + wsc.Conn.WriteMessage(websocket.PongMessage, []byte{}) + return nil +} + +func (wsc *WebSocketClient) pingWatchdog() { + <-wsc.pingTimeoutTimer.C + wsc.PingTimeoutChannel <- true +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/websocket_message.go b/vendor/github.com/mattermost/mattermost-server/model/websocket_message.go new file mode 100644 index 00000000..ea8872d7 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/websocket_message.go @@ -0,0 +1,165 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "fmt" + "io" +) + +const ( + WEBSOCKET_EVENT_TYPING = "typing" + WEBSOCKET_EVENT_POSTED = "posted" + WEBSOCKET_EVENT_POST_EDITED = "post_edited" + WEBSOCKET_EVENT_POST_DELETED = "post_deleted" + WEBSOCKET_EVENT_CHANNEL_CONVERTED = "channel_converted" + WEBSOCKET_EVENT_CHANNEL_CREATED = "channel_created" + WEBSOCKET_EVENT_CHANNEL_DELETED = "channel_deleted" + WEBSOCKET_EVENT_CHANNEL_UPDATED = "channel_updated" + WEBSOCKET_EVENT_CHANNEL_MEMBER_UPDATED = "channel_member_updated" + WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added" + WEBSOCKET_EVENT_GROUP_ADDED = "group_added" + WEBSOCKET_EVENT_NEW_USER = "new_user" + WEBSOCKET_EVENT_ADDED_TO_TEAM = "added_to_team" + WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team" + WEBSOCKET_EVENT_UPDATE_TEAM = "update_team" + WEBSOCKET_EVENT_DELETE_TEAM = "delete_team" + WEBSOCKET_EVENT_USER_ADDED = "user_added" + WEBSOCKET_EVENT_USER_UPDATED = "user_updated" + WEBSOCKET_EVENT_USER_ROLE_UPDATED = "user_role_updated" + WEBSOCKET_EVENT_MEMBERROLE_UPDATED = "memberrole_updated" + WEBSOCKET_EVENT_USER_REMOVED = "user_removed" + WEBSOCKET_EVENT_PREFERENCE_CHANGED = "preference_changed" + WEBSOCKET_EVENT_PREFERENCES_CHANGED = "preferences_changed" + WEBSOCKET_EVENT_PREFERENCES_DELETED = "preferences_deleted" + WEBSOCKET_EVENT_EPHEMERAL_MESSAGE = "ephemeral_message" + WEBSOCKET_EVENT_STATUS_CHANGE = "status_change" + WEBSOCKET_EVENT_HELLO = "hello" + WEBSOCKET_EVENT_WEBRTC = "webrtc" + WEBSOCKET_AUTHENTICATION_CHALLENGE = "authentication_challenge" + WEBSOCKET_EVENT_REACTION_ADDED = "reaction_added" + WEBSOCKET_EVENT_REACTION_REMOVED = "reaction_removed" + WEBSOCKET_EVENT_RESPONSE = "response" + WEBSOCKET_EVENT_EMOJI_ADDED = "emoji_added" + WEBSOCKET_EVENT_CHANNEL_VIEWED = "channel_viewed" + WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED = "plugin_statuses_changed" + WEBSOCKET_EVENT_PLUGIN_ENABLED = "plugin_enabled" + WEBSOCKET_EVENT_PLUGIN_DISABLED = "plugin_disabled" + WEBSOCKET_EVENT_ROLE_UPDATED = "role_updated" + WEBSOCKET_EVENT_LICENSE_CHANGED = "license_changed" + WEBSOCKET_EVENT_CONFIG_CHANGED = "config_changed" +) + +type WebSocketMessage interface { + ToJson() string + IsValid() bool + EventType() string +} + +type WebsocketBroadcast struct { + OmitUsers map[string]bool `json:"omit_users"` // broadcast is omitted for users listed here + UserId string `json:"user_id"` // broadcast only occurs for this user + ChannelId string `json:"channel_id"` // broadcast only occurs for users in this channel + TeamId string `json:"team_id"` // broadcast only occurs for users in this team + ContainsSanitizedData bool `json:"-"` + ContainsSensitiveData bool `json:"-"` +} + +type precomputedWebSocketEventJSON struct { + Event json.RawMessage + Data json.RawMessage + Broadcast json.RawMessage +} + +type WebSocketEvent struct { + Event string `json:"event"` + Data map[string]interface{} `json:"data"` + Broadcast *WebsocketBroadcast `json:"broadcast"` + Sequence int64 `json:"seq"` + + precomputedJSON *precomputedWebSocketEventJSON +} + +// PrecomputeJSON precomputes and stores the serialized JSON for all fields other than Sequence. +// This makes ToJson much more efficient when sending the same event to multiple connections. +func (m *WebSocketEvent) PrecomputeJSON() { + event, _ := json.Marshal(m.Event) + data, _ := json.Marshal(m.Data) + broadcast, _ := json.Marshal(m.Broadcast) + m.precomputedJSON = &precomputedWebSocketEventJSON{ + Event: json.RawMessage(event), + Data: json.RawMessage(data), + Broadcast: json.RawMessage(broadcast), + } +} + +func (m *WebSocketEvent) Add(key string, value interface{}) { + m.Data[key] = value +} + +func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { + return &WebSocketEvent{Event: event, Data: make(map[string]interface{}), + Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}} +} + +func (o *WebSocketEvent) IsValid() bool { + return o.Event != "" +} + +func (o *WebSocketEvent) EventType() string { + return o.Event +} + +func (o *WebSocketEvent) ToJson() string { + if o.precomputedJSON != nil { + return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, o.precomputedJSON.Event, o.precomputedJSON.Data, o.precomputedJSON.Broadcast, o.Sequence) + } + b, _ := json.Marshal(o) + return string(b) +} + +func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { + var o *WebSocketEvent + json.NewDecoder(data).Decode(&o) + return o +} + +type WebSocketResponse struct { + Status string `json:"status"` + SeqReply int64 `json:"seq_reply,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` + Error *AppError `json:"error,omitempty"` +} + +func (m *WebSocketResponse) Add(key string, value interface{}) { + m.Data[key] = value +} + +func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse { + return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data} +} + +func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse { + return &WebSocketResponse{Status: STATUS_FAIL, SeqReply: seqReply, Error: err} +} + +func (o *WebSocketResponse) IsValid() bool { + return o.Status != "" +} + +func (o *WebSocketResponse) EventType() string { + return WEBSOCKET_EVENT_RESPONSE +} + +func (o *WebSocketResponse) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { + var o *WebSocketResponse + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/model/websocket_request.go b/vendor/github.com/mattermost/mattermost-server/model/websocket_request.go new file mode 100644 index 00000000..4da626e2 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/model/websocket_request.go @@ -0,0 +1,34 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "encoding/json" + "io" + + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type WebSocketRequest struct { + // Client-provided fields + Seq int64 `json:"seq"` + Action string `json:"action"` + Data map[string]interface{} `json:"data"` + + // Server-provided fields + Session Session `json:"-"` + T goi18n.TranslateFunc `json:"-"` + Locale string `json:"-"` +} + +func (o *WebSocketRequest) ToJson() string { + b, _ := json.Marshal(o) + return string(b) +} + +func WebSocketRequestFromJson(data io.Reader) *WebSocketRequest { + var o *WebSocketRequest + json.NewDecoder(data).Decode(&o) + return o +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/jsonutils/json.go b/vendor/github.com/mattermost/mattermost-server/utils/jsonutils/json.go new file mode 100644 index 00000000..da77a2b6 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/jsonutils/json.go @@ -0,0 +1,56 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package jsonutils + +import ( + "bytes" + "encoding/json" + + "github.com/pkg/errors" +) + +type HumanizedJsonError struct { + Err error + Line int + Character int +} + +func (e *HumanizedJsonError) Error() string { + return e.Err.Error() +} + +// HumanizeJsonError extracts error offsets and annotates the error with useful context +func HumanizeJsonError(err error, data []byte) error { + if syntaxError, ok := err.(*json.SyntaxError); ok { + return NewHumanizedJsonError(syntaxError, data, syntaxError.Offset) + } else if unmarshalError, ok := err.(*json.UnmarshalTypeError); ok { + return NewHumanizedJsonError(unmarshalError, data, unmarshalError.Offset) + } else { + return err + } +} + +func NewHumanizedJsonError(err error, data []byte, offset int64) *HumanizedJsonError { + if err == nil { + return nil + } + + if offset < 0 || offset > int64(len(data)) { + return &HumanizedJsonError{ + Err: errors.Wrapf(err, "invalid offset %d", offset), + } + } + + lineSep := []byte{'\n'} + + line := bytes.Count(data[:offset], lineSep) + 1 + lastLineOffset := bytes.LastIndex(data[:offset], lineSep) + character := int(offset) - (lastLineOffset + 1) + 1 + + return &HumanizedJsonError{ + Line: line, + Character: character, + Err: errors.Wrapf(err, "parsing error at line %d, character %d", line, character), + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/autolink.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/autolink.go new file mode 100644 index 00000000..7f7d1117 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/autolink.go @@ -0,0 +1,253 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "regexp" + "strings" + "unicode" + "unicode/utf8" +) + +// Based off of extensions/autolink.c from https://github.com/github/cmark + +var ( + DefaultUrlSchemes = []string{"http", "https", "ftp", "mailto", "tel"} +) + +// Given a string with a w at the given position, tries to parse and return a range containing a www link. +// if one exists. If the text at the given position isn't a link, returns an empty string. Equivalent to +// www_match from the reference code. +func parseWWWAutolink(data string, position int) (Range, bool) { + // Check that this isn't part of another word + if position > 1 { + prevChar := data[position-1] + + if !isWhitespaceByte(prevChar) && !isAllowedBeforeWWWLink(prevChar) { + return Range{}, false + } + } + + // Check that this starts with www + if len(data)-position < 4 || !regexp.MustCompile(`^www\d{0,3}\.`).MatchString(data[position:]) { + return Range{}, false + } + + end := checkDomain(data[position:], false) + if end == 0 { + return Range{}, false + } + + end += position + + // Grab all text until the end of the string or the next whitespace character + for end < len(data) && !isWhitespaceByte(data[end]) { + end += 1 + } + + // Trim trailing punctuation + end = trimTrailingCharactersFromLink(data, position, end) + if position == end { + return Range{}, false + } + + return Range{position, end}, true +} + +func isAllowedBeforeWWWLink(c byte) bool { + switch c { + case '*', '_', '~', ')': + return true + default: + return false + } +} + +// Given a string with a : at the given position, tried to parse and return a range containing a URL scheme +// if one exists. If the text around the given position isn't a link, returns an empty string. Equivalent to +// url_match from the reference code. +func parseURLAutolink(data string, position int) (Range, bool) { + // Check that a :// exists. This doesn't match the clients that treat the slashes as optional. + if len(data)-position < 4 || data[position+1] != '/' || data[position+2] != '/' { + return Range{}, false + } + + start := position - 1 + for start > 0 && isAlphanumericByte(data[start-1]) { + start -= 1 + } + + // Ensure that the URL scheme is allowed and that at least one character after the scheme is valid. + scheme := data[start:position] + if !isSchemeAllowed(scheme) || !isValidHostCharacter(data[position+3:]) { + return Range{}, false + } + + end := checkDomain(data[position+3:], true) + if end == 0 { + return Range{}, false + } + + end += position + + // Grab all text until the end of the string or the next whitespace character + for end < len(data) && !isWhitespaceByte(data[end]) { + end += 1 + } + + // Trim trailing punctuation + end = trimTrailingCharactersFromLink(data, start, end) + if start == end { + return Range{}, false + } + + return Range{start, end}, true +} + +func isSchemeAllowed(scheme string) bool { + // Note that this doesn't support the custom URL schemes implemented by the client + for _, allowed := range DefaultUrlSchemes { + if strings.EqualFold(allowed, scheme) { + return true + } + } + + return false +} + +// Given a string starting with a URL, returns the number of valid characters that make up the URL's domain. +// Returns 0 if the string doesn't start with a domain name. allowShort determines whether or not the domain +// needs to contain a period to be considered valid. Equivalent to check_domain from the reference code. +func checkDomain(data string, allowShort bool) int { + foundUnderscore := false + foundPeriod := false + + i := 1 + for ; i < len(data)-1; i++ { + if data[i] == '_' { + foundUnderscore = true + break + } else if data[i] == '.' { + foundPeriod = true + } else if !isValidHostCharacter(data[i:]) && data[i] != '-' { + break + } + } + + if foundUnderscore { + return 0 + } + + if allowShort { + // If allowShort is set, accept any string of valid domain characters + return i + } + + // If allowShort isn't set, a valid domain just requires at least a single period. Note that this + // logic isn't entirely necessary because we already know the string starts with "www." when + // this is called from parseWWWAutolink + if foundPeriod { + return i + } else { + return 0 + } +} + +// Returns true if the provided link starts with a valid character for a domain name. Equivalent to +// is_valid_hostchar from the reference code. +func isValidHostCharacter(link string) bool { + c, _ := utf8.DecodeRuneInString(link) + if c == utf8.RuneError { + return false + } + + return !unicode.IsSpace(c) && !unicode.IsPunct(c) +} + +// Removes any trailing characters such as punctuation or stray brackets that shouldn't be part of the link. +// Returns a new end position for the link. Equivalent to autolink_delim from the reference code. +func trimTrailingCharactersFromLink(markdown string, start int, end int) int { + runes := []rune(markdown[start:end]) + linkEnd := len(runes) + + // Cut off the link before an open angle bracket if it contains one + for i, c := range runes { + if c == '<' { + linkEnd = i + break + } + } + + for linkEnd > 0 { + c := runes[linkEnd-1] + + if !canEndAutolink(c) { + // Trim trailing quotes, periods, etc + linkEnd = linkEnd - 1 + } else if c == ';' { + // Trim a trailing HTML entity + newEnd := linkEnd - 2 + + for newEnd > 0 && ((runes[newEnd] >= 'a' && runes[newEnd] <= 'z') || (runes[newEnd] >= 'A' && runes[newEnd] <= 'Z')) { + newEnd -= 1 + } + + if newEnd < linkEnd-2 && runes[newEnd] == '&' { + linkEnd = newEnd + } else { + // This isn't actually an HTML entity, so just trim the semicolon + linkEnd = linkEnd - 1 + } + } else if c == ')' { + // Only allow an autolink ending with a bracket if that bracket is part of a matching pair of brackets. + // If there are more closing brackets than opening ones, remove the extra bracket + + numClosing := 0 + numOpening := 0 + + // Examples (input text => output linked portion): + // + // http://www.pokemon.com/Pikachu_(Electric) + // => http://www.pokemon.com/Pikachu_(Electric) + // + // http://www.pokemon.com/Pikachu_((Electric) + // => http://www.pokemon.com/Pikachu_((Electric) + // + // http://www.pokemon.com/Pikachu_(Electric)) + // => http://www.pokemon.com/Pikachu_(Electric) + // + // http://www.pokemon.com/Pikachu_((Electric)) + // => http://www.pokemon.com/Pikachu_((Electric)) + + for i := 0; i < linkEnd; i++ { + if runes[i] == '(' { + numOpening += 1 + } else if runes[i] == ')' { + numClosing += 1 + } + } + + if numClosing <= numOpening { + // There's fewer or equal closing brackets, so we've found the end of the link + break + } + + linkEnd -= 1 + } else { + // There's no special characters at the end of the link, so we're at the end + break + } + } + + return start + len(string(runes[:linkEnd])) +} + +func canEndAutolink(c rune) bool { + switch c { + case '?', '!', '.', ',', ':', '*', '_', '~', '\'', '"': + return false + default: + return true + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/block_quote.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/block_quote.go new file mode 100644 index 00000000..04a32461 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/block_quote.go @@ -0,0 +1,62 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +type BlockQuote struct { + blockBase + markdown string + + Children []Block +} + +func (b *BlockQuote) Continuation(indentation int, r Range) *continuation { + if indentation > 3 { + return nil + } + s := b.markdown[r.Position:r.End] + if s == "" || s[0] != '>' { + return nil + } + remaining := Range{r.Position + 1, r.End} + indentation, indentationBytes := countIndentation(b.markdown, remaining) + if indentation > 0 { + indentation-- + } + return &continuation{ + Indentation: indentation, + Remaining: Range{remaining.Position + indentationBytes, remaining.End}, + } +} + +func (b *BlockQuote) AddChild(openBlocks []Block) []Block { + b.Children = append(b.Children, openBlocks[0]) + return openBlocks +} + +func blockQuoteStart(markdown string, indent int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { + if indent > 3 { + return nil + } + s := markdown[r.Position:r.End] + if s == "" || s[0] != '>' { + return nil + } + + block := &BlockQuote{ + markdown: markdown, + } + r.Position++ + if len(s) > 1 && s[1] == ' ' { + r.Position++ + } + + indent, bytes := countIndentation(markdown, r) + + ret := []Block{block} + if descendants := blockStartOrParagraph(markdown, indent, Range{r.Position + bytes, r.End}, nil, nil); descendants != nil { + block.Children = append(block.Children, descendants[0]) + ret = append(ret, descendants...) + } + return ret +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/blocks.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/blocks.go new file mode 100644 index 00000000..14972f94 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/blocks.go @@ -0,0 +1,153 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "strings" +) + +type continuation struct { + Indentation int + Remaining Range +} + +type Block interface { + Continuation(indentation int, r Range) *continuation + AddLine(indentation int, r Range) bool + Close() + AllowsBlockStarts() bool + HasTrailingBlankLine() bool +} + +type blockBase struct{} + +func (*blockBase) AddLine(indentation int, r Range) bool { return false } +func (*blockBase) Close() {} +func (*blockBase) AllowsBlockStarts() bool { return true } +func (*blockBase) HasTrailingBlankLine() bool { return false } + +type ContainerBlock interface { + Block + AddChild(openBlocks []Block) []Block +} + +type Range struct { + Position int + End int +} + +func closeBlocks(blocks []Block, referenceDefinitions *[]*ReferenceDefinition) { + for _, block := range blocks { + block.Close() + if p, ok := block.(*Paragraph); ok && len(p.ReferenceDefinitions) > 0 { + *referenceDefinitions = append(*referenceDefinitions, p.ReferenceDefinitions...) + } + } +} + +func ParseBlocks(markdown string, lines []Line) (*Document, []*ReferenceDefinition) { + document := &Document{} + var referenceDefinitions []*ReferenceDefinition + + openBlocks := []Block{document} + + for _, line := range lines { + r := line.Range + lastMatchIndex := 0 + + indentation, indentationBytes := countIndentation(markdown, r) + r = Range{r.Position + indentationBytes, r.End} + + for i, block := range openBlocks { + if continuation := block.Continuation(indentation, r); continuation != nil { + indentation = continuation.Indentation + r = continuation.Remaining + additionalIndentation, additionalIndentationBytes := countIndentation(markdown, r) + r = Range{r.Position + additionalIndentationBytes, r.End} + indentation += additionalIndentation + lastMatchIndex = i + } else { + break + } + } + + if openBlocks[lastMatchIndex].AllowsBlockStarts() { + if newBlocks := blockStart(markdown, indentation, r, openBlocks[:lastMatchIndex+1], openBlocks[lastMatchIndex+1:]); newBlocks != nil { + didAdd := false + for i := lastMatchIndex; i >= 0; i-- { + if container, ok := openBlocks[i].(ContainerBlock); ok { + if newBlocks := container.AddChild(newBlocks); newBlocks != nil { + closeBlocks(openBlocks[i+1:], &referenceDefinitions) + openBlocks = openBlocks[:i+1] + openBlocks = append(openBlocks, newBlocks...) + didAdd = true + break + } + } + } + if didAdd { + continue + } + } + } + + isBlank := strings.TrimSpace(markdown[r.Position:r.End]) == "" + if paragraph, ok := openBlocks[len(openBlocks)-1].(*Paragraph); ok && !isBlank { + paragraph.Text = append(paragraph.Text, r) + continue + } + + closeBlocks(openBlocks[lastMatchIndex+1:], &referenceDefinitions) + openBlocks = openBlocks[:lastMatchIndex+1] + + if openBlocks[lastMatchIndex].AddLine(indentation, r) { + continue + } + + if paragraph := newParagraph(markdown, r); paragraph != nil { + for i := lastMatchIndex; i >= 0; i-- { + if container, ok := openBlocks[i].(ContainerBlock); ok { + if newBlocks := container.AddChild([]Block{paragraph}); newBlocks != nil { + closeBlocks(openBlocks[i+1:], &referenceDefinitions) + openBlocks = openBlocks[:i+1] + openBlocks = append(openBlocks, newBlocks...) + break + } + } + } + } + } + + closeBlocks(openBlocks, &referenceDefinitions) + + return document, referenceDefinitions +} + +func blockStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { + if r.Position >= r.End { + return nil + } + + if start := blockQuoteStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { + return start + } else if start := listStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { + return start + } else if start := indentedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { + return start + } else if start := fencedCodeStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { + return start + } + + return nil +} + +func blockStartOrParagraph(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { + if start := blockStart(markdown, indentation, r, matchedBlocks, unmatchedBlocks); start != nil { + return start + } + if paragraph := newParagraph(markdown, r); paragraph != nil { + return []Block{paragraph} + } + return nil +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/document.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/document.go new file mode 100644 index 00000000..224b5d21 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/document.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +type Document struct { + blockBase + + Children []Block +} + +func (b *Document) Continuation(indentation int, r Range) *continuation { + return &continuation{ + Indentation: indentation, + Remaining: r, + } +} + +func (b *Document) AddChild(openBlocks []Block) []Block { + b.Children = append(b.Children, openBlocks[0]) + return openBlocks +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/fenced_code.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/fenced_code.go new file mode 100644 index 00000000..8b2ebd4f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/fenced_code.go @@ -0,0 +1,112 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "strings" +) + +type FencedCodeLine struct { + Indentation int + Range Range +} + +type FencedCode struct { + blockBase + markdown string + didSeeClosingFence bool + + Indentation int + OpeningFence Range + RawInfo Range + RawCode []FencedCodeLine +} + +func (b *FencedCode) Code() (result string) { + for _, code := range b.RawCode { + result += strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End] + } + return +} + +func (b *FencedCode) Info() string { + return Unescape(b.markdown[b.RawInfo.Position:b.RawInfo.End]) +} + +func (b *FencedCode) Continuation(indentation int, r Range) *continuation { + if b.didSeeClosingFence { + return nil + } + return &continuation{ + Indentation: indentation, + Remaining: r, + } +} + +func (b *FencedCode) AddLine(indentation int, r Range) bool { + s := b.markdown[r.Position:r.End] + if indentation <= 3 && strings.HasPrefix(s, b.markdown[b.OpeningFence.Position:b.OpeningFence.End]) { + suffix := strings.TrimSpace(s[b.OpeningFence.End-b.OpeningFence.Position:]) + isClosingFence := true + for _, c := range suffix { + if c != rune(s[0]) { + isClosingFence = false + break + } + } + if isClosingFence { + b.didSeeClosingFence = true + return true + } + } + + if indentation >= b.Indentation { + indentation -= b.Indentation + } else { + indentation = 0 + } + + b.RawCode = append(b.RawCode, FencedCodeLine{ + Indentation: indentation, + Range: r, + }) + return true +} + +func (b *FencedCode) AllowsBlockStarts() bool { + return false +} + +func fencedCodeStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { + s := markdown[r.Position:r.End] + + if !strings.HasPrefix(s, "```") && !strings.HasPrefix(s, "~~~") { + return nil + } + + fenceCharacter := rune(s[0]) + fenceLength := 3 + for _, c := range s[3:] { + if c == fenceCharacter { + fenceLength++ + } else { + break + } + } + + for i := r.Position + fenceLength; i < r.End; i++ { + if markdown[i] == '`' { + return nil + } + } + + return []Block{ + &FencedCode{ + markdown: markdown, + Indentation: indentation, + RawInfo: trimRightSpace(markdown, Range{r.Position + fenceLength, r.End}), + OpeningFence: Range{r.Position, r.Position + fenceLength}, + }, + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/html.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/html.go new file mode 100644 index 00000000..afb72bff --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/html.go @@ -0,0 +1,192 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "fmt" + "strings" +) + +var htmlEscaper = strings.NewReplacer( + `&`, "&", + `<`, "<", + `>`, ">", + `"`, """, +) + +// RenderHTML produces HTML with the same behavior as the example renderer used in the CommonMark +// reference materials except for one slight difference: for brevity, no unnecessary whitespace is +// inserted between elements. The output is not defined by the CommonMark spec, and it exists +// primarily as an aid in testing. +func RenderHTML(markdown string) string { + return RenderBlockHTML(Parse(markdown)) +} + +func RenderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition) (result string) { + return renderBlockHTML(block, referenceDefinitions, false) +} + +func renderBlockHTML(block Block, referenceDefinitions []*ReferenceDefinition, isTightList bool) (result string) { + switch v := block.(type) { + case *Document: + for _, block := range v.Children { + result += RenderBlockHTML(block, referenceDefinitions) + } + case *Paragraph: + if len(v.Text) == 0 { + return + } + if !isTightList { + result += "

" + } + for _, inline := range v.ParseInlines(referenceDefinitions) { + result += RenderInlineHTML(inline) + } + if !isTightList { + result += "

" + } + case *List: + if v.IsOrdered { + if v.OrderedStart != 1 { + result += fmt.Sprintf(`
    `, v.OrderedStart) + } else { + result += "
      " + } + } else { + result += "
        " + } + for _, block := range v.Children { + result += renderBlockHTML(block, referenceDefinitions, !v.IsLoose) + } + if v.IsOrdered { + result += "
    " + } else { + result += "" + } + case *ListItem: + result += "
  1. " + for _, block := range v.Children { + result += renderBlockHTML(block, referenceDefinitions, isTightList) + } + result += "
  2. " + case *BlockQuote: + result += "
    " + for _, block := range v.Children { + result += RenderBlockHTML(block, referenceDefinitions) + } + result += "
    " + case *FencedCode: + if info := v.Info(); info != "" { + language := strings.Fields(info)[0] + result += `
    `
    +		} else {
    +			result += "
    "
    +		}
    +		result += htmlEscaper.Replace(v.Code()) + "
    " + case *IndentedCode: + result += "
    " + htmlEscaper.Replace(v.Code()) + "
    " + default: + panic(fmt.Sprintf("missing case for type %T", v)) + } + return +} + +func escapeURL(url string) (result string) { + for i := 0; i < len(url); { + switch b := url[i]; b { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '-', '_', '.', '!', '~', '*', '\'', '(', ')', '#': + result += string(b) + i++ + default: + if b == '%' && i+2 < len(url) && isHexByte(url[i+1]) && isHexByte(url[i+2]) { + result += url[i : i+3] + i += 3 + } else if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') { + result += string(b) + i++ + } else { + result += fmt.Sprintf("%%%0X", b) + i++ + } + } + } + return +} + +func RenderInlineHTML(inline Inline) (result string) { + switch v := inline.(type) { + case *Text: + return htmlEscaper.Replace(v.Text) + case *HardLineBreak: + return "
    " + case *SoftLineBreak: + return "\n" + case *CodeSpan: + return "" + htmlEscaper.Replace(v.Code) + "" + case *InlineImage: + result += `` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `` + case *ReferenceImage: + result += `` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `` + case *InlineLink: + result += `` + for _, inline := range v.Children { + result += RenderInlineHTML(inline) + } + result += "" + case *ReferenceLink: + result += `` + for _, inline := range v.Children { + result += RenderInlineHTML(inline) + } + result += "" + case *Autolink: + result += `` + for _, inline := range v.Children { + result += RenderInlineHTML(inline) + } + result += "" + default: + panic(fmt.Sprintf("missing case for type %T", v)) + } + return +} + +func renderImageAltText(children []Inline) (result string) { + for _, inline := range children { + result += renderImageChildAltText(inline) + } + return +} + +func renderImageChildAltText(inline Inline) (result string) { + switch v := inline.(type) { + case *Text: + return v.Text + case *InlineImage: + for _, inline := range v.Children { + result += renderImageChildAltText(inline) + } + case *InlineLink: + for _, inline := range v.Children { + result += renderImageChildAltText(inline) + } + } + return +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/html_entities.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/html_entities.go new file mode 100644 index 00000000..8fe24811 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/html_entities.go @@ -0,0 +1,2132 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +var htmlEntities = map[string]string{ + "AElig": "\u00C6", + "AMP": "\u0026", + "Aacute": "\u00C1", + "Abreve": "\u0102", + "Acirc": "\u00C2", + "Acy": "\u0410", + "Afr": "\U0001D504", + "Agrave": "\u00C0", + "Alpha": "\u0391", + "Amacr": "\u0100", + "And": "\u2A53", + "Aogon": "\u0104", + "Aopf": "\U0001D538", + "ApplyFunction": "\u2061", + "Aring": "\u00C5", + "Ascr": "\U0001D49C", + "Assign": "\u2254", + "Atilde": "\u00C3", + "Auml": "\u00C4", + "Backslash": "\u2216", + "Barv": "\u2AE7", + "Barwed": "\u2306", + "Bcy": "\u0411", + "Because": "\u2235", + "Bernoullis": "\u212C", + "Beta": "\u0392", + "Bfr": "\U0001D505", + "Bopf": "\U0001D539", + "Breve": "\u02D8", + "Bscr": "\u212C", + "Bumpeq": "\u224E", + "CHcy": "\u0427", + "COPY": "\u00A9", + "Cacute": "\u0106", + "Cap": "\u22D2", + "CapitalDifferentialD": "\u2145", + "Cayleys": "\u212D", + "Ccaron": "\u010C", + "Ccedil": "\u00C7", + "Ccirc": "\u0108", + "Cconint": "\u2230", + "Cdot": "\u010A", + "Cedilla": "\u00B8", + "CenterDot": "\u00B7", + "Cfr": "\u212D", + "Chi": "\u03A7", + "CircleDot": "\u2299", + "CircleMinus": "\u2296", + "CirclePlus": "\u2295", + "CircleTimes": "\u2297", + "ClockwiseContourIntegral": "\u2232", + "CloseCurlyDoubleQuote": "\u201D", + "CloseCurlyQuote": "\u2019", + "Colon": "\u2237", + "Colone": "\u2A74", + "Congruent": "\u2261", + "Conint": "\u222F", + "ContourIntegral": "\u222E", + "Copf": "\u2102", + "Coproduct": "\u2210", + "CounterClockwiseContourIntegral": "\u2233", + "Cross": "\u2A2F", + "Cscr": "\U0001D49E", + "Cup": "\u22D3", + "CupCap": "\u224D", + "DD": "\u2145", + "DDotrahd": "\u2911", + "DJcy": "\u0402", + "DScy": "\u0405", + "DZcy": "\u040F", + "Dagger": "\u2021", + "Darr": "\u21A1", + "Dashv": "\u2AE4", + "Dcaron": "\u010E", + "Dcy": "\u0414", + "Del": "\u2207", + "Delta": "\u0394", + "Dfr": "\U0001D507", + "DiacriticalAcute": "\u00B4", + "DiacriticalDot": "\u02D9", + "DiacriticalDoubleAcute": "\u02DD", + "DiacriticalGrave": "\u0060", + "DiacriticalTilde": "\u02DC", + "Diamond": "\u22C4", + "DifferentialD": "\u2146", + "Dopf": "\U0001D53B", + "Dot": "\u00A8", + "DotDot": "\u20DC", + "DotEqual": "\u2250", + "DoubleContourIntegral": "\u222F", + "DoubleDot": "\u00A8", + "DoubleDownArrow": "\u21D3", + "DoubleLeftArrow": "\u21D0", + "DoubleLeftRightArrow": "\u21D4", + "DoubleLeftTee": "\u2AE4", + "DoubleLongLeftArrow": "\u27F8", + "DoubleLongLeftRightArrow": "\u27FA", + "DoubleLongRightArrow": "\u27F9", + "DoubleRightArrow": "\u21D2", + "DoubleRightTee": "\u22A8", + "DoubleUpArrow": "\u21D1", + "DoubleUpDownArrow": "\u21D5", + "DoubleVerticalBar": "\u2225", + "DownArrow": "\u2193", + "DownArrowBar": "\u2913", + "DownArrowUpArrow": "\u21F5", + "DownBreve": "\u0311", + "DownLeftRightVector": "\u2950", + "DownLeftTeeVector": "\u295E", + "DownLeftVector": "\u21BD", + "DownLeftVectorBar": "\u2956", + "DownRightTeeVector": "\u295F", + "DownRightVector": "\u21C1", + "DownRightVectorBar": "\u2957", + "DownTee": "\u22A4", + "DownTeeArrow": "\u21A7", + "Downarrow": "\u21D3", + "Dscr": "\U0001D49F", + "Dstrok": "\u0110", + "ENG": "\u014A", + "ETH": "\u00D0", + "Eacute": "\u00C9", + "Ecaron": "\u011A", + "Ecirc": "\u00CA", + "Ecy": "\u042D", + "Edot": "\u0116", + "Efr": "\U0001D508", + "Egrave": "\u00C8", + "Element": "\u2208", + "Emacr": "\u0112", + "EmptySmallSquare": "\u25FB", + "EmptyVerySmallSquare": "\u25AB", + "Eogon": "\u0118", + "Eopf": "\U0001D53C", + "Epsilon": "\u0395", + "Equal": "\u2A75", + "EqualTilde": "\u2242", + "Equilibrium": "\u21CC", + "Escr": "\u2130", + "Esim": "\u2A73", + "Eta": "\u0397", + "Euml": "\u00CB", + "Exists": "\u2203", + "ExponentialE": "\u2147", + "Fcy": "\u0424", + "Ffr": "\U0001D509", + "FilledSmallSquare": "\u25FC", + "FilledVerySmallSquare": "\u25AA", + "Fopf": "\U0001D53D", + "ForAll": "\u2200", + "Fouriertrf": "\u2131", + "Fscr": "\u2131", + "GJcy": "\u0403", + "GT": "\u003E", + "Gamma": "\u0393", + "Gammad": "\u03DC", + "Gbreve": "\u011E", + "Gcedil": "\u0122", + "Gcirc": "\u011C", + "Gcy": "\u0413", + "Gdot": "\u0120", + "Gfr": "\U0001D50A", + "Gg": "\u22D9", + "Gopf": "\U0001D53E", + "GreaterEqual": "\u2265", + "GreaterEqualLess": "\u22DB", + "GreaterFullEqual": "\u2267", + "GreaterGreater": "\u2AA2", + "GreaterLess": "\u2277", + "GreaterSlantEqual": "\u2A7E", + "GreaterTilde": "\u2273", + "Gscr": "\U0001D4A2", + "Gt": "\u226B", + "HARDcy": "\u042A", + "Hacek": "\u02C7", + "Hat": "\u005E", + "Hcirc": "\u0124", + "Hfr": "\u210C", + "HilbertSpace": "\u210B", + "Hopf": "\u210D", + "HorizontalLine": "\u2500", + "Hscr": "\u210B", + "Hstrok": "\u0126", + "HumpDownHump": "\u224E", + "HumpEqual": "\u224F", + "IEcy": "\u0415", + "IJlig": "\u0132", + "IOcy": "\u0401", + "Iacute": "\u00CD", + "Icirc": "\u00CE", + "Icy": "\u0418", + "Idot": "\u0130", + "Ifr": "\u2111", + "Igrave": "\u00CC", + "Im": "\u2111", + "Imacr": "\u012A", + "ImaginaryI": "\u2148", + "Implies": "\u21D2", + "Int": "\u222C", + "Integral": "\u222B", + "Intersection": "\u22C2", + "InvisibleComma": "\u2063", + "InvisibleTimes": "\u2062", + "Iogon": "\u012E", + "Iopf": "\U0001D540", + "Iota": "\u0399", + "Iscr": "\u2110", + "Itilde": "\u0128", + "Iukcy": "\u0406", + "Iuml": "\u00CF", + "Jcirc": "\u0134", + "Jcy": "\u0419", + "Jfr": "\U0001D50D", + "Jopf": "\U0001D541", + "Jscr": "\U0001D4A5", + "Jsercy": "\u0408", + "Jukcy": "\u0404", + "KHcy": "\u0425", + "KJcy": "\u040C", + "Kappa": "\u039A", + "Kcedil": "\u0136", + "Kcy": "\u041A", + "Kfr": "\U0001D50E", + "Kopf": "\U0001D542", + "Kscr": "\U0001D4A6", + "LJcy": "\u0409", + "LT": "\u003C", + "Lacute": "\u0139", + "Lambda": "\u039B", + "Lang": "\u27EA", + "Laplacetrf": "\u2112", + "Larr": "\u219E", + "Lcaron": "\u013D", + "Lcedil": "\u013B", + "Lcy": "\u041B", + "LeftAngleBracket": "\u27E8", + "LeftArrow": "\u2190", + "LeftArrowBar": "\u21E4", + "LeftArrowRightArrow": "\u21C6", + "LeftCeiling": "\u2308", + "LeftDoubleBracket": "\u27E6", + "LeftDownTeeVector": "\u2961", + "LeftDownVector": "\u21C3", + "LeftDownVectorBar": "\u2959", + "LeftFloor": "\u230A", + "LeftRightArrow": "\u2194", + "LeftRightVector": "\u294E", + "LeftTee": "\u22A3", + "LeftTeeArrow": "\u21A4", + "LeftTeeVector": "\u295A", + "LeftTriangle": "\u22B2", + "LeftTriangleBar": "\u29CF", + "LeftTriangleEqual": "\u22B4", + "LeftUpDownVector": "\u2951", + "LeftUpTeeVector": "\u2960", + "LeftUpVector": "\u21BF", + "LeftUpVectorBar": "\u2958", + "LeftVector": "\u21BC", + "LeftVectorBar": "\u2952", + "Leftarrow": "\u21D0", + "Leftrightarrow": "\u21D4", + "LessEqualGreater": "\u22DA", + "LessFullEqual": "\u2266", + "LessGreater": "\u2276", + "LessLess": "\u2AA1", + "LessSlantEqual": "\u2A7D", + "LessTilde": "\u2272", + "Lfr": "\U0001D50F", + "Ll": "\u22D8", + "Lleftarrow": "\u21DA", + "Lmidot": "\u013F", + "LongLeftArrow": "\u27F5", + "LongLeftRightArrow": "\u27F7", + "LongRightArrow": "\u27F6", + "Longleftarrow": "\u27F8", + "Longleftrightarrow": "\u27FA", + "Longrightarrow": "\u27F9", + "Lopf": "\U0001D543", + "LowerLeftArrow": "\u2199", + "LowerRightArrow": "\u2198", + "Lscr": "\u2112", + "Lsh": "\u21B0", + "Lstrok": "\u0141", + "Lt": "\u226A", + "Map": "\u2905", + "Mcy": "\u041C", + "MediumSpace": "\u205F", + "Mellintrf": "\u2133", + "Mfr": "\U0001D510", + "MinusPlus": "\u2213", + "Mopf": "\U0001D544", + "Mscr": "\u2133", + "Mu": "\u039C", + "NJcy": "\u040A", + "Nacute": "\u0143", + "Ncaron": "\u0147", + "Ncedil": "\u0145", + "Ncy": "\u041D", + "NegativeMediumSpace": "\u200B", + "NegativeThickSpace": "\u200B", + "NegativeThinSpace": "\u200B", + "NegativeVeryThinSpace": "\u200B", + "NestedGreaterGreater": "\u226B", + "NestedLessLess": "\u226A", + "NewLine": "\u000A", + "Nfr": "\U0001D511", + "NoBreak": "\u2060", + "NonBreakingSpace": "\u00A0", + "Nopf": "\u2115", + "Not": "\u2AEC", + "NotCongruent": "\u2262", + "NotCupCap": "\u226D", + "NotDoubleVerticalBar": "\u2226", + "NotElement": "\u2209", + "NotEqual": "\u2260", + "NotEqualTilde": "\u2242\u0338", + "NotExists": "\u2204", + "NotGreater": "\u226F", + "NotGreaterEqual": "\u2271", + "NotGreaterFullEqual": "\u2267\u0338", + "NotGreaterGreater": "\u226B\u0338", + "NotGreaterLess": "\u2279", + "NotGreaterSlantEqual": "\u2A7E\u0338", + "NotGreaterTilde": "\u2275", + "NotHumpDownHump": "\u224E\u0338", + "NotHumpEqual": "\u224F\u0338", + "NotLeftTriangle": "\u22EA", + "NotLeftTriangleBar": "\u29CF\u0338", + "NotLeftTriangleEqual": "\u22EC", + "NotLess": "\u226E", + "NotLessEqual": "\u2270", + "NotLessGreater": "\u2278", + "NotLessLess": "\u226A\u0338", + "NotLessSlantEqual": "\u2A7D\u0338", + "NotLessTilde": "\u2274", + "NotNestedGreaterGreater": "\u2AA2\u0338", + "NotNestedLessLess": "\u2AA1\u0338", + "NotPrecedes": "\u2280", + "NotPrecedesEqual": "\u2AAF\u0338", + "NotPrecedesSlantEqual": "\u22E0", + "NotReverseElement": "\u220C", + "NotRightTriangle": "\u22EB", + "NotRightTriangleBar": "\u29D0\u0338", + "NotRightTriangleEqual": "\u22ED", + "NotSquareSubset": "\u228F\u0338", + "NotSquareSubsetEqual": "\u22E2", + "NotSquareSuperset": "\u2290\u0338", + "NotSquareSupersetEqual": "\u22E3", + "NotSubset": "\u2282\u20D2", + "NotSubsetEqual": "\u2288", + "NotSucceeds": "\u2281", + "NotSucceedsEqual": "\u2AB0\u0338", + "NotSucceedsSlantEqual": "\u22E1", + "NotSucceedsTilde": "\u227F\u0338", + "NotSuperset": "\u2283\u20D2", + "NotSupersetEqual": "\u2289", + "NotTilde": "\u2241", + "NotTildeEqual": "\u2244", + "NotTildeFullEqual": "\u2247", + "NotTildeTilde": "\u2249", + "NotVerticalBar": "\u2224", + "Nscr": "\U0001D4A9", + "Ntilde": "\u00D1", + "Nu": "\u039D", + "OElig": "\u0152", + "Oacute": "\u00D3", + "Ocirc": "\u00D4", + "Ocy": "\u041E", + "Odblac": "\u0150", + "Ofr": "\U0001D512", + "Ograve": "\u00D2", + "Omacr": "\u014C", + "Omega": "\u03A9", + "Omicron": "\u039F", + "Oopf": "\U0001D546", + "OpenCurlyDoubleQuote": "\u201C", + "OpenCurlyQuote": "\u2018", + "Or": "\u2A54", + "Oscr": "\U0001D4AA", + "Oslash": "\u00D8", + "Otilde": "\u00D5", + "Otimes": "\u2A37", + "Ouml": "\u00D6", + "OverBar": "\u203E", + "OverBrace": "\u23DE", + "OverBracket": "\u23B4", + "OverParenthesis": "\u23DC", + "PartialD": "\u2202", + "Pcy": "\u041F", + "Pfr": "\U0001D513", + "Phi": "\u03A6", + "Pi": "\u03A0", + "PlusMinus": "\u00B1", + "Poincareplane": "\u210C", + "Popf": "\u2119", + "Pr": "\u2ABB", + "Precedes": "\u227A", + "PrecedesEqual": "\u2AAF", + "PrecedesSlantEqual": "\u227C", + "PrecedesTilde": "\u227E", + "Prime": "\u2033", + "Product": "\u220F", + "Proportion": "\u2237", + "Proportional": "\u221D", + "Pscr": "\U0001D4AB", + "Psi": "\u03A8", + "QUOT": "\u0022", + "Qfr": "\U0001D514", + "Qopf": "\u211A", + "Qscr": "\U0001D4AC", + "RBarr": "\u2910", + "REG": "\u00AE", + "Racute": "\u0154", + "Rang": "\u27EB", + "Rarr": "\u21A0", + "Rarrtl": "\u2916", + "Rcaron": "\u0158", + "Rcedil": "\u0156", + "Rcy": "\u0420", + "Re": "\u211C", + "ReverseElement": "\u220B", + "ReverseEquilibrium": "\u21CB", + "ReverseUpEquilibrium": "\u296F", + "Rfr": "\u211C", + "Rho": "\u03A1", + "RightAngleBracket": "\u27E9", + "RightArrow": "\u2192", + "RightArrowBar": "\u21E5", + "RightArrowLeftArrow": "\u21C4", + "RightCeiling": "\u2309", + "RightDoubleBracket": "\u27E7", + "RightDownTeeVector": "\u295D", + "RightDownVector": "\u21C2", + "RightDownVectorBar": "\u2955", + "RightFloor": "\u230B", + "RightTee": "\u22A2", + "RightTeeArrow": "\u21A6", + "RightTeeVector": "\u295B", + "RightTriangle": "\u22B3", + "RightTriangleBar": "\u29D0", + "RightTriangleEqual": "\u22B5", + "RightUpDownVector": "\u294F", + "RightUpTeeVector": "\u295C", + "RightUpVector": "\u21BE", + "RightUpVectorBar": "\u2954", + "RightVector": "\u21C0", + "RightVectorBar": "\u2953", + "Rightarrow": "\u21D2", + "Ropf": "\u211D", + "RoundImplies": "\u2970", + "Rrightarrow": "\u21DB", + "Rscr": "\u211B", + "Rsh": "\u21B1", + "RuleDelayed": "\u29F4", + "SHCHcy": "\u0429", + "SHcy": "\u0428", + "SOFTcy": "\u042C", + "Sacute": "\u015A", + "Sc": "\u2ABC", + "Scaron": "\u0160", + "Scedil": "\u015E", + "Scirc": "\u015C", + "Scy": "\u0421", + "Sfr": "\U0001D516", + "ShortDownArrow": "\u2193", + "ShortLeftArrow": "\u2190", + "ShortRightArrow": "\u2192", + "ShortUpArrow": "\u2191", + "Sigma": "\u03A3", + "SmallCircle": "\u2218", + "Sopf": "\U0001D54A", + "Sqrt": "\u221A", + "Square": "\u25A1", + "SquareIntersection": "\u2293", + "SquareSubset": "\u228F", + "SquareSubsetEqual": "\u2291", + "SquareSuperset": "\u2290", + "SquareSupersetEqual": "\u2292", + "SquareUnion": "\u2294", + "Sscr": "\U0001D4AE", + "Star": "\u22C6", + "Sub": "\u22D0", + "Subset": "\u22D0", + "SubsetEqual": "\u2286", + "Succeeds": "\u227B", + "SucceedsEqual": "\u2AB0", + "SucceedsSlantEqual": "\u227D", + "SucceedsTilde": "\u227F", + "SuchThat": "\u220B", + "Sum": "\u2211", + "Sup": "\u22D1", + "Superset": "\u2283", + "SupersetEqual": "\u2287", + "Supset": "\u22D1", + "THORN": "\u00DE", + "TRADE": "\u2122", + "TSHcy": "\u040B", + "TScy": "\u0426", + "Tab": "\u0009", + "Tau": "\u03A4", + "Tcaron": "\u0164", + "Tcedil": "\u0162", + "Tcy": "\u0422", + "Tfr": "\U0001D517", + "Therefore": "\u2234", + "Theta": "\u0398", + "ThickSpace": "\u205F\u200A", + "ThinSpace": "\u2009", + "Tilde": "\u223C", + "TildeEqual": "\u2243", + "TildeFullEqual": "\u2245", + "TildeTilde": "\u2248", + "Topf": "\U0001D54B", + "TripleDot": "\u20DB", + "Tscr": "\U0001D4AF", + "Tstrok": "\u0166", + "Uacute": "\u00DA", + "Uarr": "\u219F", + "Uarrocir": "\u2949", + "Ubrcy": "\u040E", + "Ubreve": "\u016C", + "Ucirc": "\u00DB", + "Ucy": "\u0423", + "Udblac": "\u0170", + "Ufr": "\U0001D518", + "Ugrave": "\u00D9", + "Umacr": "\u016A", + "UnderBar": "\u005F", + "UnderBrace": "\u23DF", + "UnderBracket": "\u23B5", + "UnderParenthesis": "\u23DD", + "Union": "\u22C3", + "UnionPlus": "\u228E", + "Uogon": "\u0172", + "Uopf": "\U0001D54C", + "UpArrow": "\u2191", + "UpArrowBar": "\u2912", + "UpArrowDownArrow": "\u21C5", + "UpDownArrow": "\u2195", + "UpEquilibrium": "\u296E", + "UpTee": "\u22A5", + "UpTeeArrow": "\u21A5", + "Uparrow": "\u21D1", + "Updownarrow": "\u21D5", + "UpperLeftArrow": "\u2196", + "UpperRightArrow": "\u2197", + "Upsi": "\u03D2", + "Upsilon": "\u03A5", + "Uring": "\u016E", + "Uscr": "\U0001D4B0", + "Utilde": "\u0168", + "Uuml": "\u00DC", + "VDash": "\u22AB", + "Vbar": "\u2AEB", + "Vcy": "\u0412", + "Vdash": "\u22A9", + "Vdashl": "\u2AE6", + "Vee": "\u22C1", + "Verbar": "\u2016", + "Vert": "\u2016", + "VerticalBar": "\u2223", + "VerticalLine": "\u007C", + "VerticalSeparator": "\u2758", + "VerticalTilde": "\u2240", + "VeryThinSpace": "\u200A", + "Vfr": "\U0001D519", + "Vopf": "\U0001D54D", + "Vscr": "\U0001D4B1", + "Vvdash": "\u22AA", + "Wcirc": "\u0174", + "Wedge": "\u22C0", + "Wfr": "\U0001D51A", + "Wopf": "\U0001D54E", + "Wscr": "\U0001D4B2", + "Xfr": "\U0001D51B", + "Xi": "\u039E", + "Xopf": "\U0001D54F", + "Xscr": "\U0001D4B3", + "YAcy": "\u042F", + "YIcy": "\u0407", + "YUcy": "\u042E", + "Yacute": "\u00DD", + "Ycirc": "\u0176", + "Ycy": "\u042B", + "Yfr": "\U0001D51C", + "Yopf": "\U0001D550", + "Yscr": "\U0001D4B4", + "Yuml": "\u0178", + "ZHcy": "\u0416", + "Zacute": "\u0179", + "Zcaron": "\u017D", + "Zcy": "\u0417", + "Zdot": "\u017B", + "ZeroWidthSpace": "\u200B", + "Zeta": "\u0396", + "Zfr": "\u2128", + "Zopf": "\u2124", + "Zscr": "\U0001D4B5", + "aacute": "\u00E1", + "abreve": "\u0103", + "ac": "\u223E", + "acE": "\u223E\u0333", + "acd": "\u223F", + "acirc": "\u00E2", + "acute": "\u00B4", + "acy": "\u0430", + "aelig": "\u00E6", + "af": "\u2061", + "afr": "\U0001D51E", + "agrave": "\u00E0", + "alefsym": "\u2135", + "aleph": "\u2135", + "alpha": "\u03B1", + "amacr": "\u0101", + "amalg": "\u2A3F", + "amp": "\u0026", + "and": "\u2227", + "andand": "\u2A55", + "andd": "\u2A5C", + "andslope": "\u2A58", + "andv": "\u2A5A", + "ang": "\u2220", + "ange": "\u29A4", + "angle": "\u2220", + "angmsd": "\u2221", + "angmsdaa": "\u29A8", + "angmsdab": "\u29A9", + "angmsdac": "\u29AA", + "angmsdad": "\u29AB", + "angmsdae": "\u29AC", + "angmsdaf": "\u29AD", + "angmsdag": "\u29AE", + "angmsdah": "\u29AF", + "angrt": "\u221F", + "angrtvb": "\u22BE", + "angrtvbd": "\u299D", + "angsph": "\u2222", + "angst": "\u00C5", + "angzarr": "\u237C", + "aogon": "\u0105", + "aopf": "\U0001D552", + "ap": "\u2248", + "apE": "\u2A70", + "apacir": "\u2A6F", + "ape": "\u224A", + "apid": "\u224B", + "apos": "\u0027", + "approx": "\u2248", + "approxeq": "\u224A", + "aring": "\u00E5", + "ascr": "\U0001D4B6", + "ast": "\u002A", + "asymp": "\u2248", + "asympeq": "\u224D", + "atilde": "\u00E3", + "auml": "\u00E4", + "awconint": "\u2233", + "awint": "\u2A11", + "bNot": "\u2AED", + "backcong": "\u224C", + "backepsilon": "\u03F6", + "backprime": "\u2035", + "backsim": "\u223D", + "backsimeq": "\u22CD", + "barvee": "\u22BD", + "barwed": "\u2305", + "barwedge": "\u2305", + "bbrk": "\u23B5", + "bbrktbrk": "\u23B6", + "bcong": "\u224C", + "bcy": "\u0431", + "bdquo": "\u201E", + "becaus": "\u2235", + "because": "\u2235", + "bemptyv": "\u29B0", + "bepsi": "\u03F6", + "bernou": "\u212C", + "beta": "\u03B2", + "beth": "\u2136", + "between": "\u226C", + "bfr": "\U0001D51F", + "bigcap": "\u22C2", + "bigcirc": "\u25EF", + "bigcup": "\u22C3", + "bigodot": "\u2A00", + "bigoplus": "\u2A01", + "bigotimes": "\u2A02", + "bigsqcup": "\u2A06", + "bigstar": "\u2605", + "bigtriangledown": "\u25BD", + "bigtriangleup": "\u25B3", + "biguplus": "\u2A04", + "bigvee": "\u22C1", + "bigwedge": "\u22C0", + "bkarow": "\u290D", + "blacklozenge": "\u29EB", + "blacksquare": "\u25AA", + "blacktriangle": "\u25B4", + "blacktriangledown": "\u25BE", + "blacktriangleleft": "\u25C2", + "blacktriangleright": "\u25B8", + "blank": "\u2423", + "blk12": "\u2592", + "blk14": "\u2591", + "blk34": "\u2593", + "block": "\u2588", + "bne": "\u003D\u20E5", + "bnequiv": "\u2261\u20E5", + "bnot": "\u2310", + "bopf": "\U0001D553", + "bot": "\u22A5", + "bottom": "\u22A5", + "bowtie": "\u22C8", + "boxDL": "\u2557", + "boxDR": "\u2554", + "boxDl": "\u2556", + "boxDr": "\u2553", + "boxH": "\u2550", + "boxHD": "\u2566", + "boxHU": "\u2569", + "boxHd": "\u2564", + "boxHu": "\u2567", + "boxUL": "\u255D", + "boxUR": "\u255A", + "boxUl": "\u255C", + "boxUr": "\u2559", + "boxV": "\u2551", + "boxVH": "\u256C", + "boxVL": "\u2563", + "boxVR": "\u2560", + "boxVh": "\u256B", + "boxVl": "\u2562", + "boxVr": "\u255F", + "boxbox": "\u29C9", + "boxdL": "\u2555", + "boxdR": "\u2552", + "boxdl": "\u2510", + "boxdr": "\u250C", + "boxh": "\u2500", + "boxhD": "\u2565", + "boxhU": "\u2568", + "boxhd": "\u252C", + "boxhu": "\u2534", + "boxminus": "\u229F", + "boxplus": "\u229E", + "boxtimes": "\u22A0", + "boxuL": "\u255B", + "boxuR": "\u2558", + "boxul": "\u2518", + "boxur": "\u2514", + "boxv": "\u2502", + "boxvH": "\u256A", + "boxvL": "\u2561", + "boxvR": "\u255E", + "boxvh": "\u253C", + "boxvl": "\u2524", + "boxvr": "\u251C", + "bprime": "\u2035", + "breve": "\u02D8", + "brvbar": "\u00A6", + "bscr": "\U0001D4B7", + "bsemi": "\u204F", + "bsim": "\u223D", + "bsime": "\u22CD", + "bsol": "\u005C", + "bsolb": "\u29C5", + "bsolhsub": "\u27C8", + "bull": "\u2022", + "bullet": "\u2022", + "bump": "\u224E", + "bumpE": "\u2AAE", + "bumpe": "\u224F", + "bumpeq": "\u224F", + "cacute": "\u0107", + "cap": "\u2229", + "capand": "\u2A44", + "capbrcup": "\u2A49", + "capcap": "\u2A4B", + "capcup": "\u2A47", + "capdot": "\u2A40", + "caps": "\u2229\uFE00", + "caret": "\u2041", + "caron": "\u02C7", + "ccaps": "\u2A4D", + "ccaron": "\u010D", + "ccedil": "\u00E7", + "ccirc": "\u0109", + "ccups": "\u2A4C", + "ccupssm": "\u2A50", + "cdot": "\u010B", + "cedil": "\u00B8", + "cemptyv": "\u29B2", + "cent": "\u00A2", + "centerdot": "\u00B7", + "cfr": "\U0001D520", + "chcy": "\u0447", + "check": "\u2713", + "checkmark": "\u2713", + "chi": "\u03C7", + "cir": "\u25CB", + "cirE": "\u29C3", + "circ": "\u02C6", + "circeq": "\u2257", + "circlearrowleft": "\u21BA", + "circlearrowright": "\u21BB", + "circledR": "\u00AE", + "circledS": "\u24C8", + "circledast": "\u229B", + "circledcirc": "\u229A", + "circleddash": "\u229D", + "cire": "\u2257", + "cirfnint": "\u2A10", + "cirmid": "\u2AEF", + "cirscir": "\u29C2", + "clubs": "\u2663", + "clubsuit": "\u2663", + "colon": "\u003A", + "colone": "\u2254", + "coloneq": "\u2254", + "comma": "\u002C", + "commat": "\u0040", + "comp": "\u2201", + "compfn": "\u2218", + "complement": "\u2201", + "complexes": "\u2102", + "cong": "\u2245", + "congdot": "\u2A6D", + "conint": "\u222E", + "copf": "\U0001D554", + "coprod": "\u2210", + "copy": "\u00A9", + "copysr": "\u2117", + "crarr": "\u21B5", + "cross": "\u2717", + "cscr": "\U0001D4B8", + "csub": "\u2ACF", + "csube": "\u2AD1", + "csup": "\u2AD0", + "csupe": "\u2AD2", + "ctdot": "\u22EF", + "cudarrl": "\u2938", + "cudarrr": "\u2935", + "cuepr": "\u22DE", + "cuesc": "\u22DF", + "cularr": "\u21B6", + "cularrp": "\u293D", + "cup": "\u222A", + "cupbrcap": "\u2A48", + "cupcap": "\u2A46", + "cupcup": "\u2A4A", + "cupdot": "\u228D", + "cupor": "\u2A45", + "cups": "\u222A\uFE00", + "curarr": "\u21B7", + "curarrm": "\u293C", + "curlyeqprec": "\u22DE", + "curlyeqsucc": "\u22DF", + "curlyvee": "\u22CE", + "curlywedge": "\u22CF", + "curren": "\u00A4", + "curvearrowleft": "\u21B6", + "curvearrowright": "\u21B7", + "cuvee": "\u22CE", + "cuwed": "\u22CF", + "cwconint": "\u2232", + "cwint": "\u2231", + "cylcty": "\u232D", + "dArr": "\u21D3", + "dHar": "\u2965", + "dagger": "\u2020", + "daleth": "\u2138", + "darr": "\u2193", + "dash": "\u2010", + "dashv": "\u22A3", + "dbkarow": "\u290F", + "dblac": "\u02DD", + "dcaron": "\u010F", + "dcy": "\u0434", + "dd": "\u2146", + "ddagger": "\u2021", + "ddarr": "\u21CA", + "ddotseq": "\u2A77", + "deg": "\u00B0", + "delta": "\u03B4", + "demptyv": "\u29B1", + "dfisht": "\u297F", + "dfr": "\U0001D521", + "dharl": "\u21C3", + "dharr": "\u21C2", + "diam": "\u22C4", + "diamond": "\u22C4", + "diamondsuit": "\u2666", + "diams": "\u2666", + "die": "\u00A8", + "digamma": "\u03DD", + "disin": "\u22F2", + "div": "\u00F7", + "divide": "\u00F7", + "divideontimes": "\u22C7", + "divonx": "\u22C7", + "djcy": "\u0452", + "dlcorn": "\u231E", + "dlcrop": "\u230D", + "dollar": "\u0024", + "dopf": "\U0001D555", + "dot": "\u02D9", + "doteq": "\u2250", + "doteqdot": "\u2251", + "dotminus": "\u2238", + "dotplus": "\u2214", + "dotsquare": "\u22A1", + "doublebarwedge": "\u2306", + "downarrow": "\u2193", + "downdownarrows": "\u21CA", + "downharpoonleft": "\u21C3", + "downharpoonright": "\u21C2", + "drbkarow": "\u2910", + "drcorn": "\u231F", + "drcrop": "\u230C", + "dscr": "\U0001D4B9", + "dscy": "\u0455", + "dsol": "\u29F6", + "dstrok": "\u0111", + "dtdot": "\u22F1", + "dtri": "\u25BF", + "dtrif": "\u25BE", + "duarr": "\u21F5", + "duhar": "\u296F", + "dwangle": "\u29A6", + "dzcy": "\u045F", + "dzigrarr": "\u27FF", + "eDDot": "\u2A77", + "eDot": "\u2251", + "eacute": "\u00E9", + "easter": "\u2A6E", + "ecaron": "\u011B", + "ecir": "\u2256", + "ecirc": "\u00EA", + "ecolon": "\u2255", + "ecy": "\u044D", + "edot": "\u0117", + "ee": "\u2147", + "efDot": "\u2252", + "efr": "\U0001D522", + "eg": "\u2A9A", + "egrave": "\u00E8", + "egs": "\u2A96", + "egsdot": "\u2A98", + "el": "\u2A99", + "elinters": "\u23E7", + "ell": "\u2113", + "els": "\u2A95", + "elsdot": "\u2A97", + "emacr": "\u0113", + "empty": "\u2205", + "emptyset": "\u2205", + "emptyv": "\u2205", + "emsp": "\u2003", + "emsp13": "\u2004", + "emsp14": "\u2005", + "eng": "\u014B", + "ensp": "\u2002", + "eogon": "\u0119", + "eopf": "\U0001D556", + "epar": "\u22D5", + "eparsl": "\u29E3", + "eplus": "\u2A71", + "epsi": "\u03B5", + "epsilon": "\u03B5", + "epsiv": "\u03F5", + "eqcirc": "\u2256", + "eqcolon": "\u2255", + "eqsim": "\u2242", + "eqslantgtr": "\u2A96", + "eqslantless": "\u2A95", + "equals": "\u003D", + "equest": "\u225F", + "equiv": "\u2261", + "equivDD": "\u2A78", + "eqvparsl": "\u29E5", + "erDot": "\u2253", + "erarr": "\u2971", + "escr": "\u212F", + "esdot": "\u2250", + "esim": "\u2242", + "eta": "\u03B7", + "eth": "\u00F0", + "euml": "\u00EB", + "euro": "\u20AC", + "excl": "\u0021", + "exist": "\u2203", + "expectation": "\u2130", + "exponentiale": "\u2147", + "fallingdotseq": "\u2252", + "fcy": "\u0444", + "female": "\u2640", + "ffilig": "\uFB03", + "fflig": "\uFB00", + "ffllig": "\uFB04", + "ffr": "\U0001D523", + "filig": "\uFB01", + "fjlig": "\u0066\u006A", + "flat": "\u266D", + "fllig": "\uFB02", + "fltns": "\u25B1", + "fnof": "\u0192", + "fopf": "\U0001D557", + "forall": "\u2200", + "fork": "\u22D4", + "forkv": "\u2AD9", + "fpartint": "\u2A0D", + "frac12": "\u00BD", + "frac13": "\u2153", + "frac14": "\u00BC", + "frac15": "\u2155", + "frac16": "\u2159", + "frac18": "\u215B", + "frac23": "\u2154", + "frac25": "\u2156", + "frac34": "\u00BE", + "frac35": "\u2157", + "frac38": "\u215C", + "frac45": "\u2158", + "frac56": "\u215A", + "frac58": "\u215D", + "frac78": "\u215E", + "frasl": "\u2044", + "frown": "\u2322", + "fscr": "\U0001D4BB", + "gE": "\u2267", + "gEl": "\u2A8C", + "gacute": "\u01F5", + "gamma": "\u03B3", + "gammad": "\u03DD", + "gap": "\u2A86", + "gbreve": "\u011F", + "gcirc": "\u011D", + "gcy": "\u0433", + "gdot": "\u0121", + "ge": "\u2265", + "gel": "\u22DB", + "geq": "\u2265", + "geqq": "\u2267", + "geqslant": "\u2A7E", + "ges": "\u2A7E", + "gescc": "\u2AA9", + "gesdot": "\u2A80", + "gesdoto": "\u2A82", + "gesdotol": "\u2A84", + "gesl": "\u22DB\uFE00", + "gesles": "\u2A94", + "gfr": "\U0001D524", + "gg": "\u226B", + "ggg": "\u22D9", + "gimel": "\u2137", + "gjcy": "\u0453", + "gl": "\u2277", + "glE": "\u2A92", + "gla": "\u2AA5", + "glj": "\u2AA4", + "gnE": "\u2269", + "gnap": "\u2A8A", + "gnapprox": "\u2A8A", + "gne": "\u2A88", + "gneq": "\u2A88", + "gneqq": "\u2269", + "gnsim": "\u22E7", + "gopf": "\U0001D558", + "grave": "\u0060", + "gscr": "\u210A", + "gsim": "\u2273", + "gsime": "\u2A8E", + "gsiml": "\u2A90", + "gt": "\u003E", + "gtcc": "\u2AA7", + "gtcir": "\u2A7A", + "gtdot": "\u22D7", + "gtlPar": "\u2995", + "gtquest": "\u2A7C", + "gtrapprox": "\u2A86", + "gtrarr": "\u2978", + "gtrdot": "\u22D7", + "gtreqless": "\u22DB", + "gtreqqless": "\u2A8C", + "gtrless": "\u2277", + "gtrsim": "\u2273", + "gvertneqq": "\u2269\uFE00", + "gvnE": "\u2269\uFE00", + "hArr": "\u21D4", + "hairsp": "\u200A", + "half": "\u00BD", + "hamilt": "\u210B", + "hardcy": "\u044A", + "harr": "\u2194", + "harrcir": "\u2948", + "harrw": "\u21AD", + "hbar": "\u210F", + "hcirc": "\u0125", + "hearts": "\u2665", + "heartsuit": "\u2665", + "hellip": "\u2026", + "hercon": "\u22B9", + "hfr": "\U0001D525", + "hksearow": "\u2925", + "hkswarow": "\u2926", + "hoarr": "\u21FF", + "homtht": "\u223B", + "hookleftarrow": "\u21A9", + "hookrightarrow": "\u21AA", + "hopf": "\U0001D559", + "horbar": "\u2015", + "hscr": "\U0001D4BD", + "hslash": "\u210F", + "hstrok": "\u0127", + "hybull": "\u2043", + "hyphen": "\u2010", + "iacute": "\u00ED", + "ic": "\u2063", + "icirc": "\u00EE", + "icy": "\u0438", + "iecy": "\u0435", + "iexcl": "\u00A1", + "iff": "\u21D4", + "ifr": "\U0001D526", + "igrave": "\u00EC", + "ii": "\u2148", + "iiiint": "\u2A0C", + "iiint": "\u222D", + "iinfin": "\u29DC", + "iiota": "\u2129", + "ijlig": "\u0133", + "imacr": "\u012B", + "image": "\u2111", + "imagline": "\u2110", + "imagpart": "\u2111", + "imath": "\u0131", + "imof": "\u22B7", + "imped": "\u01B5", + "in": "\u2208", + "incare": "\u2105", + "infin": "\u221E", + "infintie": "\u29DD", + "inodot": "\u0131", + "int": "\u222B", + "intcal": "\u22BA", + "integers": "\u2124", + "intercal": "\u22BA", + "intlarhk": "\u2A17", + "intprod": "\u2A3C", + "iocy": "\u0451", + "iogon": "\u012F", + "iopf": "\U0001D55A", + "iota": "\u03B9", + "iprod": "\u2A3C", + "iquest": "\u00BF", + "iscr": "\U0001D4BE", + "isin": "\u2208", + "isinE": "\u22F9", + "isindot": "\u22F5", + "isins": "\u22F4", + "isinsv": "\u22F3", + "isinv": "\u2208", + "it": "\u2062", + "itilde": "\u0129", + "iukcy": "\u0456", + "iuml": "\u00EF", + "jcirc": "\u0135", + "jcy": "\u0439", + "jfr": "\U0001D527", + "jmath": "\u0237", + "jopf": "\U0001D55B", + "jscr": "\U0001D4BF", + "jsercy": "\u0458", + "jukcy": "\u0454", + "kappa": "\u03BA", + "kappav": "\u03F0", + "kcedil": "\u0137", + "kcy": "\u043A", + "kfr": "\U0001D528", + "kgreen": "\u0138", + "khcy": "\u0445", + "kjcy": "\u045C", + "kopf": "\U0001D55C", + "kscr": "\U0001D4C0", + "lAarr": "\u21DA", + "lArr": "\u21D0", + "lAtail": "\u291B", + "lBarr": "\u290E", + "lE": "\u2266", + "lEg": "\u2A8B", + "lHar": "\u2962", + "lacute": "\u013A", + "laemptyv": "\u29B4", + "lagran": "\u2112", + "lambda": "\u03BB", + "lang": "\u27E8", + "langd": "\u2991", + "langle": "\u27E8", + "lap": "\u2A85", + "laquo": "\u00AB", + "larr": "\u2190", + "larrb": "\u21E4", + "larrbfs": "\u291F", + "larrfs": "\u291D", + "larrhk": "\u21A9", + "larrlp": "\u21AB", + "larrpl": "\u2939", + "larrsim": "\u2973", + "larrtl": "\u21A2", + "lat": "\u2AAB", + "latail": "\u2919", + "late": "\u2AAD", + "lates": "\u2AAD\uFE00", + "lbarr": "\u290C", + "lbbrk": "\u2772", + "lbrace": "\u007B", + "lbrack": "\u005B", + "lbrke": "\u298B", + "lbrksld": "\u298F", + "lbrkslu": "\u298D", + "lcaron": "\u013E", + "lcedil": "\u013C", + "lceil": "\u2308", + "lcub": "\u007B", + "lcy": "\u043B", + "ldca": "\u2936", + "ldquo": "\u201C", + "ldquor": "\u201E", + "ldrdhar": "\u2967", + "ldrushar": "\u294B", + "ldsh": "\u21B2", + "le": "\u2264", + "leftarrow": "\u2190", + "leftarrowtail": "\u21A2", + "leftharpoondown": "\u21BD", + "leftharpoonup": "\u21BC", + "leftleftarrows": "\u21C7", + "leftrightarrow": "\u2194", + "leftrightarrows": "\u21C6", + "leftrightharpoons": "\u21CB", + "leftrightsquigarrow": "\u21AD", + "leftthreetimes": "\u22CB", + "leg": "\u22DA", + "leq": "\u2264", + "leqq": "\u2266", + "leqslant": "\u2A7D", + "les": "\u2A7D", + "lescc": "\u2AA8", + "lesdot": "\u2A7F", + "lesdoto": "\u2A81", + "lesdotor": "\u2A83", + "lesg": "\u22DA\uFE00", + "lesges": "\u2A93", + "lessapprox": "\u2A85", + "lessdot": "\u22D6", + "lesseqgtr": "\u22DA", + "lesseqqgtr": "\u2A8B", + "lessgtr": "\u2276", + "lesssim": "\u2272", + "lfisht": "\u297C", + "lfloor": "\u230A", + "lfr": "\U0001D529", + "lg": "\u2276", + "lgE": "\u2A91", + "lhard": "\u21BD", + "lharu": "\u21BC", + "lharul": "\u296A", + "lhblk": "\u2584", + "ljcy": "\u0459", + "ll": "\u226A", + "llarr": "\u21C7", + "llcorner": "\u231E", + "llhard": "\u296B", + "lltri": "\u25FA", + "lmidot": "\u0140", + "lmoust": "\u23B0", + "lmoustache": "\u23B0", + "lnE": "\u2268", + "lnap": "\u2A89", + "lnapprox": "\u2A89", + "lne": "\u2A87", + "lneq": "\u2A87", + "lneqq": "\u2268", + "lnsim": "\u22E6", + "loang": "\u27EC", + "loarr": "\u21FD", + "lobrk": "\u27E6", + "longleftarrow": "\u27F5", + "longleftrightarrow": "\u27F7", + "longmapsto": "\u27FC", + "longrightarrow": "\u27F6", + "looparrowleft": "\u21AB", + "looparrowright": "\u21AC", + "lopar": "\u2985", + "lopf": "\U0001D55D", + "loplus": "\u2A2D", + "lotimes": "\u2A34", + "lowast": "\u2217", + "lowbar": "\u005F", + "loz": "\u25CA", + "lozenge": "\u25CA", + "lozf": "\u29EB", + "lpar": "\u0028", + "lparlt": "\u2993", + "lrarr": "\u21C6", + "lrcorner": "\u231F", + "lrhar": "\u21CB", + "lrhard": "\u296D", + "lrm": "\u200E", + "lrtri": "\u22BF", + "lsaquo": "\u2039", + "lscr": "\U0001D4C1", + "lsh": "\u21B0", + "lsim": "\u2272", + "lsime": "\u2A8D", + "lsimg": "\u2A8F", + "lsqb": "\u005B", + "lsquo": "\u2018", + "lsquor": "\u201A", + "lstrok": "\u0142", + "lt": "\u003C", + "ltcc": "\u2AA6", + "ltcir": "\u2A79", + "ltdot": "\u22D6", + "lthree": "\u22CB", + "ltimes": "\u22C9", + "ltlarr": "\u2976", + "ltquest": "\u2A7B", + "ltrPar": "\u2996", + "ltri": "\u25C3", + "ltrie": "\u22B4", + "ltrif": "\u25C2", + "lurdshar": "\u294A", + "luruhar": "\u2966", + "lvertneqq": "\u2268\uFE00", + "lvnE": "\u2268\uFE00", + "mDDot": "\u223A", + "macr": "\u00AF", + "male": "\u2642", + "malt": "\u2720", + "maltese": "\u2720", + "map": "\u21A6", + "mapsto": "\u21A6", + "mapstodown": "\u21A7", + "mapstoleft": "\u21A4", + "mapstoup": "\u21A5", + "marker": "\u25AE", + "mcomma": "\u2A29", + "mcy": "\u043C", + "mdash": "\u2014", + "measuredangle": "\u2221", + "mfr": "\U0001D52A", + "mho": "\u2127", + "micro": "\u00B5", + "mid": "\u2223", + "midast": "\u002A", + "midcir": "\u2AF0", + "middot": "\u00B7", + "minus": "\u2212", + "minusb": "\u229F", + "minusd": "\u2238", + "minusdu": "\u2A2A", + "mlcp": "\u2ADB", + "mldr": "\u2026", + "mnplus": "\u2213", + "models": "\u22A7", + "mopf": "\U0001D55E", + "mp": "\u2213", + "mscr": "\U0001D4C2", + "mstpos": "\u223E", + "mu": "\u03BC", + "multimap": "\u22B8", + "mumap": "\u22B8", + "nGg": "\u22D9\u0338", + "nGt": "\u226B\u20D2", + "nGtv": "\u226B\u0338", + "nLeftarrow": "\u21CD", + "nLeftrightarrow": "\u21CE", + "nLl": "\u22D8\u0338", + "nLt": "\u226A\u20D2", + "nLtv": "\u226A\u0338", + "nRightarrow": "\u21CF", + "nVDash": "\u22AF", + "nVdash": "\u22AE", + "nabla": "\u2207", + "nacute": "\u0144", + "nang": "\u2220\u20D2", + "nap": "\u2249", + "napE": "\u2A70\u0338", + "napid": "\u224B\u0338", + "napos": "\u0149", + "napprox": "\u2249", + "natur": "\u266E", + "natural": "\u266E", + "naturals": "\u2115", + "nbsp": "\u00A0", + "nbump": "\u224E\u0338", + "nbumpe": "\u224F\u0338", + "ncap": "\u2A43", + "ncaron": "\u0148", + "ncedil": "\u0146", + "ncong": "\u2247", + "ncongdot": "\u2A6D\u0338", + "ncup": "\u2A42", + "ncy": "\u043D", + "ndash": "\u2013", + "ne": "\u2260", + "neArr": "\u21D7", + "nearhk": "\u2924", + "nearr": "\u2197", + "nearrow": "\u2197", + "nedot": "\u2250\u0338", + "nequiv": "\u2262", + "nesear": "\u2928", + "nesim": "\u2242\u0338", + "nexist": "\u2204", + "nexists": "\u2204", + "nfr": "\U0001D52B", + "ngE": "\u2267\u0338", + "nge": "\u2271", + "ngeq": "\u2271", + "ngeqq": "\u2267\u0338", + "ngeqslant": "\u2A7E\u0338", + "nges": "\u2A7E\u0338", + "ngsim": "\u2275", + "ngt": "\u226F", + "ngtr": "\u226F", + "nhArr": "\u21CE", + "nharr": "\u21AE", + "nhpar": "\u2AF2", + "ni": "\u220B", + "nis": "\u22FC", + "nisd": "\u22FA", + "niv": "\u220B", + "njcy": "\u045A", + "nlArr": "\u21CD", + "nlE": "\u2266\u0338", + "nlarr": "\u219A", + "nldr": "\u2025", + "nle": "\u2270", + "nleftarrow": "\u219A", + "nleftrightarrow": "\u21AE", + "nleq": "\u2270", + "nleqq": "\u2266\u0338", + "nleqslant": "\u2A7D\u0338", + "nles": "\u2A7D\u0338", + "nless": "\u226E", + "nlsim": "\u2274", + "nlt": "\u226E", + "nltri": "\u22EA", + "nltrie": "\u22EC", + "nmid": "\u2224", + "nopf": "\U0001D55F", + "not": "\u00AC", + "notin": "\u2209", + "notinE": "\u22F9\u0338", + "notindot": "\u22F5\u0338", + "notinva": "\u2209", + "notinvb": "\u22F7", + "notinvc": "\u22F6", + "notni": "\u220C", + "notniva": "\u220C", + "notnivb": "\u22FE", + "notnivc": "\u22FD", + "npar": "\u2226", + "nparallel": "\u2226", + "nparsl": "\u2AFD\u20E5", + "npart": "\u2202\u0338", + "npolint": "\u2A14", + "npr": "\u2280", + "nprcue": "\u22E0", + "npre": "\u2AAF\u0338", + "nprec": "\u2280", + "npreceq": "\u2AAF\u0338", + "nrArr": "\u21CF", + "nrarr": "\u219B", + "nrarrc": "\u2933\u0338", + "nrarrw": "\u219D\u0338", + "nrightarrow": "\u219B", + "nrtri": "\u22EB", + "nrtrie": "\u22ED", + "nsc": "\u2281", + "nsccue": "\u22E1", + "nsce": "\u2AB0\u0338", + "nscr": "\U0001D4C3", + "nshortmid": "\u2224", + "nshortparallel": "\u2226", + "nsim": "\u2241", + "nsime": "\u2244", + "nsimeq": "\u2244", + "nsmid": "\u2224", + "nspar": "\u2226", + "nsqsube": "\u22E2", + "nsqsupe": "\u22E3", + "nsub": "\u2284", + "nsubE": "\u2AC5\u0338", + "nsube": "\u2288", + "nsubset": "\u2282\u20D2", + "nsubseteq": "\u2288", + "nsubseteqq": "\u2AC5\u0338", + "nsucc": "\u2281", + "nsucceq": "\u2AB0\u0338", + "nsup": "\u2285", + "nsupE": "\u2AC6\u0338", + "nsupe": "\u2289", + "nsupset": "\u2283\u20D2", + "nsupseteq": "\u2289", + "nsupseteqq": "\u2AC6\u0338", + "ntgl": "\u2279", + "ntilde": "\u00F1", + "ntlg": "\u2278", + "ntriangleleft": "\u22EA", + "ntrianglelefteq": "\u22EC", + "ntriangleright": "\u22EB", + "ntrianglerighteq": "\u22ED", + "nu": "\u03BD", + "num": "\u0023", + "numero": "\u2116", + "numsp": "\u2007", + "nvDash": "\u22AD", + "nvHarr": "\u2904", + "nvap": "\u224D\u20D2", + "nvdash": "\u22AC", + "nvge": "\u2265\u20D2", + "nvgt": "\u003E\u20D2", + "nvinfin": "\u29DE", + "nvlArr": "\u2902", + "nvle": "\u2264\u20D2", + "nvlt": "\u003C\u20D2", + "nvltrie": "\u22B4\u20D2", + "nvrArr": "\u2903", + "nvrtrie": "\u22B5\u20D2", + "nvsim": "\u223C\u20D2", + "nwArr": "\u21D6", + "nwarhk": "\u2923", + "nwarr": "\u2196", + "nwarrow": "\u2196", + "nwnear": "\u2927", + "oS": "\u24C8", + "oacute": "\u00F3", + "oast": "\u229B", + "ocir": "\u229A", + "ocirc": "\u00F4", + "ocy": "\u043E", + "odash": "\u229D", + "odblac": "\u0151", + "odiv": "\u2A38", + "odot": "\u2299", + "odsold": "\u29BC", + "oelig": "\u0153", + "ofcir": "\u29BF", + "ofr": "\U0001D52C", + "ogon": "\u02DB", + "ograve": "\u00F2", + "ogt": "\u29C1", + "ohbar": "\u29B5", + "ohm": "\u03A9", + "oint": "\u222E", + "olarr": "\u21BA", + "olcir": "\u29BE", + "olcross": "\u29BB", + "oline": "\u203E", + "olt": "\u29C0", + "omacr": "\u014D", + "omega": "\u03C9", + "omicron": "\u03BF", + "omid": "\u29B6", + "ominus": "\u2296", + "oopf": "\U0001D560", + "opar": "\u29B7", + "operp": "\u29B9", + "oplus": "\u2295", + "or": "\u2228", + "orarr": "\u21BB", + "ord": "\u2A5D", + "order": "\u2134", + "orderof": "\u2134", + "ordf": "\u00AA", + "ordm": "\u00BA", + "origof": "\u22B6", + "oror": "\u2A56", + "orslope": "\u2A57", + "orv": "\u2A5B", + "oscr": "\u2134", + "oslash": "\u00F8", + "osol": "\u2298", + "otilde": "\u00F5", + "otimes": "\u2297", + "otimesas": "\u2A36", + "ouml": "\u00F6", + "ovbar": "\u233D", + "par": "\u2225", + "para": "\u00B6", + "parallel": "\u2225", + "parsim": "\u2AF3", + "parsl": "\u2AFD", + "part": "\u2202", + "pcy": "\u043F", + "percnt": "\u0025", + "period": "\u002E", + "permil": "\u2030", + "perp": "\u22A5", + "pertenk": "\u2031", + "pfr": "\U0001D52D", + "phi": "\u03C6", + "phiv": "\u03D5", + "phmmat": "\u2133", + "phone": "\u260E", + "pi": "\u03C0", + "pitchfork": "\u22D4", + "piv": "\u03D6", + "planck": "\u210F", + "planckh": "\u210E", + "plankv": "\u210F", + "plus": "\u002B", + "plusacir": "\u2A23", + "plusb": "\u229E", + "pluscir": "\u2A22", + "plusdo": "\u2214", + "plusdu": "\u2A25", + "pluse": "\u2A72", + "plusmn": "\u00B1", + "plussim": "\u2A26", + "plustwo": "\u2A27", + "pm": "\u00B1", + "pointint": "\u2A15", + "popf": "\U0001D561", + "pound": "\u00A3", + "pr": "\u227A", + "prE": "\u2AB3", + "prap": "\u2AB7", + "prcue": "\u227C", + "pre": "\u2AAF", + "prec": "\u227A", + "precapprox": "\u2AB7", + "preccurlyeq": "\u227C", + "preceq": "\u2AAF", + "precnapprox": "\u2AB9", + "precneqq": "\u2AB5", + "precnsim": "\u22E8", + "precsim": "\u227E", + "prime": "\u2032", + "primes": "\u2119", + "prnE": "\u2AB5", + "prnap": "\u2AB9", + "prnsim": "\u22E8", + "prod": "\u220F", + "profalar": "\u232E", + "profline": "\u2312", + "profsurf": "\u2313", + "prop": "\u221D", + "propto": "\u221D", + "prsim": "\u227E", + "prurel": "\u22B0", + "pscr": "\U0001D4C5", + "psi": "\u03C8", + "puncsp": "\u2008", + "qfr": "\U0001D52E", + "qint": "\u2A0C", + "qopf": "\U0001D562", + "qprime": "\u2057", + "qscr": "\U0001D4C6", + "quaternions": "\u210D", + "quatint": "\u2A16", + "quest": "\u003F", + "questeq": "\u225F", + "quot": "\u0022", + "rAarr": "\u21DB", + "rArr": "\u21D2", + "rAtail": "\u291C", + "rBarr": "\u290F", + "rHar": "\u2964", + "race": "\u223D\u0331", + "racute": "\u0155", + "radic": "\u221A", + "raemptyv": "\u29B3", + "rang": "\u27E9", + "rangd": "\u2992", + "range": "\u29A5", + "rangle": "\u27E9", + "raquo": "\u00BB", + "rarr": "\u2192", + "rarrap": "\u2975", + "rarrb": "\u21E5", + "rarrbfs": "\u2920", + "rarrc": "\u2933", + "rarrfs": "\u291E", + "rarrhk": "\u21AA", + "rarrlp": "\u21AC", + "rarrpl": "\u2945", + "rarrsim": "\u2974", + "rarrtl": "\u21A3", + "rarrw": "\u219D", + "ratail": "\u291A", + "ratio": "\u2236", + "rationals": "\u211A", + "rbarr": "\u290D", + "rbbrk": "\u2773", + "rbrace": "\u007D", + "rbrack": "\u005D", + "rbrke": "\u298C", + "rbrksld": "\u298E", + "rbrkslu": "\u2990", + "rcaron": "\u0159", + "rcedil": "\u0157", + "rceil": "\u2309", + "rcub": "\u007D", + "rcy": "\u0440", + "rdca": "\u2937", + "rdldhar": "\u2969", + "rdquo": "\u201D", + "rdquor": "\u201D", + "rdsh": "\u21B3", + "real": "\u211C", + "realine": "\u211B", + "realpart": "\u211C", + "reals": "\u211D", + "rect": "\u25AD", + "reg": "\u00AE", + "rfisht": "\u297D", + "rfloor": "\u230B", + "rfr": "\U0001D52F", + "rhard": "\u21C1", + "rharu": "\u21C0", + "rharul": "\u296C", + "rho": "\u03C1", + "rhov": "\u03F1", + "rightarrow": "\u2192", + "rightarrowtail": "\u21A3", + "rightharpoondown": "\u21C1", + "rightharpoonup": "\u21C0", + "rightleftarrows": "\u21C4", + "rightleftharpoons": "\u21CC", + "rightrightarrows": "\u21C9", + "rightsquigarrow": "\u219D", + "rightthreetimes": "\u22CC", + "ring": "\u02DA", + "risingdotseq": "\u2253", + "rlarr": "\u21C4", + "rlhar": "\u21CC", + "rlm": "\u200F", + "rmoust": "\u23B1", + "rmoustache": "\u23B1", + "rnmid": "\u2AEE", + "roang": "\u27ED", + "roarr": "\u21FE", + "robrk": "\u27E7", + "ropar": "\u2986", + "ropf": "\U0001D563", + "roplus": "\u2A2E", + "rotimes": "\u2A35", + "rpar": "\u0029", + "rpargt": "\u2994", + "rppolint": "\u2A12", + "rrarr": "\u21C9", + "rsaquo": "\u203A", + "rscr": "\U0001D4C7", + "rsh": "\u21B1", + "rsqb": "\u005D", + "rsquo": "\u2019", + "rsquor": "\u2019", + "rthree": "\u22CC", + "rtimes": "\u22CA", + "rtri": "\u25B9", + "rtrie": "\u22B5", + "rtrif": "\u25B8", + "rtriltri": "\u29CE", + "ruluhar": "\u2968", + "rx": "\u211E", + "sacute": "\u015B", + "sbquo": "\u201A", + "sc": "\u227B", + "scE": "\u2AB4", + "scap": "\u2AB8", + "scaron": "\u0161", + "sccue": "\u227D", + "sce": "\u2AB0", + "scedil": "\u015F", + "scirc": "\u015D", + "scnE": "\u2AB6", + "scnap": "\u2ABA", + "scnsim": "\u22E9", + "scpolint": "\u2A13", + "scsim": "\u227F", + "scy": "\u0441", + "sdot": "\u22C5", + "sdotb": "\u22A1", + "sdote": "\u2A66", + "seArr": "\u21D8", + "searhk": "\u2925", + "searr": "\u2198", + "searrow": "\u2198", + "sect": "\u00A7", + "semi": "\u003B", + "seswar": "\u2929", + "setminus": "\u2216", + "setmn": "\u2216", + "sext": "\u2736", + "sfr": "\U0001D530", + "sfrown": "\u2322", + "sharp": "\u266F", + "shchcy": "\u0449", + "shcy": "\u0448", + "shortmid": "\u2223", + "shortparallel": "\u2225", + "shy": "\u00AD", + "sigma": "\u03C3", + "sigmaf": "\u03C2", + "sigmav": "\u03C2", + "sim": "\u223C", + "simdot": "\u2A6A", + "sime": "\u2243", + "simeq": "\u2243", + "simg": "\u2A9E", + "simgE": "\u2AA0", + "siml": "\u2A9D", + "simlE": "\u2A9F", + "simne": "\u2246", + "simplus": "\u2A24", + "simrarr": "\u2972", + "slarr": "\u2190", + "smallsetminus": "\u2216", + "smashp": "\u2A33", + "smeparsl": "\u29E4", + "smid": "\u2223", + "smile": "\u2323", + "smt": "\u2AAA", + "smte": "\u2AAC", + "smtes": "\u2AAC\uFE00", + "softcy": "\u044C", + "sol": "\u002F", + "solb": "\u29C4", + "solbar": "\u233F", + "sopf": "\U0001D564", + "spades": "\u2660", + "spadesuit": "\u2660", + "spar": "\u2225", + "sqcap": "\u2293", + "sqcaps": "\u2293\uFE00", + "sqcup": "\u2294", + "sqcups": "\u2294\uFE00", + "sqsub": "\u228F", + "sqsube": "\u2291", + "sqsubset": "\u228F", + "sqsubseteq": "\u2291", + "sqsup": "\u2290", + "sqsupe": "\u2292", + "sqsupset": "\u2290", + "sqsupseteq": "\u2292", + "squ": "\u25A1", + "square": "\u25A1", + "squarf": "\u25AA", + "squf": "\u25AA", + "srarr": "\u2192", + "sscr": "\U0001D4C8", + "ssetmn": "\u2216", + "ssmile": "\u2323", + "sstarf": "\u22C6", + "star": "\u2606", + "starf": "\u2605", + "straightepsilon": "\u03F5", + "straightphi": "\u03D5", + "strns": "\u00AF", + "sub": "\u2282", + "subE": "\u2AC5", + "subdot": "\u2ABD", + "sube": "\u2286", + "subedot": "\u2AC3", + "submult": "\u2AC1", + "subnE": "\u2ACB", + "subne": "\u228A", + "subplus": "\u2ABF", + "subrarr": "\u2979", + "subset": "\u2282", + "subseteq": "\u2286", + "subseteqq": "\u2AC5", + "subsetneq": "\u228A", + "subsetneqq": "\u2ACB", + "subsim": "\u2AC7", + "subsub": "\u2AD5", + "subsup": "\u2AD3", + "succ": "\u227B", + "succapprox": "\u2AB8", + "succcurlyeq": "\u227D", + "succeq": "\u2AB0", + "succnapprox": "\u2ABA", + "succneqq": "\u2AB6", + "succnsim": "\u22E9", + "succsim": "\u227F", + "sum": "\u2211", + "sung": "\u266A", + "sup": "\u2283", + "sup1": "\u00B9", + "sup2": "\u00B2", + "sup3": "\u00B3", + "supE": "\u2AC6", + "supdot": "\u2ABE", + "supdsub": "\u2AD8", + "supe": "\u2287", + "supedot": "\u2AC4", + "suphsol": "\u27C9", + "suphsub": "\u2AD7", + "suplarr": "\u297B", + "supmult": "\u2AC2", + "supnE": "\u2ACC", + "supne": "\u228B", + "supplus": "\u2AC0", + "supset": "\u2283", + "supseteq": "\u2287", + "supseteqq": "\u2AC6", + "supsetneq": "\u228B", + "supsetneqq": "\u2ACC", + "supsim": "\u2AC8", + "supsub": "\u2AD4", + "supsup": "\u2AD6", + "swArr": "\u21D9", + "swarhk": "\u2926", + "swarr": "\u2199", + "swarrow": "\u2199", + "swnwar": "\u292A", + "szlig": "\u00DF", + "target": "\u2316", + "tau": "\u03C4", + "tbrk": "\u23B4", + "tcaron": "\u0165", + "tcedil": "\u0163", + "tcy": "\u0442", + "tdot": "\u20DB", + "telrec": "\u2315", + "tfr": "\U0001D531", + "there4": "\u2234", + "therefore": "\u2234", + "theta": "\u03B8", + "thetasym": "\u03D1", + "thetav": "\u03D1", + "thickapprox": "\u2248", + "thicksim": "\u223C", + "thinsp": "\u2009", + "thkap": "\u2248", + "thksim": "\u223C", + "thorn": "\u00FE", + "tilde": "\u02DC", + "times": "\u00D7", + "timesb": "\u22A0", + "timesbar": "\u2A31", + "timesd": "\u2A30", + "tint": "\u222D", + "toea": "\u2928", + "top": "\u22A4", + "topbot": "\u2336", + "topcir": "\u2AF1", + "topf": "\U0001D565", + "topfork": "\u2ADA", + "tosa": "\u2929", + "tprime": "\u2034", + "trade": "\u2122", + "triangle": "\u25B5", + "triangledown": "\u25BF", + "triangleleft": "\u25C3", + "trianglelefteq": "\u22B4", + "triangleq": "\u225C", + "triangleright": "\u25B9", + "trianglerighteq": "\u22B5", + "tridot": "\u25EC", + "trie": "\u225C", + "triminus": "\u2A3A", + "triplus": "\u2A39", + "trisb": "\u29CD", + "tritime": "\u2A3B", + "trpezium": "\u23E2", + "tscr": "\U0001D4C9", + "tscy": "\u0446", + "tshcy": "\u045B", + "tstrok": "\u0167", + "twixt": "\u226C", + "twoheadleftarrow": "\u219E", + "twoheadrightarrow": "\u21A0", + "uArr": "\u21D1", + "uHar": "\u2963", + "uacute": "\u00FA", + "uarr": "\u2191", + "ubrcy": "\u045E", + "ubreve": "\u016D", + "ucirc": "\u00FB", + "ucy": "\u0443", + "udarr": "\u21C5", + "udblac": "\u0171", + "udhar": "\u296E", + "ufisht": "\u297E", + "ufr": "\U0001D532", + "ugrave": "\u00F9", + "uharl": "\u21BF", + "uharr": "\u21BE", + "uhblk": "\u2580", + "ulcorn": "\u231C", + "ulcorner": "\u231C", + "ulcrop": "\u230F", + "ultri": "\u25F8", + "umacr": "\u016B", + "uml": "\u00A8", + "uogon": "\u0173", + "uopf": "\U0001D566", + "uparrow": "\u2191", + "updownarrow": "\u2195", + "upharpoonleft": "\u21BF", + "upharpoonright": "\u21BE", + "uplus": "\u228E", + "upsi": "\u03C5", + "upsih": "\u03D2", + "upsilon": "\u03C5", + "upuparrows": "\u21C8", + "urcorn": "\u231D", + "urcorner": "\u231D", + "urcrop": "\u230E", + "uring": "\u016F", + "urtri": "\u25F9", + "uscr": "\U0001D4CA", + "utdot": "\u22F0", + "utilde": "\u0169", + "utri": "\u25B5", + "utrif": "\u25B4", + "uuarr": "\u21C8", + "uuml": "\u00FC", + "uwangle": "\u29A7", + "vArr": "\u21D5", + "vBar": "\u2AE8", + "vBarv": "\u2AE9", + "vDash": "\u22A8", + "vangrt": "\u299C", + "varepsilon": "\u03F5", + "varkappa": "\u03F0", + "varnothing": "\u2205", + "varphi": "\u03D5", + "varpi": "\u03D6", + "varpropto": "\u221D", + "varr": "\u2195", + "varrho": "\u03F1", + "varsigma": "\u03C2", + "varsubsetneq": "\u228A\uFE00", + "varsubsetneqq": "\u2ACB\uFE00", + "varsupsetneq": "\u228B\uFE00", + "varsupsetneqq": "\u2ACC\uFE00", + "vartheta": "\u03D1", + "vartriangleleft": "\u22B2", + "vartriangleright": "\u22B3", + "vcy": "\u0432", + "vdash": "\u22A2", + "vee": "\u2228", + "veebar": "\u22BB", + "veeeq": "\u225A", + "vellip": "\u22EE", + "verbar": "\u007C", + "vert": "\u007C", + "vfr": "\U0001D533", + "vltri": "\u22B2", + "vnsub": "\u2282\u20D2", + "vnsup": "\u2283\u20D2", + "vopf": "\U0001D567", + "vprop": "\u221D", + "vrtri": "\u22B3", + "vscr": "\U0001D4CB", + "vsubnE": "\u2ACB\uFE00", + "vsubne": "\u228A\uFE00", + "vsupnE": "\u2ACC\uFE00", + "vsupne": "\u228B\uFE00", + "vzigzag": "\u299A", + "wcirc": "\u0175", + "wedbar": "\u2A5F", + "wedge": "\u2227", + "wedgeq": "\u2259", + "weierp": "\u2118", + "wfr": "\U0001D534", + "wopf": "\U0001D568", + "wp": "\u2118", + "wr": "\u2240", + "wreath": "\u2240", + "wscr": "\U0001D4CC", + "xcap": "\u22C2", + "xcirc": "\u25EF", + "xcup": "\u22C3", + "xdtri": "\u25BD", + "xfr": "\U0001D535", + "xhArr": "\u27FA", + "xharr": "\u27F7", + "xi": "\u03BE", + "xlArr": "\u27F8", + "xlarr": "\u27F5", + "xmap": "\u27FC", + "xnis": "\u22FB", + "xodot": "\u2A00", + "xopf": "\U0001D569", + "xoplus": "\u2A01", + "xotime": "\u2A02", + "xrArr": "\u27F9", + "xrarr": "\u27F6", + "xscr": "\U0001D4CD", + "xsqcup": "\u2A06", + "xuplus": "\u2A04", + "xutri": "\u25B3", + "xvee": "\u22C1", + "xwedge": "\u22C0", + "yacute": "\u00FD", + "yacy": "\u044F", + "ycirc": "\u0177", + "ycy": "\u044B", + "yen": "\u00A5", + "yfr": "\U0001D536", + "yicy": "\u0457", + "yopf": "\U0001D56A", + "yscr": "\U0001D4CE", + "yucy": "\u044E", + "yuml": "\u00FF", + "zacute": "\u017A", + "zcaron": "\u017E", + "zcy": "\u0437", + "zdot": "\u017C", + "zeetrf": "\u2128", + "zeta": "\u03B6", + "zfr": "\U0001D537", + "zhcy": "\u0436", + "zigrarr": "\u21DD", + "zopf": "\U0001D56B", + "zscr": "\U0001D4CF", + "zwj": "\u200D", + "zwnj": "\u200C", +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/indented_code.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/indented_code.go new file mode 100644 index 00000000..dc5dce1a --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/indented_code.go @@ -0,0 +1,98 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "strings" +) + +type IndentedCodeLine struct { + Indentation int + Range Range +} + +type IndentedCode struct { + blockBase + markdown string + + RawCode []IndentedCodeLine +} + +func (b *IndentedCode) Code() (result string) { + for _, code := range b.RawCode { + result += strings.Repeat(" ", code.Indentation) + b.markdown[code.Range.Position:code.Range.End] + } + return +} + +func (b *IndentedCode) Continuation(indentation int, r Range) *continuation { + if indentation >= 4 { + return &continuation{ + Indentation: indentation - 4, + Remaining: r, + } + } + s := b.markdown[r.Position:r.End] + if strings.TrimSpace(s) == "" { + return &continuation{ + Remaining: r, + } + } + return nil +} + +func (b *IndentedCode) AddLine(indentation int, r Range) bool { + b.RawCode = append(b.RawCode, IndentedCodeLine{ + Indentation: indentation, + Range: r, + }) + return true +} + +func (b *IndentedCode) Close() { + for { + last := b.RawCode[len(b.RawCode)-1] + s := b.markdown[last.Range.Position:last.Range.End] + if strings.TrimRight(s, "\r\n") == "" { + b.RawCode = b.RawCode[:len(b.RawCode)-1] + } else { + break + } + } +} + +func (b *IndentedCode) AllowsBlockStarts() bool { + return false +} + +func indentedCodeStart(markdown string, indentation int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { + if len(unmatchedBlocks) > 0 { + if _, ok := unmatchedBlocks[len(unmatchedBlocks)-1].(*Paragraph); ok { + return nil + } + } else if len(matchedBlocks) > 0 { + if _, ok := matchedBlocks[len(matchedBlocks)-1].(*Paragraph); ok { + return nil + } + } + + if indentation < 4 { + return nil + } + + s := markdown[r.Position:r.End] + if strings.TrimSpace(s) == "" { + return nil + } + + return []Block{ + &IndentedCode{ + markdown: markdown, + RawCode: []IndentedCodeLine{{ + Indentation: indentation - 4, + Range: r, + }}, + }, + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/inlines.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/inlines.go new file mode 100644 index 00000000..a3abccef --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/inlines.go @@ -0,0 +1,664 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "container/list" + "strings" + "unicode" + "unicode/utf8" +) + +type Inline interface { + IsInline() bool +} + +type inlineBase struct{} + +func (inlineBase) IsInline() bool { return true } + +type Text struct { + inlineBase + + Text string + Range Range +} + +type CodeSpan struct { + inlineBase + + Code string +} + +type HardLineBreak struct { + inlineBase +} + +type SoftLineBreak struct { + inlineBase +} + +type InlineLinkOrImage struct { + inlineBase + + Children []Inline + + RawDestination Range + + markdown string + rawTitle string +} + +func (i *InlineLinkOrImage) Destination() string { + return Unescape(i.markdown[i.RawDestination.Position:i.RawDestination.End]) +} + +func (i *InlineLinkOrImage) Title() string { + return Unescape(i.rawTitle) +} + +type InlineLink struct { + InlineLinkOrImage +} + +type InlineImage struct { + InlineLinkOrImage +} + +type ReferenceLinkOrImage struct { + inlineBase + *ReferenceDefinition + + Children []Inline +} + +type ReferenceLink struct { + ReferenceLinkOrImage +} + +type ReferenceImage struct { + ReferenceLinkOrImage +} + +type Autolink struct { + inlineBase + + Children []Inline + + RawDestination Range + + markdown string +} + +func (i *Autolink) Destination() string { + destination := Unescape(i.markdown[i.RawDestination.Position:i.RawDestination.End]) + + if strings.HasPrefix(destination, "www") { + destination = "http://" + destination + } + + return destination +} + +type delimiterType int + +const ( + linkOpeningDelimiter delimiterType = iota + imageOpeningDelimiter +) + +type delimiter struct { + Type delimiterType + IsInactive bool + TextNode int + Range Range +} + +type inlineParser struct { + markdown string + ranges []Range + referenceDefinitions []*ReferenceDefinition + + raw string + position int + inlines []Inline + delimiterStack *list.List +} + +func newInlineParser(markdown string, ranges []Range, referenceDefinitions []*ReferenceDefinition) *inlineParser { + return &inlineParser{ + markdown: markdown, + ranges: ranges, + referenceDefinitions: referenceDefinitions, + delimiterStack: list.New(), + } +} + +func (p *inlineParser) parseBackticks() { + count := 1 + for i := p.position + 1; i < len(p.raw) && p.raw[i] == '`'; i++ { + count++ + } + opening := p.raw[p.position : p.position+count] + search := p.position + count + for search < len(p.raw) { + end := strings.Index(p.raw[search:], opening) + if end == -1 { + break + } + if search+end+count < len(p.raw) && p.raw[search+end+count] == '`' { + search += end + count + for search < len(p.raw) && p.raw[search] == '`' { + search++ + } + continue + } + code := strings.Join(strings.Fields(p.raw[p.position+count:search+end]), " ") + p.position = search + end + count + p.inlines = append(p.inlines, &CodeSpan{ + Code: code, + }) + return + } + p.position += len(opening) + absPos := relativeToAbsolutePosition(p.ranges, p.position-len(opening)) + p.inlines = append(p.inlines, &Text{ + Text: opening, + Range: Range{absPos, absPos + len(opening)}, + }) +} + +func (p *inlineParser) parseLineEnding() { + if p.position >= 1 && p.raw[p.position-1] == '\t' { + p.inlines = append(p.inlines, &HardLineBreak{}) + } else if p.position >= 2 && p.raw[p.position-1] == ' ' && (p.raw[p.position-2] == '\t' || p.raw[p.position-1] == ' ') { + p.inlines = append(p.inlines, &HardLineBreak{}) + } else { + p.inlines = append(p.inlines, &SoftLineBreak{}) + } + p.position++ + if p.position < len(p.raw) && p.raw[p.position] == '\n' { + p.position++ + } +} + +func (p *inlineParser) parseEscapeCharacter() { + if p.position+1 < len(p.raw) && isEscapableByte(p.raw[p.position+1]) { + absPos := relativeToAbsolutePosition(p.ranges, p.position+1) + p.inlines = append(p.inlines, &Text{ + Text: string(p.raw[p.position+1]), + Range: Range{absPos, absPos + len(string(p.raw[p.position+1]))}, + }) + p.position += 2 + } else { + absPos := relativeToAbsolutePosition(p.ranges, p.position) + p.inlines = append(p.inlines, &Text{ + Text: `\`, + Range: Range{absPos, absPos + 1}, + }) + p.position++ + } +} + +func (p *inlineParser) parseText() { + if next := strings.IndexAny(p.raw[p.position:], "\r\n\\`&![]wW:"); next == -1 { + absPos := relativeToAbsolutePosition(p.ranges, p.position) + p.inlines = append(p.inlines, &Text{ + Text: strings.TrimRightFunc(p.raw[p.position:], isWhitespace), + Range: Range{absPos, absPos + len(p.raw[p.position:])}, + }) + p.position = len(p.raw) + } else { + absPos := relativeToAbsolutePosition(p.ranges, p.position) + if p.raw[p.position+next] == '\r' || p.raw[p.position+next] == '\n' { + s := strings.TrimRightFunc(p.raw[p.position:p.position+next], isWhitespace) + p.inlines = append(p.inlines, &Text{ + Text: s, + Range: Range{absPos, absPos + len(s)}, + }) + } else { + if next == 0 { + // Always read at least one character since 'w', 'W', and ':' may not actually match another + // type of node + next = 1 + } + + p.inlines = append(p.inlines, &Text{ + Text: p.raw[p.position : p.position+next], + Range: Range{absPos, absPos + next}, + }) + } + p.position += next + } +} + +func (p *inlineParser) parseLinkOrImageDelimiter() { + absPos := relativeToAbsolutePosition(p.ranges, p.position) + if p.raw[p.position] == '[' { + p.inlines = append(p.inlines, &Text{ + Text: "[", + Range: Range{absPos, absPos + 1}, + }) + p.delimiterStack.PushBack(&delimiter{ + Type: linkOpeningDelimiter, + TextNode: len(p.inlines) - 1, + Range: Range{p.position, p.position + 1}, + }) + p.position++ + } else if p.raw[p.position] == '!' && p.position+1 < len(p.raw) && p.raw[p.position+1] == '[' { + p.inlines = append(p.inlines, &Text{ + Text: "![", + Range: Range{absPos, absPos + 2}, + }) + p.delimiterStack.PushBack(&delimiter{ + Type: imageOpeningDelimiter, + TextNode: len(p.inlines) - 1, + Range: Range{p.position, p.position + 2}, + }) + p.position += 2 + } else { + p.inlines = append(p.inlines, &Text{ + Text: "!", + Range: Range{absPos, absPos + 1}, + }) + p.position++ + } +} + +func (p *inlineParser) peekAtInlineLinkDestinationAndTitle(position int, isImage bool) (destination, title Range, end int, ok bool) { + if position >= len(p.raw) || p.raw[position] != '(' { + return + } + position++ + + destinationStart := nextNonWhitespace(p.raw, position) + if destinationStart >= len(p.raw) { + return + } else if p.raw[destinationStart] == ')' { + return Range{destinationStart, destinationStart}, Range{destinationStart, destinationStart}, destinationStart + 1, true + } + + destination, end, ok = parseLinkDestination(p.raw, destinationStart) + if !ok { + return + } + position = end + + if isImage && position < len(p.raw) && isWhitespaceByte(p.raw[position]) { + dimensionsStart := nextNonWhitespace(p.raw, position) + if dimensionsStart >= len(p.raw) { + return + } + + if p.raw[dimensionsStart] == '=' { + // Read optional image dimensions even if we don't use them + _, end, ok = parseImageDimensions(p.raw, dimensionsStart) + if !ok { + return + } + + position = end + } + } + + if position < len(p.raw) && isWhitespaceByte(p.raw[position]) { + titleStart := nextNonWhitespace(p.raw, position) + if titleStart >= len(p.raw) { + return + } else if p.raw[titleStart] == ')' { + return destination, Range{titleStart, titleStart}, titleStart + 1, true + } + + if p.raw[titleStart] == '"' || p.raw[titleStart] == '\'' || p.raw[titleStart] == '(' { + title, end, ok = parseLinkTitle(p.raw, titleStart) + if !ok { + return + } + position = end + } + } + + closingPosition := nextNonWhitespace(p.raw, position) + if closingPosition >= len(p.raw) || p.raw[closingPosition] != ')' { + return Range{}, Range{}, 0, false + } + + return destination, title, closingPosition + 1, true +} + +func (p *inlineParser) referenceDefinition(label string) *ReferenceDefinition { + clean := strings.Join(strings.Fields(label), " ") + for _, d := range p.referenceDefinitions { + if strings.EqualFold(clean, strings.Join(strings.Fields(d.Label()), " ")) { + return d + } + } + return nil +} + +func (p *inlineParser) lookForLinkOrImage() { + for element := p.delimiterStack.Back(); element != nil; element = element.Prev() { + d := element.Value.(*delimiter) + if d.Type != imageOpeningDelimiter && d.Type != linkOpeningDelimiter { + continue + } + if d.IsInactive { + p.delimiterStack.Remove(element) + break + } + + isImage := d.Type == imageOpeningDelimiter + + var inline Inline + + if destination, title, next, ok := p.peekAtInlineLinkDestinationAndTitle(p.position+1, isImage); ok { + destinationMarkdownPosition := relativeToAbsolutePosition(p.ranges, destination.Position) + linkOrImage := InlineLinkOrImage{ + Children: append([]Inline(nil), p.inlines[d.TextNode+1:]...), + RawDestination: Range{destinationMarkdownPosition, destinationMarkdownPosition + destination.End - destination.Position}, + markdown: p.markdown, + rawTitle: p.raw[title.Position:title.End], + } + if d.Type == imageOpeningDelimiter { + inline = &InlineImage{linkOrImage} + } else { + inline = &InlineLink{linkOrImage} + } + p.position = next + } else { + referenceLabel := "" + label, next, hasLinkLabel := parseLinkLabel(p.raw, p.position+1) + if hasLinkLabel && label.End > label.Position { + referenceLabel = p.raw[label.Position:label.End] + } else { + referenceLabel = p.raw[d.Range.End:p.position] + if !hasLinkLabel { + next = p.position + 1 + } + } + if referenceLabel != "" { + if reference := p.referenceDefinition(referenceLabel); reference != nil { + linkOrImage := ReferenceLinkOrImage{ + ReferenceDefinition: reference, + Children: append([]Inline(nil), p.inlines[d.TextNode+1:]...), + } + if d.Type == imageOpeningDelimiter { + inline = &ReferenceImage{linkOrImage} + } else { + inline = &ReferenceLink{linkOrImage} + } + p.position = next + } + } + } + + if inline != nil { + if d.Type == imageOpeningDelimiter { + p.inlines = append(p.inlines[:d.TextNode], inline) + } else { + p.inlines = append(p.inlines[:d.TextNode], inline) + for element := element.Prev(); element != nil; element = element.Prev() { + if d := element.Value.(*delimiter); d.Type == linkOpeningDelimiter { + d.IsInactive = true + } + } + } + p.delimiterStack.Remove(element) + return + } else { + p.delimiterStack.Remove(element) + break + } + } + absPos := relativeToAbsolutePosition(p.ranges, p.position) + p.inlines = append(p.inlines, &Text{ + Text: "]", + Range: Range{absPos, absPos + 1}, + }) + p.position++ +} + +func CharacterReference(ref string) string { + if ref == "" { + return "" + } + if ref[0] == '#' { + if len(ref) < 2 { + return "" + } + n := 0 + if ref[1] == 'X' || ref[1] == 'x' { + if len(ref) < 3 { + return "" + } + for i := 2; i < len(ref); i++ { + if i > 9 { + return "" + } + d := ref[i] + switch { + case d >= '0' && d <= '9': + n = n*16 + int(d-'0') + case d >= 'a' && d <= 'f': + n = n*16 + 10 + int(d-'a') + case d >= 'A' && d <= 'F': + n = n*16 + 10 + int(d-'A') + default: + return "" + } + } + } else { + for i := 1; i < len(ref); i++ { + if i > 8 || ref[i] < '0' || ref[i] > '9' { + return "" + } + n = n*10 + int(ref[i]-'0') + } + } + c := rune(n) + if c == '\u0000' || !utf8.ValidRune(c) { + return string(unicode.ReplacementChar) + } + return string(c) + } + if entity, ok := htmlEntities[ref]; ok { + return entity + } + return "" +} + +func (p *inlineParser) parseCharacterReference() { + absPos := relativeToAbsolutePosition(p.ranges, p.position) + p.position++ + if semicolon := strings.IndexByte(p.raw[p.position:], ';'); semicolon == -1 { + p.inlines = append(p.inlines, &Text{ + Text: "&", + Range: Range{absPos, absPos + 1}, + }) + } else if s := CharacterReference(p.raw[p.position : p.position+semicolon]); s != "" { + p.position += semicolon + 1 + p.inlines = append(p.inlines, &Text{ + Text: s, + Range: Range{absPos, absPos + len(s)}, + }) + } else { + p.inlines = append(p.inlines, &Text{ + Text: "&", + Range: Range{absPos, absPos + 1}, + }) + } +} + +func (p *inlineParser) parseAutolink(c rune) bool { + for element := p.delimiterStack.Back(); element != nil; element = element.Prev() { + d := element.Value.(*delimiter) + if !d.IsInactive { + return false + } + } + + var link Range + if c == ':' { + var ok bool + link, ok = parseURLAutolink(p.raw, p.position) + + if !ok { + return false + } + + // Since the current position is at the colon, we have to rewind the parsing slightly so that + // we don't duplicate the URL scheme + rewind := strings.Index(p.raw[link.Position:link.End], ":") + if rewind != -1 { + lastInline := p.inlines[len(p.inlines)-1] + lastText, ok := lastInline.(*Text) + + if !ok { + // This should never occur since parseURLAutolink will only return a non-empty value + // when the previous text ends in a valid URL protocol which would mean that the previous + // node is a Text node + return false + } + + p.inlines = p.inlines[0 : len(p.inlines)-1] + p.inlines = append(p.inlines, &Text{ + Text: lastText.Text[:len(lastText.Text)-rewind], + Range: Range{lastText.Range.Position, lastText.Range.End - rewind}, + }) + p.position -= rewind + } + } else if c == 'w' || c == 'W' { + var ok bool + link, ok = parseWWWAutolink(p.raw, p.position) + + if !ok { + return false + } + } + + linkMarkdownPosition := relativeToAbsolutePosition(p.ranges, link.Position) + linkRange := Range{linkMarkdownPosition, linkMarkdownPosition + link.End - link.Position} + + p.inlines = append(p.inlines, &Autolink{ + Children: []Inline{ + &Text{ + Text: p.raw[link.Position:link.End], + Range: linkRange, + }, + }, + RawDestination: linkRange, + markdown: p.markdown, + }) + p.position += (link.End - link.Position) + + return true +} + +func (p *inlineParser) Parse() []Inline { + for _, r := range p.ranges { + p.raw += p.markdown[r.Position:r.End] + } + + for p.position < len(p.raw) { + c, _ := utf8.DecodeRuneInString(p.raw[p.position:]) + + switch c { + case '\r', '\n': + p.parseLineEnding() + case '\\': + p.parseEscapeCharacter() + case '`': + p.parseBackticks() + case '&': + p.parseCharacterReference() + case '!', '[': + p.parseLinkOrImageDelimiter() + case ']': + p.lookForLinkOrImage() + case 'w', 'W', ':': + matched := p.parseAutolink(c) + + if !matched { + p.parseText() + } + default: + p.parseText() + } + } + + return p.inlines +} + +func ParseInlines(markdown string, ranges []Range, referenceDefinitions []*ReferenceDefinition) (inlines []Inline) { + return newInlineParser(markdown, ranges, referenceDefinitions).Parse() +} + +func MergeInlineText(inlines []Inline) []Inline { + var ret []Inline + for i, v := range inlines { + // always add first node + if i == 0 { + ret = append(ret, v) + continue + } + // not a text node? nothing to merge + text, ok := v.(*Text) + if !ok { + ret = append(ret, v) + continue + } + // previous node is not a text node? nothing to merge + prevText, ok := ret[len(ret)-1].(*Text) + if !ok { + ret = append(ret, v) + continue + } + // previous node is not right before this one + if prevText.Range.End != text.Range.Position { + ret = append(ret, v) + continue + } + // we have two consecutive text nodes + ret[len(ret)-1] = &Text{ + Text: prevText.Text + text.Text, + Range: Range{prevText.Range.Position, text.Range.End}, + } + } + return ret +} + +func Unescape(markdown string) string { + ret := "" + + position := 0 + for position < len(markdown) { + c, cSize := utf8.DecodeRuneInString(markdown[position:]) + + switch c { + case '\\': + if position+1 < len(markdown) && isEscapableByte(markdown[position+1]) { + ret += string(markdown[position+1]) + position += 2 + } else { + ret += `\` + position++ + } + case '&': + position++ + if semicolon := strings.IndexByte(markdown[position:], ';'); semicolon == -1 { + ret += "&" + } else if s := CharacterReference(markdown[position : position+semicolon]); s != "" { + position += semicolon + 1 + ret += s + } else { + ret += "&" + } + default: + ret += string(c) + position += cSize + } + } + + return ret +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/inspect.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/inspect.go new file mode 100644 index 00000000..80b5bc24 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/inspect.go @@ -0,0 +1,78 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +// Inspect traverses the markdown tree in depth-first order. If f returns true, Inspect invokes f +// recursively for each child of the block or inline, followed by a call of f(nil). +func Inspect(markdown string, f func(interface{}) bool) { + document, referenceDefinitions := Parse(markdown) + InspectBlock(document, func(block Block) bool { + if !f(block) { + return false + } + switch v := block.(type) { + case *Paragraph: + for _, inline := range MergeInlineText(v.ParseInlines(referenceDefinitions)) { + InspectInline(inline, func(inline Inline) bool { + return f(inline) + }) + } + } + return true + }) +} + +// InspectBlock traverses the blocks in depth-first order, starting with block. If f returns true, +// InspectBlock invokes f recursively for each child of the block, followed by a call of f(nil). +func InspectBlock(block Block, f func(Block) bool) { + if !f(block) { + return + } + switch v := block.(type) { + case *Document: + for _, child := range v.Children { + InspectBlock(child, f) + } + case *List: + for _, child := range v.Children { + InspectBlock(child, f) + } + case *ListItem: + for _, child := range v.Children { + InspectBlock(child, f) + } + case *BlockQuote: + for _, child := range v.Children { + InspectBlock(child, f) + } + } + f(nil) +} + +// InspectInline traverses the blocks in depth-first order, starting with block. If f returns true, +// InspectInline invokes f recursively for each child of the block, followed by a call of f(nil). +func InspectInline(inline Inline, f func(Inline) bool) { + if !f(inline) { + return + } + switch v := inline.(type) { + case *InlineImage: + for _, child := range v.Children { + InspectInline(child, f) + } + case *InlineLink: + for _, child := range v.Children { + InspectInline(child, f) + } + case *ReferenceImage: + for _, child := range v.Children { + InspectInline(child, f) + } + case *ReferenceLink: + for _, child := range v.Children { + InspectInline(child, f) + } + } + f(nil) +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/lines.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/lines.go new file mode 100644 index 00000000..a38b5164 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/lines.go @@ -0,0 +1,27 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +type Line struct { + Range +} + +func ParseLines(markdown string) (lines []Line) { + lineStartPosition := 0 + isAfterCarriageReturn := false + for position, r := range markdown { + if r == '\n' { + lines = append(lines, Line{Range{lineStartPosition, position + 1}}) + lineStartPosition = position + 1 + } else if isAfterCarriageReturn { + lines = append(lines, Line{Range{lineStartPosition, position}}) + lineStartPosition = position + } + isAfterCarriageReturn = r == '\r' + } + if lineStartPosition < len(markdown) { + lines = append(lines, Line{Range{lineStartPosition, len(markdown)}}) + } + return +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/links.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/links.go new file mode 100644 index 00000000..9f3128c4 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/links.go @@ -0,0 +1,184 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "unicode/utf8" +) + +func parseLinkDestination(markdown string, position int) (raw Range, next int, ok bool) { + if position >= len(markdown) { + return + } + + if markdown[position] == '<' { + isEscaped := false + + for offset, c := range []byte(markdown[position+1:]) { + if isEscaped { + isEscaped = false + if isEscapableByte(c) { + continue + } + } + + if c == '\\' { + isEscaped = true + } else if c == '<' { + break + } else if c == '>' { + return Range{position + 1, position + 1 + offset}, position + 1 + offset + 1, true + } else if isWhitespaceByte(c) { + break + } + } + } + + openCount := 0 + isEscaped := false + for offset, c := range []byte(markdown[position:]) { + if isEscaped { + isEscaped = false + if isEscapableByte(c) { + continue + } + } + + switch c { + case '\\': + isEscaped = true + case '(': + openCount++ + case ')': + if openCount < 1 { + return Range{position, position + offset}, position + offset, true + } + openCount-- + default: + if isWhitespaceByte(c) { + return Range{position, position + offset}, position + offset, true + } + } + } + return Range{position, len(markdown)}, len(markdown), true +} + +func parseLinkTitle(markdown string, position int) (raw Range, next int, ok bool) { + if position >= len(markdown) { + return + } + + originalPosition := position + + var closer byte + switch markdown[position] { + case '"', '\'': + closer = markdown[position] + case '(': + closer = ')' + default: + return + } + position++ + + for position < len(markdown) { + switch markdown[position] { + case '\\': + position++ + if position < len(markdown) && isEscapableByte(markdown[position]) { + position++ + } + case closer: + return Range{originalPosition + 1, position}, position + 1, true + default: + position++ + } + } + + return +} + +func parseLinkLabel(markdown string, position int) (raw Range, next int, ok bool) { + if position >= len(markdown) || markdown[position] != '[' { + return + } + + originalPosition := position + position++ + + for position < len(markdown) { + switch markdown[position] { + case '\\': + position++ + if position < len(markdown) && isEscapableByte(markdown[position]) { + position++ + } + case '[': + return + case ']': + if position-originalPosition >= 1000 && utf8.RuneCountInString(markdown[originalPosition:position]) >= 1000 { + return + } + return Range{originalPosition + 1, position}, position + 1, true + default: + position++ + } + } + + return +} + +// As a non-standard feature, we allow image links to specify dimensions of the image by adding "=WIDTHxHEIGHT" +// after the image destination but before the image title like ![alt](http://example.com/image.png =100x200 "title"). +// Both width and height are optional, but at least one of them must be specified. +func parseImageDimensions(markdown string, position int) (raw Range, next int, ok bool) { + if position >= len(markdown) { + return + } + + originalPosition := position + + // Read = + position += 1 + if position >= len(markdown) { + return + } + + // Read width + hasWidth := false + for isNumericByte(markdown[position]) { + hasWidth = true + position += 1 + } + + // Look for early end of dimensions + if isWhitespaceByte(markdown[position]) || markdown[position] == ')' { + return Range{originalPosition, position - 1}, position, true + } + + // Read the x + if markdown[position] != 'x' && markdown[position] != 'X' { + return + } + position += 1 + + // Read height + hasHeight := false + for isNumericByte(markdown[position]) { + hasHeight = true + position += 1 + } + + // Make sure the there's no trailing characters + if !isWhitespaceByte(markdown[position]) && markdown[position] != ')' { + return + } + + if !hasWidth && !hasHeight { + // At least one of width or height is required + return + } + + return Range{originalPosition, position - 1}, position, true +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/list.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/list.go new file mode 100644 index 00000000..aea71156 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/list.go @@ -0,0 +1,220 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "strings" +) + +type ListItem struct { + blockBase + markdown string + hasTrailingBlankLine bool + hasBlankLineBetweenChildren bool + + Indentation int + Children []Block +} + +func (b *ListItem) Continuation(indentation int, r Range) *continuation { + s := b.markdown[r.Position:r.End] + if strings.TrimSpace(s) == "" { + if b.Children == nil { + return nil + } + return &continuation{ + Remaining: r, + } + } + if indentation < b.Indentation { + return nil + } + return &continuation{ + Indentation: indentation - b.Indentation, + Remaining: r, + } +} + +func (b *ListItem) AddChild(openBlocks []Block) []Block { + b.Children = append(b.Children, openBlocks[0]) + if b.hasTrailingBlankLine { + b.hasBlankLineBetweenChildren = true + } + b.hasTrailingBlankLine = false + return openBlocks +} + +func (b *ListItem) AddLine(indentation int, r Range) bool { + isBlank := strings.TrimSpace(b.markdown[r.Position:r.End]) == "" + if isBlank { + b.hasTrailingBlankLine = true + } + return false +} + +func (b *ListItem) HasTrailingBlankLine() bool { + return b.hasTrailingBlankLine || (len(b.Children) > 0 && b.Children[len(b.Children)-1].HasTrailingBlankLine()) +} + +func (b *ListItem) isLoose() bool { + if b.hasBlankLineBetweenChildren { + return true + } + for i, child := range b.Children { + if i < len(b.Children)-1 && child.HasTrailingBlankLine() { + return true + } + } + return false +} + +type List struct { + blockBase + markdown string + hasTrailingBlankLine bool + hasBlankLineBetweenChildren bool + + IsLoose bool + IsOrdered bool + OrderedStart int + BulletOrDelimiter byte + Children []*ListItem +} + +func (b *List) Continuation(indentation int, r Range) *continuation { + s := b.markdown[r.Position:r.End] + if strings.TrimSpace(s) == "" { + return &continuation{ + Remaining: r, + } + } + return &continuation{ + Indentation: indentation, + Remaining: r, + } +} + +func (b *List) AddChild(openBlocks []Block) []Block { + if item, ok := openBlocks[0].(*ListItem); ok { + b.Children = append(b.Children, item) + if b.hasTrailingBlankLine { + b.hasBlankLineBetweenChildren = true + } + b.hasTrailingBlankLine = false + return openBlocks + } else if list, ok := openBlocks[0].(*List); ok { + if len(list.Children) == 1 && list.IsOrdered == b.IsOrdered && list.BulletOrDelimiter == b.BulletOrDelimiter { + return b.AddChild(openBlocks[1:]) + } + } + return nil +} + +func (b *List) AddLine(indentation int, r Range) bool { + isBlank := strings.TrimSpace(b.markdown[r.Position:r.End]) == "" + if isBlank { + b.hasTrailingBlankLine = true + } + return false +} + +func (b *List) HasTrailingBlankLine() bool { + return b.hasTrailingBlankLine || (len(b.Children) > 0 && b.Children[len(b.Children)-1].HasTrailingBlankLine()) +} + +func (b *List) isLoose() bool { + if b.hasBlankLineBetweenChildren { + return true + } + for i, child := range b.Children { + if child.isLoose() || (i < len(b.Children)-1 && child.HasTrailingBlankLine()) { + return true + } + } + return false +} + +func (b *List) Close() { + b.IsLoose = b.isLoose() +} + +func parseListMarker(markdown string, r Range) (success, isOrdered bool, orderedStart int, bulletOrDelimiter byte, markerWidth int, remaining Range) { + digits := 0 + n := 0 + for i := r.Position; i < r.End && markdown[i] >= '0' && markdown[i] <= '9'; i++ { + digits++ + n = n*10 + int(markdown[i]-'0') + } + if digits > 0 { + if digits > 9 || r.Position+digits >= r.End { + return + } + next := markdown[r.Position+digits] + if next != '.' && next != ')' { + return + } + return true, true, n, next, digits + 1, Range{r.Position + digits + 1, r.End} + } + if r.Position >= r.End { + return + } + next := markdown[r.Position] + if next != '-' && next != '+' && next != '*' { + return + } + return true, false, 0, next, 1, Range{r.Position + 1, r.End} +} + +func listStart(markdown string, indent int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block { + afterList := false + if len(matchedBlocks) > 0 { + _, afterList = matchedBlocks[len(matchedBlocks)-1].(*List) + } + if !afterList && indent > 3 { + return nil + } + + success, isOrdered, orderedStart, bulletOrDelimiter, markerWidth, remaining := parseListMarker(markdown, r) + if !success { + return nil + } + + isBlank := strings.TrimSpace(markdown[remaining.Position:remaining.End]) == "" + if len(matchedBlocks) > 0 && len(unmatchedBlocks) == 0 { + if _, ok := matchedBlocks[len(matchedBlocks)-1].(*Paragraph); ok { + if isBlank || (isOrdered && orderedStart != 1) { + return nil + } + } + } + + indentAfterMarker, indentBytesAfterMarker := countIndentation(markdown, remaining) + if !isBlank && indentAfterMarker < 1 { + return nil + } + + remaining = Range{remaining.Position + indentBytesAfterMarker, remaining.End} + consumedIndentAfterMarker := indentAfterMarker + if isBlank || indentAfterMarker >= 5 { + consumedIndentAfterMarker = 1 + } + + listItem := &ListItem{ + markdown: markdown, + Indentation: indent + markerWidth + consumedIndentAfterMarker, + } + list := &List{ + markdown: markdown, + IsOrdered: isOrdered, + OrderedStart: orderedStart, + BulletOrDelimiter: bulletOrDelimiter, + Children: []*ListItem{listItem}, + } + ret := []Block{list, listItem} + if descendants := blockStartOrParagraph(markdown, indentAfterMarker-consumedIndentAfterMarker, remaining, nil, nil); descendants != nil { + listItem.Children = append(listItem.Children, descendants[0]) + ret = append(ret, descendants...) + } + return ret +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/markdown.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/markdown.go new file mode 100644 index 00000000..57b10f3f --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/markdown.go @@ -0,0 +1,148 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +// This package implements a parser for the subset of the CommonMark spec necessary for us to do +// server-side processing. It is not a full implementation and lacks many features. But it is +// complete enough to efficiently and accurately allow us to do what we need to like rewrite image +// URLs for proxying. +package markdown + +import ( + "strings" +) + +func isEscapable(c rune) bool { + return c > ' ' && (c < '0' || (c > '9' && (c < 'A' || (c > 'Z' && (c < 'a' || (c > 'z' && c <= '~')))))) +} + +func isEscapableByte(c byte) bool { + return isEscapable(rune(c)) +} + +func isWhitespace(c rune) bool { + switch c { + case ' ', '\t', '\n', '\u000b', '\u000c', '\r': + return true + default: + return false + } +} + +func isWhitespaceByte(c byte) bool { + return isWhitespace(rune(c)) +} + +func isNumeric(c rune) bool { + return c >= '0' && c <= '9' +} + +func isNumericByte(c byte) bool { + return isNumeric(rune(c)) +} + +func isHex(c rune) bool { + return isNumeric(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') +} + +func isHexByte(c byte) bool { + return isHex(rune(c)) +} + +func isAlphanumeric(c rune) bool { + return isNumeric(c) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isAlphanumericByte(c byte) bool { + return isAlphanumeric(rune(c)) +} + +func nextNonWhitespace(markdown string, position int) int { + for offset, c := range []byte(markdown[position:]) { + if !isWhitespaceByte(c) { + return position + offset + } + } + return len(markdown) +} + +func nextLine(markdown string, position int) (linePosition int, skippedNonWhitespace bool) { + for i := position; i < len(markdown); i++ { + c := markdown[i] + if c == '\r' { + if i+1 < len(markdown) && markdown[i+1] == '\n' { + return i + 2, skippedNonWhitespace + } + return i + 1, skippedNonWhitespace + } else if c == '\n' { + return i + 1, skippedNonWhitespace + } else if !isWhitespaceByte(c) { + skippedNonWhitespace = true + } + } + return len(markdown), skippedNonWhitespace +} + +func countIndentation(markdown string, r Range) (spaces, bytes int) { + for i := r.Position; i < r.End; i++ { + if markdown[i] == ' ' { + spaces++ + bytes++ + } else if markdown[i] == '\t' { + spaces += 4 + bytes++ + } else { + break + } + } + return +} + +func trimLeftSpace(markdown string, r Range) Range { + s := markdown[r.Position:r.End] + trimmed := strings.TrimLeftFunc(s, isWhitespace) + return Range{r.Position, r.End - (len(s) - len(trimmed))} +} + +func trimRightSpace(markdown string, r Range) Range { + s := markdown[r.Position:r.End] + trimmed := strings.TrimRightFunc(s, isWhitespace) + return Range{r.Position, r.End - (len(s) - len(trimmed))} +} + +func relativeToAbsolutePosition(ranges []Range, position int) int { + rem := position + for _, r := range ranges { + l := r.End - r.Position + if rem < l { + return r.Position + rem + } + rem -= l + } + if len(ranges) == 0 { + return 0 + } + return ranges[len(ranges)-1].End +} + +func trimBytesFromRanges(ranges []Range, bytes int) (result []Range) { + rem := bytes + for _, r := range ranges { + if rem == 0 { + result = append(result, r) + continue + } + l := r.End - r.Position + if rem < l { + result = append(result, Range{r.Position + rem, r.End}) + rem = 0 + continue + } + rem -= l + } + return +} + +func Parse(markdown string) (*Document, []*ReferenceDefinition) { + lines := ParseLines(markdown) + return ParseBlocks(markdown, lines) +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/paragraph.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/paragraph.go new file mode 100644 index 00000000..6a40fdf8 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/paragraph.go @@ -0,0 +1,71 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +import ( + "strings" +) + +type Paragraph struct { + blockBase + markdown string + + Text []Range + ReferenceDefinitions []*ReferenceDefinition +} + +func (b *Paragraph) ParseInlines(referenceDefinitions []*ReferenceDefinition) []Inline { + return ParseInlines(b.markdown, b.Text, referenceDefinitions) +} + +func (b *Paragraph) Continuation(indentation int, r Range) *continuation { + s := b.markdown[r.Position:r.End] + if strings.TrimSpace(s) == "" { + return nil + } + return &continuation{ + Indentation: indentation, + Remaining: r, + } +} + +func (b *Paragraph) Close() { + for { + for i := 0; i < len(b.Text); i++ { + b.Text[i] = trimLeftSpace(b.markdown, b.Text[i]) + if b.Text[i].Position < b.Text[i].End { + break + } + } + + if len(b.Text) == 0 || b.Text[0].Position < b.Text[0].End && b.markdown[b.Text[0].Position] != '[' { + break + } + + definition, remaining := parseReferenceDefinition(b.markdown, b.Text) + if definition == nil { + break + } + b.ReferenceDefinitions = append(b.ReferenceDefinitions, definition) + b.Text = remaining + } + + for i := len(b.Text) - 1; i >= 0; i-- { + b.Text[i] = trimRightSpace(b.markdown, b.Text[i]) + if b.Text[i].Position < b.Text[i].End { + break + } + } +} + +func newParagraph(markdown string, r Range) *Paragraph { + s := markdown[r.Position:r.End] + if strings.TrimSpace(s) == "" { + return nil + } + return &Paragraph{ + markdown: markdown, + Text: []Range{r}, + } +} diff --git a/vendor/github.com/mattermost/mattermost-server/utils/markdown/reference_definition.go b/vendor/github.com/mattermost/mattermost-server/utils/markdown/reference_definition.go new file mode 100644 index 00000000..e2d0be35 --- /dev/null +++ b/vendor/github.com/mattermost/mattermost-server/utils/markdown/reference_definition.go @@ -0,0 +1,75 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package markdown + +type ReferenceDefinition struct { + RawDestination Range + + markdown string + rawLabel string + rawTitle string +} + +func (d *ReferenceDefinition) Destination() string { + return Unescape(d.markdown[d.RawDestination.Position:d.RawDestination.End]) +} + +func (d *ReferenceDefinition) Label() string { + return d.rawLabel +} + +func (d *ReferenceDefinition) Title() string { + return Unescape(d.rawTitle) +} + +func parseReferenceDefinition(markdown string, ranges []Range) (*ReferenceDefinition, []Range) { + raw := "" + for _, r := range ranges { + raw += markdown[r.Position:r.End] + } + + label, next, ok := parseLinkLabel(raw, 0) + if !ok { + return nil, nil + } + position := next + + if position >= len(raw) || raw[position] != ':' { + return nil, nil + } + position++ + + destination, next, ok := parseLinkDestination(raw, nextNonWhitespace(raw, position)) + if !ok { + return nil, nil + } + position = next + + absoluteDestination := relativeToAbsolutePosition(ranges, destination.Position) + ret := &ReferenceDefinition{ + RawDestination: Range{absoluteDestination, absoluteDestination + destination.End - destination.Position}, + markdown: markdown, + rawLabel: raw[label.Position:label.End], + } + + if position < len(raw) && isWhitespaceByte(raw[position]) { + title, next, ok := parseLinkTitle(raw, nextNonWhitespace(raw, position)) + if !ok { + if nextLine, skippedNonWhitespace := nextLine(raw, position); !skippedNonWhitespace { + return ret, trimBytesFromRanges(ranges, nextLine) + } + return nil, nil + } + if nextLine, skippedNonWhitespace := nextLine(raw, next); !skippedNonWhitespace { + ret.rawTitle = raw[title.Position:title.End] + return ret, trimBytesFromRanges(ranges, nextLine) + } + } + + if nextLine, skippedNonWhitespace := nextLine(raw, position); !skippedNonWhitespace { + return ret, trimBytesFromRanges(ranges, nextLine) + } + + return nil, nil +} diff --git a/vendor/github.com/mattermost/platform/LICENSE.txt b/vendor/github.com/mattermost/platform/LICENSE.txt deleted file mode 100644 index ead98cf0..00000000 --- a/vendor/github.com/mattermost/platform/LICENSE.txt +++ /dev/null @@ -1,897 +0,0 @@ -Mattermost Licensing - -SOFTWARE LICENSING - -You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE - -- See MIT-COMPILED-LICENSE.md included in compiled versions for details - -You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways: - -1. Under the Free Software Foundation’s GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or -2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com - -You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/, -webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0. - -We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not -link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and -(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of -a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license. - -MATTERMOST TRADEMARK GUIDELINES - -Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organization’s Trademark -Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions -you have about using these trademarks, please email trademark@mattermost.com - ------------------------------------------------------------------------------------------------------------------------------- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ------------------------------------------------------------------------------- - -The software is released under the terms of the GNU Affero General Public -License, version 3. - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/vendor/github.com/mattermost/platform/NOTICE.txt b/vendor/github.com/mattermost/platform/NOTICE.txt deleted file mode 100644 index f68ef872..00000000 --- a/vendor/github.com/mattermost/platform/NOTICE.txt +++ /dev/null @@ -1,2935 +0,0 @@ -Mattermost Server -© 2015-present Mattermost, Inc. All Rights Reserved. See LICENSE.txt for license information. - -NOTICES: --------- - -This document includes a list of open source components used in Mattermost Server, including those that have been modified. - ---- - -This product contains a modified portion of 'jquery-dragster', a drag and drop listener -by Jan Martin. - -* HOMEPAGE: - * https://github.com/catmanjan/jquery-dragster - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2015 Jan Martin - -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. - ---- - -This product contains a modified portion of 'golang-lru', a golang LRU cache by hashicorp, -based on Groupcache by Google Inc. - -* HOMEPAGE: - * https://github.com/hashicorp/golang-lru - -* LICENSE: - -This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a -copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - ---- - -This product contains a modified portion of 'golang-freetype', a port of the Freetype -font rasterizer (www.freetype.org) to the Go programming. - -Portions of this software are copyright © 2010 The FreeType Project (www.freetype.org). -All rights reserved. - -* HOMEPAGE: - * http://www.freetype.org - -* LICENSE: - - The FreeType Project LICENSE - ---------------------------- - - 2006-Jan-27 - - Copyright 1996-2002, 2006 by - David Turner, Robert Wilhelm, and Werner Lemberg - - - -Introduction -============ - - The FreeType Project is distributed in several archive packages; - some of them may contain, in addition to the FreeType font engine, - various tools and contributions which rely on, or relate to, the - FreeType Project. - - This license applies to all files found in such packages, and - which do not fall under their own explicit license. The license - affects thus the FreeType font engine, the test programs, - documentation and makefiles, at the very least. - - This license was inspired by the BSD, Artistic, and IJG - (Independent JPEG Group) licenses, which all encourage inclusion - and use of free software in commercial and freeware products - alike. As a consequence, its main points are that: - - o We don't promise that this software works. However, we will be - interested in any kind of bug reports. (`as is' distribution) - - o You can use this software for whatever you want, in parts or - full form, without having to pay us. (`royalty-free' usage) - - o You may not pretend that you wrote this software. If you use - it, or only parts of it, in a program, you must acknowledge - somewhere in your documentation that you have used the - FreeType code. (`credits') - - We specifically permit and encourage the inclusion of this - software, with or without modifications, in commercial products. - We disclaim all warranties covering The FreeType Project and - assume no liability related to The FreeType Project. - - - Finally, many people asked us for a preferred form for a - credit/disclaimer to use in compliance with this license. We thus - encourage you to use the following text: - - """ - Portions of this software are copyright � The FreeType - Project (www.freetype.org). All rights reserved. - """ - - Please replace with the value from the FreeType version you - actually use. - - -Legal Terms -=========== - -0. Definitions --------------- - - Throughout this license, the terms `package', `FreeType Project', - and `FreeType archive' refer to the set of files originally - distributed by the authors (David Turner, Robert Wilhelm, and - Werner Lemberg) as the `FreeType Project', be they named as alpha, - beta or final release. - - `You' refers to the licensee, or person using the project, where - `using' is a generic term including compiling the project's source - code as well as linking it to form a `program' or `executable'. - This program is referred to as `a program using the FreeType - engine'. - - This license applies to all files distributed in the original - FreeType Project, including all source code, binaries and - documentation, unless otherwise stated in the file in its - original, unmodified form as distributed in the original archive. - If you are unsure whether or not a particular file is covered by - this license, you must contact us to verify this. - - The FreeType Project is copyright (C) 1996-2000 by David Turner, - Robert Wilhelm, and Werner Lemberg. All rights reserved except as - specified below. - -1. No Warranty --------------- - - THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY - KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO - USE, OF THE FREETYPE PROJECT. - -2. Redistribution ------------------ - - This license grants a worldwide, royalty-free, perpetual and - irrevocable right and license to use, execute, perform, compile, - display, copy, create derivative works of, distribute and - sublicense the FreeType Project (in both source and object code - forms) and derivative works thereof for any purpose; and to - authorize others to exercise some or all of the rights granted - herein, subject to the following conditions: - - o Redistribution of source code must retain this license file - (`FTL.TXT') unaltered; any additions, deletions or changes to - the original files must be clearly indicated in accompanying - documentation. The copyright notices of the unaltered, - original files must be preserved in all copies of source - files. - - o Redistribution in binary form must provide a disclaimer that - states that the software is based in part of the work of the - FreeType Team, in the distribution documentation. We also - encourage you to put an URL to the FreeType web page in your - documentation, though this isn't mandatory. - - These conditions apply to any software derived from or based on - the FreeType Project, not just the unmodified files. If you use - our work, you must acknowledge us. However, no fee need be paid - to us. - -3. Advertising --------------- - - Neither the FreeType authors and contributors nor you shall use - the name of the other for commercial, advertising, or promotional - purposes without specific prior written permission. - - We suggest, but do not require, that you use one or more of the - following phrases to refer to this software in your documentation - or advertising materials: `FreeType Project', `FreeType Engine', - `FreeType library', or `FreeType Distribution'. - - As you have not signed this license, you are not required to - accept it. However, as the FreeType Project is copyrighted - material, only this license, or another one contracted with the - authors, grants you the right to use, distribute, and modify it. - Therefore, by using, distributing, or modifying the FreeType - Project, you indicate that you understand and accept all the terms - of this license. - -4. Contacts ------------ - - There are two mailing lists related to FreeType: - - o freetype@nongnu.org - - Discusses general use and applications of FreeType, as well as - future and wanted additions to the library and distribution. - If you are looking for support, start in this list if you - haven't found anything to help you in the documentation. - - o freetype-devel@nongnu.org - - Discusses bugs, as well as engine internals, design issues, - specific licenses, porting, etc. - - Our home page can be found at - - http://www.freetype.org - ---- end of FTL.TXT --- - ---- - -This product contains a modified portion of 'gemoji', a collection of emoji images and names by Apple Inc. and other contributors. - -* HOMEPAGE: - * https://github.com/github/gemoji/blob/master/LICENSE - -* LICENSE: - -octocat, squirrel, shipit -Copyright (c) 2013 GitHub Inc. All rights reserved. - -bowtie, neckbeard, fu -Copyright (c) 2013 37signals, LLC. All rights reserved. - -feelsgood, finnadie, goberserk, godmode, hurtrealbad, rage 1-4, suspect -Copyright (c) 2013 id Software. All rights reserved. - -trollface -Copyright (c) 2013 whynne@deviantart. All rights reserved. - -All other images -Copyright (c) 2013 Apple Inc. All rights reserved. - -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. - ---- - -This product contains a modified portion of 'goexif', which provides decoding of basic exif and tiff encoded data. - -by Robert Carlsen & Contributors - -* HOMEPAGE: - * https://github.com/rwcarlsen/goexif - -* LICENSE: - -Copyright (c) 2012, Robert Carlsen & Contributors -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. - ---- - -This product contains a modified portion of 'graphics-go', an implementation of basic image manipulation operations in the Go programming language. - -by The Graphics-Go Authors - -* HOMEPAGE: - * https://code.google.com/p/graphics-go/ - -* LICENSE: - -Copyright (c) 2011 The Graphics-Go Authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -This product contains a modified portion of 'babel-es6-polyfill', a pure ES6 polyfill for Babel that only includes standardised features. - -by Jason Berry - -* HOMEPAGE: - * https://github.com/JasonBerry/babel-es6-polyfill - -* LICENSE: - -Copyright (c) 2015 Jason Berry - -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. - ---- - -This product contains a modified portion of 'go-ldap', basic LDAP v3 functionality for the GO programming language. - -by The Go Authors - -* HOMEPAGE: - * https://github.com/go-ldap/ldap - -* LICENSE: - -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -This product contains a modified portion of 'Google Fonts', various Open Source fonts for use on the web. - -by Google Inc. - -* HOMEPAGE: - * https://www.google.com/fonts - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -Copyright 2011 Google Inc. - -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. - -# Fonts licensed under CC-BY 3.0: - -CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. - -1. Definitions - -"Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. -"Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. -"Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. -"Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. -"Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. -"Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. -"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. -"Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. -"Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: - -to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; -to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; -to Distribute and Publicly Perform the Work including as incorporated in Collections; and, -to Distribute and Publicly Perform Adaptations. -For the avoidance of doubt: - -Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; -Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, -Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. -The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: - -You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. -If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. -Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - -This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. -Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. - -8. Miscellaneous - -Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. -Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. -If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. -No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. -This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. -The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. - -Creative Commons Notice - -Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. - -Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. - -Creative Commons may be contacted at https://creativecommons.org/. - ---- - -This product contains a modified portion of 'go-i18n', a Go package and a command that translates Go programs into multiple languages -by Nick Snyder. - -* HOMEPAGE: - * https://github.com/nicksnyder/go-i18n - -* LICENSE: - -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. - ---- - -This product contains a modified portion of 'dgoogauth', a go implementation of the Google Authenticator library by Damian Gryski. - -* HOMEPAGE: - * https://github.com/dgryski/dgoogauth - -* LICENSE: - -Copyright 2012 Damian Gryski - -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. - ---- - -This product contains a modified portion of 'fbjs', a collection of JavaScript utilities by Facebook, Inc. - -* HOMEPAGE: - * https://github.com/facebook/fbjs - -* LICENSE: - -BSD License - -For fbjs software - -Copyright (c) 2013-present, Facebook, 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 Facebook 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 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. - ---- - -This product contains a modified portion of 'marked', a full-featured markdown parser and compiler, written in JavaScript. Built for speed by Christopher Jeffrey. - -* HOMEPAGE: - * https://github.com/chjj/marked - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2011-2014, Christopher Jeffrey. - -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. -No attribution is required by products that make use of this software. - ---- - -This product contains a modified portion of 'gziphandler', a tiny Go package which wraps HTTP handlers to transparently gzip the response body, for clients which support it by The New York Times Company. - -* HOMEPAGE: - * https://github.com/NYTimes/gziphandler - -* LICENSE: - -Apache License 2.0 - -Copyright (c) 2015 The New York Times Company - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this library 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. - ---- - -This product contains a modified portion of 'handlers', a collection of handlers (aka "HTTP middleware") for use with Go's net/http package (or any framework supporting http.Handler) by The Gorilla Handlers Authors. - -* HOMEPAGE: - * https://github.com/gorilla/handlers - -* LICENSE: - -Copyright (c) 2013 The Gorilla Handlers 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. - ---- - -This product contains a modified portion of 'log4go', an unmaintained fork, left only so it doesn't break imports by Kyle Lemons. - -* HOMEPAGE: - * https://github.com/alecthomas/log4go - -* LICENSE: - -Copyright (c) 2010, Kyle Lemons . 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. - ---- - -This product contains a modified portion of 'manners', a package imaging providing basic image manipulation functions (resize, rotate, flip, crop, etc.) by Grigory Dryapak. - -* HOMEPAGE: - * https://github.com/disintegration/imaging - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2012-2014 Grigory Dryapak - -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. - ---- - -This product contains a modified portion of 'gorp', to save time, minimize the drudgery of getting data in and out of the database, and help code focus on algorithms, not infrastructure by James Cooper. - -* HOMEPAGE: - * https://github.com/go-gorp/gorp - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2012 James Cooper - -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. - ---- - -This product contains a modified portion of 'mysql', a lightweight and fast MySQL driver for Go's (golang) database/sql package by Mozilla Public. - -* HOMEPAGE: - * https://github.com/go-sql-driver/mysql - -* LICENSE: - -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. - ---- - -This product contains a modified portion of 'context', a golang registry for global request variables by Rodrigo Moraes. - -* HOMEPAGE: - * https://github.com/gorilla/context - -* LICENSE: - -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. - ---- - -This product contains a modified portion of 'mux', a powerful URL router and dispatcher for golang by Rodrigo Moraes. - -* HOMEPAGE: - * https://github.com/gorilla/mux - -* LICENSE: - -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. - ---- - -This product contains a modified portion of 'websocket', a WebSocket implementation for Go by The Gorilla WebSocket Authors. - -* HOMEPAGE: - * https://github.com/gorilla/websocket - -* LICENSE: - -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. - ---- - -## pq - -This product contains a modified portion of 'pq', a Pure Go Postgres driver for database/sql by Blake Mizerany and Contributors. - -* HOMEPAGE: - * https://github.com/lib/pq - -* LICENSE: - -Copyright (c) 2011-2013, 'pq' Contributors -Portions Copyright (C) 2011 Blake Mizerany - -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. - ---- - -This product contains a modified portion of 'user_agent', a Go library that parses HTTP User Agents by Miquel Sabaté Solà. - -* HOMEPAGE: - * https://github.com/mssola/user_agent - -* LICENSE: - -Copyright (c) 2012-2016 Miquel Sabaté Solà - -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. - ---- - -This product contains a modified portion of 'uuid', a package generates and inspects UUIDs based on RFC 412 and DCE 1.1: Authentication and Security Services by Google Inc. - -* HOMEPAGE: - * https://github.com/pborman/uuid - -* LICENSE: - -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. - ---- - -This product contains a modified portion of 'yaml', a YAML support for the Go language by Canonical Inc. - -* HOMEPAGE: - * https://gopkg.in/yaml.v2 - -* LICENSE: - - -Copyright (c) 2011-2014 - Canonical Inc. - -This software is licensed under the LGPLv3, included below. - -As a special exception to the GNU Lesser General Public License version 3 -("LGPL3"), the copyright holders of this Library give you permission to -convey to a third party a Combined Work that links statically or dynamically -to this Library without providing any Minimal Corresponding Source or -Minimal Application Code as set out in 4d or providing the installation -information set out in section 4e, provided that you comply with the other -provisions of LGPL3 and provided that you meet, for the Application the -terms and conditions of the license(s) which apply to the Application. - -Except as stated in this special exception, the provisions of LGPL3 will -continue to comply in full to this Library. If you modify this Library, you -may apply this exception to your version of this Library, but you are not -obliged to do so. If you do not wish to do so, delete this exception -statement from your version. This exception does not (and cannot) modify any -license terms which apply to the Application, with which you must still -comply. - - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser 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 -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - ---- - -This product contains a modified portion of 'throttled', a package store offering a memory-based and a Redis-based throttled by Martin Angers. - -* HOMEPAGE: - * http://gopkg.in/throttled/throttled.v1/store - -* LICENSE: - -Copyright (c) 2014, Martin Angers -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 the author 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 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. - ---- - -This product contains a modified portion of 'throttled', a package throttled implementing different throttling strategies for controlling access to HTTP handlers by Martin Angers and Contributors. - -* HOMEPAGE: - * http://gopkg.in/throttled/throttled.v1 - -* LICENSE: - -Copyright (c) 2014, Martin Angers and Contributors. -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 the author 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 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. - ---- - -This product contains a modified portion of 'fsnotify', to utilize golang.org/x/sys rather than syscall from the standard library by The Go Authors and fsnotify Authors. - -* HOMEPAGE: - * http://gopkg.in/fsnotify.v1 - -* LICENSE: - -Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012 fsnotify Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -## asn1-ber - -This product contains a modified portion of 'asn1-ber', an ASN1 BER Encoding / Decoding Library for the GO programming language by The Go Authors. - -* HOMEPAGE: - * http://gopkg.in/asn1-ber.v1 - -* LICENSE: - -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -This product contains a modified portion of 'asn1-ber', an ASN1 BER Encoding / Decoding Library for the GO programming language by The Go Authors. - -* HOMEPAGE: - * http://gopkg.in/asn1-ber.v1 - -* LICENSE: - -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -## redigo - -This product contains a modified portion of 'redigo', a Go client for the Redis database. - -* HOMEPAGE: - * https://github.com/garyburd/redigo - -* LICENSE: - - 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. - ---- - -## crypto - -This product contains a modified portion of 'crypto', a set of Go cryptographic libraries. - -* HOMEPAGE: - * https://github.com/golang/crypto - -* LICENSE: - -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---- - -## image - -This product contains a modified portion of 'image', a set of Go image libraries. - -* HOMEPAGE: - * https://github.com/golang/image - -* LICENSE: - -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---- - -## rsc - -This product contains a modified portion of 'rsc', a random source code library from Google - -* HOMEPAGE: - * https://code.google.com/archive/p/rsc/ - -* LICENSE: - -Copyright (c) , -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. 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. - -3. Neither the name of the copyright holder 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 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. - ---- - -## mattermost-driver-javascript - -This product contains a modified portion of 'mattermost-driver-javascript', a Javascript library for interacting with the Mattermost API. - -* HOMEPAGE: - * https://github.com/mattermost/mattermost-driver-javascript - -* LICENSE: - -Copyright 2016-present Mattermost - -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. - ---- - -This product contains a modified portion of 'osext', providing a function that returns an absolute path to the current program executable, built by kardianos. - -* HOMEPAGE: - * https://github.com/kardianos/osext - -* LICENSE: - -Copyright (c) 2012 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -This product contains a modified portion of 'go-saml', a SAML client library for Go written by RobotsAndPencils. - -* HOMEPAGE: - * https://github.com/RobotsAndPencils/go-saml - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2015 Robots and Pencils - -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. - ---- - -This product contains a modified portion of 'minio-go', a Golang Client SDK providing simple APIs to access any Amazon S3 compatible object storage server, built by Minio. - -* HOMEPAGE: - * https://github.com/minio/minio-go - -* LICENSE: - - - 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 - ---- - -This product contains a modified portion of 'graceful', a Go package enabling graceful shutdown of an http.Handler server, built by Tyler Bunnell. - -* HOMEPAGE: - * https://github.com/tylerb/graceful - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2014 Tyler Bunnell - -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. - ---- - -This product contains a modified portion of 'letsencrypt' to manage TLS certificates automatically via LetsEncrypt.org, built by Russ Cox. - -* HOMEPAGE: - * https://github.com/rsc/letsencrypt - -* LICENSE: - -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -This product contains a modified portion of ‘manners’ a polite webserver for Go, built by Braintree, a division of PayPal, Inc. - -* HOMEPAGE: - * https://github.com/braintree/manners - -* LICENSE: - -Copyright (c) 2014 Braintree, a division of PayPal, Inc. - -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. - ---- - -This product contains a modified portion of ‘perks’ which contains the Go package quantile that computes approximate quantiles over an unbounded data stream within low memory and CPU bounds, built by Blake Mizerany. - -* HOMEPAGE: - * https://github.com/beorn7/perks - -* LICENSE: - -Copyright (C) 2013 Blake Mizerany - -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. - ---- - -This product contains a modified portion of ‘protobuf’ for Go support for Protocol Buffers, built by The Go Authors. - -* HOMEPAGE: - * https://github.com/golang/protobuf - -* LICENSE: - -Go support for Protocol Buffers - Google's data interchange format - -Copyright 2010 The Go Authors. All rights reserved. -https://github.com/golang/protobuf - -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. - ---- - -This product contains a modified portion of ‘golang_protobuf_extensions’ which provides various Protocol Buffer extensions, built by matttproud. - -* HOMEPAGE: - * https://github.com/matttproud/golang_protobuf_extensions - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -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. - ---- - -This product contains a modified portion of ‘cobra’, a Commander for modern Go CLI interactions, built by spf13. - -* HOMEPAGE: - * https://github.com/spf13/cobra - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -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. - ---- - -This product contains a modified portion of ‘pflag’, a Commander for modern Go CLI interactions, built by Alex Ogier. - -* HOMEPAGE: - * https://github.com/spf13/pflag - -* LICENSE: - -Copyright (c) 2012 Alex Ogier. All rights reserved. -Copyright (c) 2012 The Go Authors. All rights reserved. - - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - -This product contains modified portions of Prometheus ‘client_golang’, which is the Go library for Prometheus. Prometheus is a systems and service monitoring system, started by Matt T. Proud and Julius Volz in 2012. - -* HOMEPAGE: - * https://github.com/prometheus/client_golang - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -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. - ---- - -This product contains modified portions of Prometheus ‘client_model’, which provides data model artifacts for Prometheus. Prometheus is a systems and service monitoring system, started by Matt T. Proud and Julius Volz in 2012. - -* HOMEPAGE: - * https://github.com/prometheus/client_model - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -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. - ---- - -This product contains modified portions of Prometheus ‘common’, which is the part of the Prometheus project that contains Go libraries that are shared across Prometheus components and libraries. Prometheus is a systems and service monitoring system, started by Matt T. Proud and Julius Volz in 2012. - -* HOMEPAGE: - * https://github.com/prometheus/common - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -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. - ---- - -This product contains modified portions of Prometheus ‘procfs’, which is the part of the Prometheus project that provides functions to retrieve system, kernel and process metrics from the pseudo-filesystem proc. Prometheus is a systems and service monitoring system, started by Matt T. Proud and Julius Volz in 2012. - -* HOMEPAGE: - * https://github.com/prometheus/procfs - -* LICENSE: - -# Code licensed under the Apache 2.0 License: - -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. - ----- - -This product contains a modified portion of 'go-opengraph', for parsing OpenGraph data from HTML into regular structures by Vitaly Dyatlov. - -* HOMEPAGE: - * https://github.com/dyatlov/go-opengraph - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2015 Vitaly Dyatlov - -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. - ----- - -This product contains a modified portion of `viper` for handling configuration in Go programs. Built by Steve Francia. - -* HOMEPAGE: - * https://github.com/spf13/viper - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2014 Steve Francia - -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. - ----- - -This product contains a modified version of `gomail` for sending emails. Built by Alexandre Cesaro. - -* HOMEPAGE: - * https://github.com/go-gomail/gomail - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2014 Alexandre Cesaro - -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. - ----- - -This product contains a modified version of `fsnotify` for file system notifications. Built by the Go Authors and the fsnotify Authors. - -* HOMEPAGE: - * https://github.com/fsnotify/fsnotify - -* LICENSE: - -Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012 fsnotify Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -## analytics-go - -This product contains a modified portion of `analytics-go`, a segment analytics client for Go by Segment. - -* HOMEPAGE: - * https://github.com/segmentio/analytics-go - -* LICENSE: - -MIT license - ----- - -## html2text - -This product contains a modified portion of 'html2text', an HTML to raw text converter by Carlos Tadeu Panato Junior, based on `html2text` by Jay Taylor. - -* HOMEPAGE - * https://github.com/mattermost/html2text - -* LICENSE - -The MIT License (MIT) - -Copyright (c) 2015 Jay Taylor - -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. - ---- - -## elastic - -This product contains a modified portion of `elastic`, an Elasticsearch client for the Go programming language by Oliver Eilhard. - -* HOMEPAGE - * https://github.com/olivere/elastic - -* LICENSE - -The MIT License (MIT) -Copyright © 2012-2015 Oliver Eilhard - -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. - ---- - -## redis - -This product contains a modified portion of `redis`, a type-safe Redis client for Golang, by go-redis. - -* HOMEPAGE - * https://github.com/go-redis/redis - -* LICENSE - -Copyright (c) 2013 The github.com/go-redis/redis 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 -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. - ---- - -## testify - -This product contains a modified portion of testify, a Golang toolkit providing tools for testifying that code behaves as intended. - -* HOMEPAGE - * https://github.com/stretchr/testify - -* LICENSE - -Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell - -Please consider promoting this project if you find it useful. - -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. - ---- - -## html2text - -This product contains a modified portion of `html2text`, a Golang library for HTML to plaintext conversion. - -* HOMEPAGE - * https://github.com/mattermost/html2text - -* LICENSE - -The MIT License (MIT) - -Copyright (c) 2015 Jay Taylor -Modified work: Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. - -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/mattermost/platform/model/access.go b/vendor/github.com/mattermost/platform/model/access.go deleted file mode 100644 index 551ef930..00000000 --- a/vendor/github.com/mattermost/platform/model/access.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" -) - -const ( - ACCESS_TOKEN_GRANT_TYPE = "authorization_code" - ACCESS_TOKEN_TYPE = "bearer" - REFRESH_TOKEN_GRANT_TYPE = "refresh_token" -) - -type AccessData struct { - ClientId string `json:"client_id"` - UserId string `json:"user_id"` - Token string `json:"token"` - RefreshToken string `json:"refresh_token"` - RedirectUri string `json:"redirect_uri"` - ExpiresAt int64 `json:"expires_at"` - Scope string `json:"scope"` -} - -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.ClientId) == 0 || len(ad.ClientId) > 26 { - return NewAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(ad.UserId) == 0 || len(ad.UserId) > 26 { - return NewAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(ad.Token) != 26 { - return NewAppError("AccessData.IsValid", "model.access.is_valid.access_token.app_error", nil, "", http.StatusBadRequest) - } - - if len(ad.RefreshToken) > 26 { - return NewAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "", http.StatusBadRequest) - } - - if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) { - return NewAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (me *AccessData) IsExpired() bool { - - if me.ExpiresAt <= 0 { - return false - } - - if GetMillis() > me.ExpiresAt { - return true - } - - return false -} - -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 deleted file mode 100644 index 5493163d..00000000 --- a/vendor/github.com/mattermost/platform/model/analytics_row.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2015-present 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 deleted file mode 100644 index 7699cf93..00000000 --- a/vendor/github.com/mattermost/platform/model/audit.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2015-present 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 deleted file mode 100644 index e57e2a3a..00000000 --- a/vendor/github.com/mattermost/platform/model/audits.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2015-present 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/authorization.go b/vendor/github.com/mattermost/platform/model/authorization.go deleted file mode 100644 index 9f4e36ea..00000000 --- a/vendor/github.com/mattermost/platform/model/authorization.go +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -type Permission struct { - Id string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` -} - -type Role struct { - Id string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Permissions []string `json:"permissions"` -} - -var PERMISSION_INVITE_USER *Permission -var PERMISSION_ADD_USER_TO_TEAM *Permission -var PERMISSION_USE_SLASH_COMMANDS *Permission -var PERMISSION_MANAGE_SLASH_COMMANDS *Permission -var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission -var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission -var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission -var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission -var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission -var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission -var PERMISSION_MANAGE_ROLES *Permission -var PERMISSION_MANAGE_TEAM_ROLES *Permission -var PERMISSION_MANAGE_CHANNEL_ROLES *Permission -var PERMISSION_CREATE_DIRECT_CHANNEL *Permission -var PERMISSION_CREATE_GROUP_CHANNEL *Permission -var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission -var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission -var PERMISSION_LIST_TEAM_CHANNELS *Permission -var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission -var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission -var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission -var PERMISSION_EDIT_OTHER_USERS *Permission -var PERMISSION_READ_CHANNEL *Permission -var PERMISSION_READ_PUBLIC_CHANNEL *Permission -var PERMISSION_PERMANENT_DELETE_USER *Permission -var PERMISSION_UPLOAD_FILE *Permission -var PERMISSION_GET_PUBLIC_LINK *Permission -var PERMISSION_MANAGE_WEBHOOKS *Permission -var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission -var PERMISSION_MANAGE_OAUTH *Permission -var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission -var PERMISSION_CREATE_POST *Permission -var PERMISSION_CREATE_POST_PUBLIC *Permission -var PERMISSION_EDIT_POST *Permission -var PERMISSION_EDIT_OTHERS_POSTS *Permission -var PERMISSION_DELETE_POST *Permission -var PERMISSION_DELETE_OTHERS_POSTS *Permission -var PERMISSION_REMOVE_USER_FROM_TEAM *Permission -var PERMISSION_CREATE_TEAM *Permission -var PERMISSION_MANAGE_TEAM *Permission -var PERMISSION_IMPORT_TEAM *Permission -var PERMISSION_VIEW_TEAM *Permission -var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission -var PERMISSION_MANAGE_JOBS *Permission -var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission -var PERMISSION_READ_USER_ACCESS_TOKEN *Permission -var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission - -// General permission that encompases all system admin functions -// in the future this could be broken up to allow access to some -// admin functions but not others -var PERMISSION_MANAGE_SYSTEM *Permission - -const ( - SYSTEM_USER_ROLE_ID = "system_user" - SYSTEM_ADMIN_ROLE_ID = "system_admin" - SYSTEM_POST_ALL_ROLE_ID = "system_post_all" - SYSTEM_POST_ALL_PUBLIC_ROLE_ID = "system_post_all_public" - SYSTEM_USER_ACCESS_TOKEN_ROLE_ID = "system_user_access_token" - - TEAM_USER_ROLE_ID = "team_user" - TEAM_ADMIN_ROLE_ID = "team_admin" - TEAM_POST_ALL_ROLE_ID = "team_post_all" - TEAM_POST_ALL_PUBLIC_ROLE_ID = "team_post_all_public" - - CHANNEL_USER_ROLE_ID = "channel_user" - CHANNEL_ADMIN_ROLE_ID = "channel_admin" - CHANNEL_GUEST_ROLE_ID = "guest" -) - -func initializePermissions() { - PERMISSION_INVITE_USER = &Permission{ - "invite_user", - "authentication.permissions.team_invite_user.name", - "authentication.permissions.team_invite_user.description", - } - PERMISSION_ADD_USER_TO_TEAM = &Permission{ - "add_user_to_team", - "authentication.permissions.add_user_to_team.name", - "authentication.permissions.add_user_to_team.description", - } - PERMISSION_USE_SLASH_COMMANDS = &Permission{ - "use_slash_commands", - "authentication.permissions.team_use_slash_commands.name", - "authentication.permissions.team_use_slash_commands.description", - } - PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{ - "manage_slash_commands", - "authentication.permissions.manage_slash_commands.name", - "authentication.permissions.manage_slash_commands.description", - } - PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{ - "manage_others_slash_commands", - "authentication.permissions.manage_others_slash_commands.name", - "authentication.permissions.manage_others_slash_commands.description", - } - PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{ - "create_public_channel", - "authentication.permissions.create_public_channel.name", - "authentication.permissions.create_public_channel.description", - } - PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{ - "create_private_channel", - "authentication.permissions.create_private_channel.name", - "authentication.permissions.create_private_channel.description", - } - PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{ - "manage_public_channel_members", - "authentication.permissions.manage_public_channel_members.name", - "authentication.permissions.manage_public_channel_members.description", - } - PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{ - "manage_private_channel_members", - "authentication.permissions.manage_private_channel_members.name", - "authentication.permissions.manage_private_channel_members.description", - } - PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{ - "assign_system_admin_role", - "authentication.permissions.assign_system_admin_role.name", - "authentication.permissions.assign_system_admin_role.description", - } - PERMISSION_MANAGE_ROLES = &Permission{ - "manage_roles", - "authentication.permissions.manage_roles.name", - "authentication.permissions.manage_roles.description", - } - PERMISSION_MANAGE_TEAM_ROLES = &Permission{ - "manage_team_roles", - "authentication.permissions.manage_team_roles.name", - "authentication.permissions.manage_team_roles.description", - } - PERMISSION_MANAGE_CHANNEL_ROLES = &Permission{ - "manage_channel_roles", - "authentication.permissions.manage_channel_roles.name", - "authentication.permissions.manage_channel_roles.description", - } - PERMISSION_MANAGE_SYSTEM = &Permission{ - "manage_system", - "authentication.permissions.manage_system.name", - "authentication.permissions.manage_system.description", - } - PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{ - "create_direct_channel", - "authentication.permissions.create_direct_channel.name", - "authentication.permissions.create_direct_channel.description", - } - PERMISSION_CREATE_GROUP_CHANNEL = &Permission{ - "create_group_channel", - "authentication.permissions.create_group_channel.name", - "authentication.permissions.create_group_channel.description", - } - PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{ - "manage__publicchannel_properties", - "authentication.permissions.manage_public_channel_properties.name", - "authentication.permissions.manage_public_channel_properties.description", - } - PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{ - "manage_private_channel_properties", - "authentication.permissions.manage_private_channel_properties.name", - "authentication.permissions.manage_private_channel_properties.description", - } - PERMISSION_LIST_TEAM_CHANNELS = &Permission{ - "list_team_channels", - "authentication.permissions.list_team_channels.name", - "authentication.permissions.list_team_channels.description", - } - PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{ - "join_public_channels", - "authentication.permissions.join_public_channels.name", - "authentication.permissions.join_public_channels.description", - } - PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{ - "delete_public_channel", - "authentication.permissions.delete_public_channel.name", - "authentication.permissions.delete_public_channel.description", - } - PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{ - "delete_private_channel", - "authentication.permissions.delete_private_channel.name", - "authentication.permissions.delete_private_channel.description", - } - PERMISSION_EDIT_OTHER_USERS = &Permission{ - "edit_other_users", - "authentication.permissions.edit_other_users.name", - "authentication.permissions.edit_other_users.description", - } - PERMISSION_READ_CHANNEL = &Permission{ - "read_channel", - "authentication.permissions.read_channel.name", - "authentication.permissions.read_channel.description", - } - PERMISSION_READ_PUBLIC_CHANNEL = &Permission{ - "read_public_channel", - "authentication.permissions.read_public_channel.name", - "authentication.permissions.read_public_channel.description", - } - PERMISSION_PERMANENT_DELETE_USER = &Permission{ - "permanent_delete_user", - "authentication.permissions.permanent_delete_user.name", - "authentication.permissions.permanent_delete_user.description", - } - PERMISSION_UPLOAD_FILE = &Permission{ - "upload_file", - "authentication.permissions.upload_file.name", - "authentication.permissions.upload_file.description", - } - PERMISSION_GET_PUBLIC_LINK = &Permission{ - "get_public_link", - "authentication.permissions.get_public_link.name", - "authentication.permissions.get_public_link.description", - } - PERMISSION_MANAGE_WEBHOOKS = &Permission{ - "manage_webhooks", - "authentication.permissions.manage_webhooks.name", - "authentication.permissions.manage_webhooks.description", - } - PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{ - "manage_others_webhooks", - "authentication.permissions.manage_others_webhooks.name", - "authentication.permissions.manage_others_webhooks.description", - } - PERMISSION_MANAGE_OAUTH = &Permission{ - "manage_oauth", - "authentication.permissions.manage_oauth.name", - "authentication.permissions.manage_oauth.description", - } - PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ - "manage_sytem_wide_oauth", - "authentication.permissions.manage_sytem_wide_oauth.name", - "authentication.permissions.manage_sytem_wide_oauth.description", - } - PERMISSION_CREATE_POST = &Permission{ - "create_post", - "authentication.permissions.create_post.name", - "authentication.permissions.create_post.description", - } - PERMISSION_CREATE_POST_PUBLIC = &Permission{ - "create_post_public", - "authentication.permissions.create_post_public.name", - "authentication.permissions.create_post_public.description", - } - PERMISSION_EDIT_POST = &Permission{ - "edit_post", - "authentication.permissions.edit_post.name", - "authentication.permissions.edit_post.description", - } - PERMISSION_EDIT_OTHERS_POSTS = &Permission{ - "edit_others_posts", - "authentication.permissions.edit_others_posts.name", - "authentication.permissions.edit_others_posts.description", - } - PERMISSION_DELETE_POST = &Permission{ - "delete_post", - "authentication.permissions.delete_post.name", - "authentication.permissions.delete_post.description", - } - PERMISSION_DELETE_OTHERS_POSTS = &Permission{ - "delete_others_posts", - "authentication.permissions.delete_others_posts.name", - "authentication.permissions.delete_others_posts.description", - } - PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{ - "remove_user_from_team", - "authentication.permissions.remove_user_from_team.name", - "authentication.permissions.remove_user_from_team.description", - } - PERMISSION_CREATE_TEAM = &Permission{ - "create_team", - "authentication.permissions.create_team.name", - "authentication.permissions.create_team.description", - } - PERMISSION_MANAGE_TEAM = &Permission{ - "manage_team", - "authentication.permissions.manage_team.name", - "authentication.permissions.manage_team.description", - } - PERMISSION_IMPORT_TEAM = &Permission{ - "import_team", - "authentication.permissions.import_team.name", - "authentication.permissions.import_team.description", - } - PERMISSION_VIEW_TEAM = &Permission{ - "view_team", - "authentication.permissions.view_team.name", - "authentication.permissions.view_team.description", - } - PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{ - "list_users_without_team", - "authentication.permissions.list_users_without_team.name", - "authentication.permissions.list_users_without_team.description", - } - PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{ - "create_user_access_token", - "authentication.permissions.create_user_access_token.name", - "authentication.permissions.create_user_access_token.description", - } - PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{ - "read_user_access_token", - "authentication.permissions.read_user_access_token.name", - "authentication.permissions.read_user_access_token.description", - } - PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{ - "revoke_user_access_token", - "authentication.permissions.revoke_user_access_token.name", - "authentication.permissions.revoke_user_access_token.description", - } - PERMISSION_MANAGE_JOBS = &Permission{ - "manage_jobs", - "authentication.permisssions.manage_jobs.name", - "authentication.permisssions.manage_jobs.description", - } -} - -var DefaultRoles map[string]*Role - -func initializeDefaultRoles() { - DefaultRoles = make(map[string]*Role) - - DefaultRoles[CHANNEL_USER_ROLE_ID] = &Role{ - "channel_user", - "authentication.roles.channel_user.name", - "authentication.roles.channel_user.description", - []string{ - PERMISSION_READ_CHANNEL.Id, - PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, - PERMISSION_UPLOAD_FILE.Id, - PERMISSION_GET_PUBLIC_LINK.Id, - PERMISSION_CREATE_POST.Id, - PERMISSION_EDIT_POST.Id, - PERMISSION_USE_SLASH_COMMANDS.Id, - }, - } - - DefaultRoles[CHANNEL_ADMIN_ROLE_ID] = &Role{ - "channel_admin", - "authentication.roles.channel_admin.name", - "authentication.roles.channel_admin.description", - []string{ - PERMISSION_MANAGE_CHANNEL_ROLES.Id, - }, - } - - DefaultRoles[CHANNEL_GUEST_ROLE_ID] = &Role{ - "guest", - "authentication.roles.global_guest.name", - "authentication.roles.global_guest.description", - []string{}, - } - - DefaultRoles[TEAM_USER_ROLE_ID] = &Role{ - "team_user", - "authentication.roles.team_user.name", - "authentication.roles.team_user.description", - []string{ - PERMISSION_LIST_TEAM_CHANNELS.Id, - PERMISSION_JOIN_PUBLIC_CHANNELS.Id, - PERMISSION_READ_PUBLIC_CHANNEL.Id, - PERMISSION_VIEW_TEAM.Id, - }, - } - - DefaultRoles[TEAM_POST_ALL_ROLE_ID] = &Role{ - "team_post_all", - "authentication.roles.team_post_all.name", - "authentication.roles.team_post_all.description", - []string{ - PERMISSION_CREATE_POST.Id, - }, - } - - DefaultRoles[TEAM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ - "team_post_all_public", - "authentication.roles.team_post_all_public.name", - "authentication.roles.team_post_all_public.description", - []string{ - PERMISSION_CREATE_POST_PUBLIC.Id, - }, - } - - DefaultRoles[TEAM_ADMIN_ROLE_ID] = &Role{ - "team_admin", - "authentication.roles.team_admin.name", - "authentication.roles.team_admin.description", - []string{ - PERMISSION_EDIT_OTHERS_POSTS.Id, - PERMISSION_REMOVE_USER_FROM_TEAM.Id, - PERMISSION_MANAGE_TEAM.Id, - PERMISSION_IMPORT_TEAM.Id, - PERMISSION_MANAGE_TEAM_ROLES.Id, - PERMISSION_MANAGE_CHANNEL_ROLES.Id, - PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, - PERMISSION_MANAGE_SLASH_COMMANDS.Id, - PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, - PERMISSION_MANAGE_WEBHOOKS.Id, - }, - } - - DefaultRoles[SYSTEM_USER_ROLE_ID] = &Role{ - "system_user", - "authentication.roles.global_user.name", - "authentication.roles.global_user.description", - []string{ - PERMISSION_CREATE_DIRECT_CHANNEL.Id, - PERMISSION_CREATE_GROUP_CHANNEL.Id, - PERMISSION_PERMANENT_DELETE_USER.Id, - }, - } - - DefaultRoles[SYSTEM_POST_ALL_ROLE_ID] = &Role{ - "system_post_all", - "authentication.roles.system_post_all.name", - "authentication.roles.system_post_all.description", - []string{ - PERMISSION_CREATE_POST.Id, - }, - } - - DefaultRoles[SYSTEM_POST_ALL_PUBLIC_ROLE_ID] = &Role{ - "system_post_all_public", - "authentication.roles.system_post_all_public.name", - "authentication.roles.system_post_all_public.description", - []string{ - PERMISSION_CREATE_POST_PUBLIC.Id, - }, - } - - DefaultRoles[SYSTEM_USER_ACCESS_TOKEN_ROLE_ID] = &Role{ - "system_user_access_token", - "authentication.roles.system_user_access_token.name", - "authentication.roles.system_user_access_token.description", - []string{ - PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, - PERMISSION_READ_USER_ACCESS_TOKEN.Id, - PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, - }, - } - - DefaultRoles[SYSTEM_ADMIN_ROLE_ID] = &Role{ - "system_admin", - "authentication.roles.global_admin.name", - "authentication.roles.global_admin.description", - // System admins can do anything channel and team admins can do - // plus everything members of teams and channels can do to all teams - // and channels on the system - append( - append( - append( - append( - []string{ - PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, - PERMISSION_MANAGE_SYSTEM.Id, - PERMISSION_MANAGE_ROLES.Id, - PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, - PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, - PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, - PERMISSION_DELETE_PUBLIC_CHANNEL.Id, - PERMISSION_CREATE_PUBLIC_CHANNEL.Id, - PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, - PERMISSION_DELETE_PRIVATE_CHANNEL.Id, - PERMISSION_CREATE_PRIVATE_CHANNEL.Id, - PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, - PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, - PERMISSION_EDIT_OTHER_USERS.Id, - PERMISSION_MANAGE_OAUTH.Id, - PERMISSION_INVITE_USER.Id, - PERMISSION_DELETE_POST.Id, - PERMISSION_DELETE_OTHERS_POSTS.Id, - PERMISSION_CREATE_TEAM.Id, - PERMISSION_ADD_USER_TO_TEAM.Id, - PERMISSION_LIST_USERS_WITHOUT_TEAM.Id, - PERMISSION_MANAGE_JOBS.Id, - PERMISSION_CREATE_POST_PUBLIC.Id, - PERMISSION_CREATE_USER_ACCESS_TOKEN.Id, - PERMISSION_READ_USER_ACCESS_TOKEN.Id, - PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id, - }, - DefaultRoles[TEAM_USER_ROLE_ID].Permissions..., - ), - DefaultRoles[CHANNEL_USER_ROLE_ID].Permissions..., - ), - DefaultRoles[TEAM_ADMIN_ROLE_ID].Permissions..., - ), - DefaultRoles[CHANNEL_ADMIN_ROLE_ID].Permissions..., - ), - } -} - -func RoleIdsToString(roles []string) string { - output := "" - for _, role := range roles { - output += role + ", " - } - - if output == "" { - return "[]" - } - - return output[:len(output)-1] -} - -func init() { - initializePermissions() - initializeDefaultRoles() -} diff --git a/vendor/github.com/mattermost/platform/model/authorize.go b/vendor/github.com/mattermost/platform/model/authorize.go deleted file mode 100644 index df07ff14..00000000 --- a/vendor/github.com/mattermost/platform/model/authorize.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" -) - -const ( - AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes - AUTHCODE_RESPONSE_TYPE = "code" - DEFAULT_SCOPE = "user" -) - -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"` -} - -type AuthorizeRequest struct { - ResponseType string `json:"response_type"` - ClientId string `json:"client_id"` - RedirectUri string `json:"redirect_uri"` - Scope string `json:"scope"` - State string `json:"state"` -} - -// 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 NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(ad.UserId) != 26 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(ad.Code) == 0 || len(ad.Code) > 128 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.auth_code.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) - } - - if ad.ExpiresIn == 0 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.expires.app_error", nil, "", http.StatusBadRequest) - } - - if ad.CreateAt <= 0 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) - } - - if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) - } - - if len(ad.State) > 128 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) - } - - if len(ad.Scope) > 128 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) - } - - return nil -} - -// IsValid validates the AuthorizeRequest and returns an error if it isn't configured -// correctly. -func (ar *AuthorizeRequest) IsValid() *AppError { - - if len(ar.ClientId) != 26 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(ar.ResponseType) == 0 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest) - } - - if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest) - } - - if len(ar.State) > 128 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest) - } - - if len(ar.Scope) > 128 { - return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest) - } - - return nil -} - -func (ad *AuthData) PreSave() { - if ad.ExpiresIn == 0 { - ad.ExpiresIn = AUTHCODE_EXPIRE_TIME - } - - if ad.CreateAt == 0 { - ad.CreateAt = GetMillis() - } - - if len(ad.Scope) == 0 { - ad.Scope = DEFAULT_SCOPE - } -} - -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 (ar *AuthorizeRequest) ToJson() string { - b, err := json.Marshal(ar) - if err != nil { - return "" - } else { - return string(b) - } -} - -func AuthorizeRequestFromJson(data io.Reader) *AuthorizeRequest { - decoder := json.NewDecoder(data) - var ar AuthorizeRequest - err := decoder.Decode(&ar) - if err == nil { - return &ar - } else { - return nil - } -} - -func (ad *AuthData) IsExpired() bool { - return GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) -} diff --git a/vendor/github.com/mattermost/platform/model/builtin.go b/vendor/github.com/mattermost/platform/model/builtin.go deleted file mode 100644 index 5dd00a96..00000000 --- a/vendor/github.com/mattermost/platform/model/builtin.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -func NewBool(b bool) *bool { return &b } -func NewInt(n int) *int { return &n } -func NewInt64(n int64) *int64 { return &n } -func NewString(s string) *string { return &s } diff --git a/vendor/github.com/mattermost/platform/model/bundle_info.go b/vendor/github.com/mattermost/platform/model/bundle_info.go deleted file mode 100644 index 6965159c..00000000 --- a/vendor/github.com/mattermost/platform/model/bundle_info.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -type BundleInfo struct { - Path string - - Manifest *Manifest - ManifestPath string - ManifestError error -} - -// Returns bundle info for the given path. The return value is never nil. -func BundleInfoForPath(path string) *BundleInfo { - m, mpath, err := FindManifest(path) - return &BundleInfo{ - Path: path, - Manifest: m, - ManifestPath: mpath, - ManifestError: err, - } -} diff --git a/vendor/github.com/mattermost/platform/model/channel.go b/vendor/github.com/mattermost/platform/model/channel.go deleted file mode 100644 index a4c733c3..00000000 --- a/vendor/github.com/mattermost/platform/model/channel.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "crypto/sha1" - "encoding/hex" - "encoding/json" - "io" - "net/http" - "sort" - "strings" - "unicode/utf8" -) - -const ( - CHANNEL_OPEN = "O" - CHANNEL_PRIVATE = "P" - CHANNEL_DIRECT = "D" - CHANNEL_GROUP = "G" - CHANNEL_GROUP_MAX_USERS = 8 - CHANNEL_GROUP_MIN_USERS = 3 - DEFAULT_CHANNEL = "town-square" - CHANNEL_DISPLAY_NAME_MAX_RUNES = 64 - CHANNEL_NAME_MIN_LENGTH = 2 - CHANNEL_NAME_MAX_LENGTH = 64 - CHANNEL_NAME_UI_MAX_LENGTH = 22 - CHANNEL_HEADER_MAX_RUNES = 1024 - CHANNEL_PURPOSE_MAX_RUNES = 250 - CHANNEL_CACHE_SIZE = 25000 -) - -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"` -} - -type ChannelPatch struct { - DisplayName *string `json:"display_name"` - Name *string `json:"name"` - Header *string `json:"header"` - Purpose *string `json:"purpose"` -} - -func (o *Channel) DeepCopy() *Channel { - copy := *o - return © -} - -func (o *Channel) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (o *ChannelPatch) 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 ChannelPatchFromJson(data io.Reader) *ChannelPatch { - decoder := json.NewDecoder(data) - var o ChannelPatch - 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) StatsEtag() string { - return Etag(o.Id, o.ExtraUpdateAt) -} - -func (o *Channel) IsValid() *AppError { - - if len(o.Id) != 26 { - return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.DisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES { - return NewAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if !IsValidChannelIdentifier(o.Name) { - return NewAppError("Channel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP) { - return NewAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.Header) > CHANNEL_HEADER_MAX_RUNES { - return NewAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.Purpose) > CHANNEL_PURPOSE_MAX_RUNES { - return NewAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.CreatorId) > 26 { - return NewAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "", http.StatusBadRequest) - } - - 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) IsGroupOrDirect() bool { - return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP -} - -func (o *Channel) Patch(patch *ChannelPatch) { - if patch.DisplayName != nil { - o.DisplayName = *patch.DisplayName - } - - if patch.Name != nil { - o.Name = *patch.Name - } - - if patch.Header != nil { - o.Header = *patch.Header - } - - if patch.Purpose != nil { - o.Purpose = *patch.Purpose - } -} - -func GetDMNameFromIds(userId1, userId2 string) string { - if userId1 > userId2 { - return userId2 + "__" + userId1 - } else { - return userId1 + "__" + userId2 - } -} - -func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string { - usernames := make([]string, len(users)) - for index, user := range users { - usernames[index] = user.Username - } - - sort.Strings(usernames) - - name := strings.Join(usernames, ", ") - - if truncate && len(name) > CHANNEL_NAME_MAX_LENGTH { - name = name[:CHANNEL_NAME_MAX_LENGTH] - } - - return name -} - -func GetGroupNameFromUserIds(userIds []string) string { - sort.Strings(userIds) - - h := sha1.New() - for _, id := range userIds { - io.WriteString(h, id) - } - - return hex.EncodeToString(h.Sum(nil)) -} diff --git a/vendor/github.com/mattermost/platform/model/channel_count.go b/vendor/github.com/mattermost/platform/model/channel_count.go deleted file mode 100644 index aecb0c71..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_count.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2015-present 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 deleted file mode 100644 index 41b7eaa6..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_data.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2015-present 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_list.go b/vendor/github.com/mattermost/platform/model/channel_list.go deleted file mode 100644 index d5a4ccb7..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_list.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type ChannelList []*Channel - -func (o *ChannelList) ToJson() string { - if b, err := json.Marshal(o); 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 { - if v.LastPostAt > t { - t = v.LastPostAt - id = v.Id - } - - if v.UpdateAt > t { - t = v.UpdateAt - id = v.Id - } - - } - - return Etag(id, t, delta, len(*o)) -} - -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 - } -} - -func ChannelSliceFromJson(data io.Reader) []*Channel { - decoder := json.NewDecoder(data) - var o []*Channel - 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 deleted file mode 100644 index e7ad8232..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_member.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "strings" -) - -const ( - 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 ChannelUnread struct { - TeamId string `json:"team_id"` - ChannelId string `json:"channel_id"` - MsgCount int64 `json:"msg_count"` - MentionCount int64 `json:"mention_count"` - NotifyProps StringMap `json:"-"` -} - -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"` -} - -type ChannelMembers []ChannelMember - -func (o *ChannelMembers) ToJson() string { - if b, err := json.Marshal(o); err != nil { - return "[]" - } else { - return string(b) - } -} - -func (o *ChannelUnread) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ChannelMembersFromJson(data io.Reader) *ChannelMembers { - decoder := json.NewDecoder(data) - var o ChannelMembers - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func ChannelUnreadFromJson(data io.Reader) *ChannelUnread { - decoder := json.NewDecoder(data) - var o ChannelUnread - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -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 NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.UserId) != 26 { - return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - notifyLevel := o.NotifyProps[DESKTOP_NOTIFY_PROP] - if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) { - return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error", nil, "notify_level="+notifyLevel, http.StatusBadRequest) - } - - markUnreadLevel := o.NotifyProps[MARK_UNREAD_NOTIFY_PROP] - if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) { - return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error", nil, "mark_unread_level="+markUnreadLevel, http.StatusBadRequest) - } - - if pushLevel, ok := o.NotifyProps[PUSH_NOTIFY_PROP]; ok { - if len(pushLevel) > 20 || !IsChannelNotifyLevelValid(pushLevel) { - return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.push_level.app_error", nil, "push_notification_level="+pushLevel, http.StatusBadRequest) - } - } - - if sendEmail, ok := o.NotifyProps[EMAIL_NOTIFY_PROP]; ok { - if len(sendEmail) > 20 || !IsSendEmailValid(sendEmail) { - return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.email_value.app_error", nil, "push_notification_level="+sendEmail, http.StatusBadRequest) - } - } - - return nil -} - -func (o *ChannelMember) PreSave() { - o.LastUpdateAt = GetMillis() -} - -func (o *ChannelMember) PreUpdate() { - o.LastUpdateAt = GetMillis() -} - -func (o *ChannelMember) GetRoles() []string { - return strings.Fields(o.Roles) -} - -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 IsSendEmailValid(sendEmail string) bool { - return sendEmail == CHANNEL_NOTIFY_DEFAULT || sendEmail == "true" || sendEmail == "false" -} - -func GetDefaultChannelNotifyProps() StringMap { - return StringMap{ - DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, - MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL, - PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, - EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT, - } -} diff --git a/vendor/github.com/mattermost/platform/model/channel_member_history.go b/vendor/github.com/mattermost/platform/model/channel_member_history.go deleted file mode 100644 index bc71b580..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_member_history.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -type ChannelMemberHistory struct { - ChannelId string - UserId string - UserEmail string `db:"Email"` - JoinTime int64 - LeaveTime *int64 -} diff --git a/vendor/github.com/mattermost/platform/model/channel_search.go b/vendor/github.com/mattermost/platform/model/channel_search.go deleted file mode 100644 index d915c164..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_search.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type ChannelSearch struct { - Term string `json:"term"` -} - -// ToJson convert a Channel to a json string -func (c *ChannelSearch) ToJson() string { - b, err := json.Marshal(c) - if err != nil { - return "" - } else { - return string(b) - } -} - -// ChannelSearchFromJson will decode the input and return a Channel -func ChannelSearchFromJson(data io.Reader) *ChannelSearch { - decoder := json.NewDecoder(data) - var cs ChannelSearch - err := decoder.Decode(&cs) - if err == nil { - return &cs - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/channel_stats.go b/vendor/github.com/mattermost/platform/model/channel_stats.go deleted file mode 100644 index 758aa2b1..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_stats.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type ChannelStats struct { - ChannelId string `json:"channel_id"` - MemberCount int64 `json:"member_count"` -} - -func (o *ChannelStats) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ChannelStatsFromJson(data io.Reader) *ChannelStats { - decoder := json.NewDecoder(data) - var o ChannelStats - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/channel_view.go b/vendor/github.com/mattermost/platform/model/channel_view.go deleted file mode 100644 index e7b1de30..00000000 --- a/vendor/github.com/mattermost/platform/model/channel_view.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type ChannelView struct { - ChannelId string `json:"channel_id"` - PrevChannelId string `json:"prev_channel_id"` -} - -func (o *ChannelView) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ChannelViewFromJson(data io.Reader) *ChannelView { - decoder := json.NewDecoder(data) - var o ChannelView - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -type ChannelViewResponse struct { - Status string `json:"status"` - LastViewedAtTimes map[string]int64 `json:"last_viewed_at_times"` -} - -func (o *ChannelViewResponse) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ChannelViewResponseFromJson(data io.Reader) *ChannelViewResponse { - decoder := json.NewDecoder(data) - var o ChannelViewResponse - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/client.go b/vendor/github.com/mattermost/platform/model/client.go deleted file mode 100644 index ef890b59..00000000 --- a/vendor/github.com/mattermost/platform/model/client.go +++ /dev/null @@ -1,2379 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - l4g "github.com/alecthomas/log4go" -) - -var UsedApiV3 *int32 = new(int32) - -const ( - HEADER_REQUEST_ID = "X-Request-ID" - HEADER_VERSION_ID = "X-Version-ID" - HEADER_CLUSTER_ID = "X-Cluster-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" - STATUS = "status" - STATUS_OK = "OK" - STATUS_FAIL = "FAIL" - STATUS_REMOVE = "REMOVE" - - CLIENT_DIR = "client" - - API_URL_SUFFIX_V1 = "/api/v1" - API_URL_SUFFIX_V3 = "/api/v3" - API_URL_SUFFIX_V4 = "/api/v4" - API_URL_SUFFIX = API_URL_SUFFIX_V4 -) - -type Result struct { - RequestId string - Etag string - Data interface{} -} - -type ResponseMetadata struct { - StatusCode int - Error *AppError - RequestId string - Etag string -} - -type Client struct { - Url string // The location of the server like "http://localhost:8065" - ApiUrl string // The api location of the server like "http://localhost:8065/api/v3" - HttpClient *http.Client // The http client - AuthToken string - AuthType string - TeamId string - RequestId string - Etag string - ServerVersion string -} - -// NewClient constructs a new client with convienence methods for talking to -// the server. -func NewClient(url string) *Client { - return &Client{url, url + API_URL_SUFFIX_V3, &http.Client{}, "", "", "", "", "", ""} -} - -func closeBody(r *http.Response) { - if r.Body != nil { - ioutil.ReadAll(r.Body) - r.Body.Close() - } -} - -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) SetTeamId(teamId string) { - c.TeamId = teamId -} - -func (c *Client) GetTeamId() string { - if len(c.TeamId) == 0 { - println(`You are trying to use a route that requires a team_id, - but you have not called SetTeamId() in client.go`) - } - - return c.TeamId -} - -func (c *Client) ClearTeamId() { - c.TeamId = "" -} - -func (c *Client) GetTeamRoute() string { - return fmt.Sprintf("/teams/%v", c.GetTeamId()) -} - -func (c *Client) GetChannelRoute(channelId string) string { - return fmt.Sprintf("/teams/%v/channels/%v", c.GetTeamId(), channelId) -} - -func (c *Client) GetUserRequiredRoute(userId string) string { - return fmt.Sprintf("/users/%v", userId) -} - -func (c *Client) GetChannelNameRoute(channelName string) string { - return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) -} - -func (c *Client) GetEmojiRoute() string { - return "/emoji" -} - -func (c *Client) GetGeneralRoute() string { - return "/general" -} - -func (c *Client) GetFileRoute(fileId string) string { - return fmt.Sprintf("/files/%v", fileId) -} - -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) - rq.Close = true - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - 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)) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - 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)) - rq.Close = true - - if len(etag) > 0 { - rq.Header.Set(HEADER_ETAG_CLIENT, etag) - } - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode == 304 { - return rp, nil - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return rp, AppErrorFromJson(rp.Body) - } else { - return rp, nil - } -} - -func getCookie(name string, resp *http.Response) *http.Cookie { - for _, cookie := range resp.Cookies() { - if cookie.Name == name { - return cookie - } - } - - return nil -} - -// Must is a convenience function used for testing. -func (c *Client) Must(result *Result, err *AppError) *Result { - if err != nil { - l4g.Close() - time.Sleep(time.Second) - panic(err) - } - - return result -} - -// MustGeneric is a convenience function used for testing. -func (c *Client) MustGeneric(result interface{}, err *AppError) interface{} { - if err != nil { - l4g.Close() - time.Sleep(time.Second) - panic(err) - } - - return result -} - -// CheckStatusOK is a convenience function for checking the return of Web Service -// call that return the a map of status=OK. -func (c *Client) CheckStatusOK(r *http.Response) bool { - m := MapFromJson(r.Body) - defer closeBody(r) - - if m != nil && m[STATUS] == STATUS_OK { - return true - } - - return false -} - -func (c *Client) fillInExtraProperties(r *http.Response) { - c.RequestId = r.Header.Get(HEADER_REQUEST_ID) - c.Etag = r.Header.Get(HEADER_ETAG_SERVER) - c.ServerVersion = r.Header.Get(HEADER_VERSION_ID) -} - -func (c *Client) clearExtraProperties() { - c.RequestId = "" - c.Etag = "" - c.ServerVersion = "" -} - -// General Routes Section - -// GetClientProperties returns properties needed by the client to show/hide -// certian features. It returns a map of strings. -func (c *Client) GetClientProperties() (map[string]string, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet(c.GetGeneralRoute()+"/client_props", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return MapFromJson(r.Body), nil - } -} - -// LogClient is a convenience Web Service call so clients can log messages into -// the server-side logs. For example we typically log javascript error messages -// into the server-side. It returns true if the logging was successful. -func (c *Client) LogClient(message string) (bool, *AppError) { - c.clearExtraProperties() - m := make(map[string]string) - m["level"] = "ERROR" - m["message"] = message - - if r, err := c.DoApiPost(c.GetGeneralRoute()+"/log_client", MapToJson(m)); err != nil { - return false, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -// GetPing returns a map of strings with server time, server version, and node Id. -// Systems that want to check on health status of the server should check the -// url /api/v3/ping for a 200 status response. -func (c *Client) GetPing() (map[string]string, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet(c.GetGeneralRoute()+"/ping", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return MapFromJson(r.Body), nil - } -} - -// Team Routes Section - -// CreateTeam creates a team based on the provided Team struct. On success it returns -// the Team struct with the Id, CreateAt and other server-decided fields populated. -func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { - if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// GetAllTeams returns a map of all teams using team ids as the key. -func (c *Client) GetAllTeams() (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil - } -} - -// GetAllTeamListings returns a map of all teams that are available to join -// using team ids as the key. Must be authenticated. -func (c *Client) GetAllTeamListings() (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil - } -} - -// FindTeamByName returns the strings "true" or "false" depending on if a team -// with the provided name was found. -func (c *Client) FindTeamByName(name string) (*Result, *AppError) { - m := make(map[string]string) - m["name"] = name - 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 - } - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), val}, nil - } -} - -// Adds a user directly to the team without sending an invite. -// The teamId and userId are required. You must be a valid member of the team and/or -// have the correct role to add new users to the team. Returns a map of user_id=userId -// if successful, otherwise returns an AppError. -func (c *Client) AddUserToTeam(teamId string, userId string) (*Result, *AppError) { - if len(teamId) == 0 { - teamId = c.GetTeamId() - } - - data := make(map[string]string) - data["user_id"] = userId - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/add_user_to_team", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// AddUserToTeamFromInvite adds a user to a team based off data provided in an invite link. -// Either hash and dataToHash are required or inviteId is required. -func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Result, *AppError) { - data := make(map[string]string) - data["hash"] = hash - data["data"] = dataToHash - data["invite_id"] = inviteId - if r, err := c.DoApiPost("/teams/add_user_to_team_from_invite", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// Removes a user directly from the team. -// The teamId and userId are required. You must be a valid member of the team and/or -// have the correct role to remove a user from the team. Returns a map of user_id=userId -// if successful, otherwise returns an AppError. -func (c *Client) RemoveUserFromTeam(teamId string, userId string) (*Result, *AppError) { - if len(teamId) == 0 { - teamId = c.GetTeamId() - } - - data := make(map[string]string) - data["user_id"] = userId - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/remove_user_from_team", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), InvitesFromJson(r.Body)}, nil - } -} - -// UpdateTeam updates a team based on the changes in the provided team struct. On success -// it returns a sanitized version of the updated team. Must be authenticated as a team admin -// for that team or a system admin. -func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// User Routes Section - -// CreateUser creates a user in the system based on the provided user struct. -func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// CreateUserWithInvite creates a user based on the provided user struct. Either the hash and -// data strings or the inviteId is required from the invite. -func (c *Client) CreateUserWithInvite(user *User, hash string, data string, inviteId string) (*Result, *AppError) { - - url := "/users/create?d=" + url.QueryEscape(data) + "&h=" + url.QueryEscape(hash) + "&iid=" + url.QueryEscape(inviteId) - - if r, err := c.DoApiPost(url, user.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -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 { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// GetUser returns a user based on a provided user id string. Must be authenticated. -func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// getByUsername returns a user based on a provided username string. Must be authenticated. -func (c *Client) GetByUsername(username string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/users/name/%v", username), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// getByEmail returns a user based on a provided username string. Must be authenticated. -func (c *Client) GetByEmail(email string, etag string) (*User, *ResponseMetadata) { - if r, err := c.DoApiGet(fmt.Sprintf("/users/email/%v", email), "", etag); err != nil { - return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - defer closeBody(r) - return UserFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -// GetMe returns the current user. -func (c *Client) GetMe(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/me", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// GetProfiles returns a map of users using user id as the key. Must be authenticated. -func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesInTeam returns a map of users for a team using user id as the key. Must -// be authenticated. -func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesInChannel returns a map of users for a channel using user id as the key. Must -// be authenticated. -func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must -// be authenticated. -func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// GetProfilesByIds returns a map of users based on the user ids provided. Must -// be authenticated. -func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -// SearchUsers returns a list of users that have a username matching or similar to the search term. Must -// be authenticated. -func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil - } -} - -// AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel", -// specifies users in the channel. The second list "out_of_channel" specifies users outside of the -// channel. Term, the string to search against, is required, channel id is also required. Must be authenticated. -func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) { - url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil - } -} - -// AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies -// the users in the team that match the provided term, matching against username, full name and -// nickname. Must be authenticated. -func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) { - url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil - } -} - -// AutocompleteUsers returns a list for autocompletion of users on the system that match the provided term, -// matching against username, full name and nickname. Must be authenticated. -func (c *Client) AutocompleteUsers(term string) (*Result, *AppError) { - url := fmt.Sprintf("/users/autocomplete?term=%s", url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil - } -} - -// LoginById authenticates a user by user id and password. -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) -} - -// Login authenticates a user by login id, which can be username, email or some sort -// of SSO identifier based on configuration, and a password. -func (c *Client) Login(loginId string, password string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - return c.login(m) -} - -// LoginByLdap authenticates a user by LDAP id and password. -func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - m["ldap_only"] = "true" - return c.login(m) -} - -// LoginWithDevice authenticates a user by login id (username, email or some sort -// of SSO identifier based on configuration), password and attaches a device id to -// the session. -func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - 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 { - NewAppError("/users/login", "model.client.login.app_error", nil, "", 0) - } - - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -// Logout terminates the current user's session. -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 - c.TeamId = "" - - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// CheckMfa returns a map with key "mfa_required" with the string value "true" or "false", -// indicating whether MFA is required to log the user in, based on a provided login id -// (username, email or some sort of SSO identifier based on configuration). -func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { - m := make(map[string]string) - m["login_id"] = loginId - - if r, err := c.DoApiPost("/users/mfa", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GenerateMfaSecret returns a QR code image containing the secret, to be scanned -// by a multi-factor authentication mobile application. It also returns the secret -// for manual entry. Must be authenticated. -func (c *Client) GenerateMfaSecret() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/generate_mfa_secret", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// UpdateMfa activates multi-factor authenticates for the current user if activate -// is true and a valid token is provided. If activate is false, then token is not -// required and multi-factor authentication is disabled for the current user. -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 { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) AdminResetMfa(userId string) (*Result, *AppError) { - m := make(map[string]string) - m["user_id"] = userId - - if r, err := c.DoApiPost("/admin/reset_mfa", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -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 { - defer closeBody(r) - 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 { - defer closeBody(r) - 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_oauth", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) OAuthToEmail(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/oauth_to_email", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) LDAPToEmail(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) Command(channelId string, command string) (*Result, *AppError) { - args := &CommandArgs{ChannelId: channelId, Command: command} - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", args.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil - } -} - -func (c *Client) ListCommands() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil - } -} - -func (c *Client) ListTeamCommands() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list_team_commands", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/create", cmd.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateCommand(cmd *Command) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/update", cmd.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil - } -} - -func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil - } -} - -func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetLogs() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil - } -} - -func (c *Client) GetClusterStatus() ([]*ClusterInfo, *AppError) { - if r, err := c.DoApiGet("/admin/cluster_status", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return ClusterInfosFromJson(r.Body), nil - } -} - -// GetRecentlyActiveUsers returns a map of users including lastActivityAt using user id as the key -func (c *Client) GetRecentlyActiveUsers(teamId string) (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/recently_active_users/"+teamId, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAllAudits() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetConfig() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/config", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ConfigFromJson(r.Body)}, nil - } -} - -// ReloadConfig will reload the config.json file from disk. Properties -// requiring a server restart will still need a server restart. You must -// have the system admin role to call this method. It will return status=OK -// if it's successfully reloaded the config file, otherwise check the returned error. -func (c *Client) ReloadConfig() (bool, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet("/admin/reload_config", "", ""); err != nil { - return false, err - } else { - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) InvalidateAllCaches() (bool, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet("/admin/invalidate_all_caches", "", ""); err != nil { - return false, err - } else { - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) SaveConfig(config *Config) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// RecycleDatabaseConnection will attempt to recycle the database connections. -// You must have the system admin role to call this method. It will return status=OK -// if it's successfully recycled the connections, otherwise check the returned error. -func (c *Client) RecycleDatabaseConnection() (bool, *AppError) { - c.clearExtraProperties() - if r, err := c.DoApiGet("/admin/recycle_db_conn", "", ""); err != nil { - return false, err - } else { - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) TestEmail(config *Config) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/test_email", config.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// TestLdap will run a connection test on the current LDAP settings. -// It will return the standard OK response if settings work. Otherwise -// it will return an appropriate error. -func (c *Client) TestLdap(config *Config) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/ldap_test", config.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetComplianceReports() (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), CompliancesFromJson(r.Body)}, nil - } -} - -func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/save_compliance_report", job.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ComplianceFromJson(r.Body)}, nil - } -} - -func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) { - var rq *http.Request - rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError("/admin/download_compliance_report", "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - defer rp.Body.Close() - return nil, AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - return &Result{rp.Header.Get(HEADER_REQUEST_ID), - rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil - } -} - -func (c *Client) GetTeamAnalytics(teamId, name string) (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) { - if r, err := c.DoApiGet("/admin/analytics/"+name, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil - } -} - -// Initiate immediate synchronization of LDAP users. -// The synchronization will be performed asynchronously and this function will -// always return OK unless you don't have permissions. -// You must be the system administrator to use this function. -func (c *Client) LdapSyncNow() (*Result, *AppError) { - if r, err := c.DoApiPost("/admin/ldap_sync_now", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create", channel.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateDirectChannel(userId string) (*Result, *AppError) { - data := make(map[string]string) - data["user_id"] = userId - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_direct", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateGroupChannel(userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_group", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update", channel.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_header", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_purpose", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_notify_props", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetMyChannelMembers() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/members", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannel(id, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelDataFromJson(r.Body)}, nil - } -} - -// GetMoreChannelsPage will return a page of open channels the user is not in based on -// the provided offset and limit. Must be authenticated. -func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetTeamRoute()+"/channels/more/%v/%v", offset, limit), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -// SearchMoreChannels will return a list of open channels the user is not in, that matches -// the search criteria provided. Must be authenticated. -func (c *Client) SearchMoreChannels(channelSearch ChannelSearch) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/more/search", channelSearch.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -// AutocompleteChannels will return a list of open channels that match the provided -// string. Must be authenticated. -func (c *Client) AutocompleteChannels(term string) (*Result, *AppError) { - url := fmt.Sprintf("%s/channels/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) - if r, err := c.DoApiGet(url, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelCountsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannels(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannelByName(channelName string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelNameRoute(channelName), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil - } -} - -func (c *Client) JoinChannel(id string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) JoinChannelByName(name string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelNameRoute(name)+"/join", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - 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(c.GetChannelRoute(id)+"/leave", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - 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(c.GetChannelRoute(id)+"/delete", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - 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(c.GetChannelRoute(id)+"/add", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), 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(c.GetChannelRoute(id)+"/remove", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -// ViewChannel performs all the actions related to viewing a channel. This includes marking -// the channel and the previous one as read, and marking the channel as being actively viewed. -// ChannelId is required but may be blank to indicate no channel is being viewed. -// PrevChannelId is optional, populate to indicate a channel switch occurred. -func (c *Client) ViewChannel(params ChannelView) (bool, *ResponseMetadata) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/view", params.ToJson()); err != nil { - return false, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - return c.CheckStatusOK(r), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil - } -} - -func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil - } -} - -// GetChannelMembersByIds will return channel member objects as an array based on the -// channel id and a list of user ids provided. Must be authenticated. -func (c *Client) GetChannelMembersByIds(channelId string, userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/members/ids", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil - } -} - -func (c *Client) CreatePost(post *Post) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdatePost(post *Post) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/update", post.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/page/%v/%v", offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/since/%v", time), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPostsBefore(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/before/%v/%v", postid, offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/posts/%v/after/%v/%v", postid, offset, limit), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get", postId), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -// GetPostById returns a post and any posts in the same thread by post id -func (c *Client) GetPostById(postId string, etag string) (*PostList, *ResponseMetadata) { - if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/%v", postId), "", etag); err != nil { - return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - defer closeBody(r) - return PostListFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -// GetPermalink returns a post list, based on the provided channel and post ID. -func (c *Client) GetPermalink(channelId string, postId string, etag string) (*PostList, *ResponseMetadata) { - if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/pltmp/%v", postId), "", etag); err != nil { - return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} - } else { - defer closeBody(r) - return PostListFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) { - data := map[string]interface{}{} - data["terms"] = terms - data["is_or_search"] = isOrSearch - if r, err := c.DoApiPost(c.GetTeamRoute()+"/posts/search", StringInterfaceToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -// GetFlaggedPosts will return a post list of posts that have been flagged by the user. -// The page is set by the integer parameters offset and limit. -func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/flagged/%v/%v", offset, limit), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetPinnedPosts(channelId string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil - } -} - -func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { - return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) -} - -func (c *Client) UploadPostAttachment(data []byte, channelId string, filename string) (*FileUploadResponse, *AppError) { - c.clearExtraProperties() - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("files", filename); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), 0) - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), 0) - } - - if part, err := writer.CreateFormField("channel_id"); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), 0) - } else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), 0) - } - - if err := writer.Close(); err != nil { - return nil, NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), 0) - } - - if result, err := c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", body.Bytes(), writer.FormDataContentType()); err != nil { - return nil, err - } else { - return result.Data.(*FileUploadResponse), nil - } -} - -func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) { - rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - return nil, AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - return &Result{rp.Header.Get(HEADER_REQUEST_ID), - rp.Header.Get(HEADER_ETAG_SERVER), FileUploadResponseFromJson(rp.Body)}, nil - } -} - -func (c *Client) GetFile(fileId string) (io.ReadCloser, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get", "", ""); err != nil { - return nil, err - } else { - c.fillInExtraProperties(r) - return r.Body, nil - } -} - -func (c *Client) GetFileThumbnail(fileId string) (io.ReadCloser, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_thumbnail", "", ""); err != nil { - return nil, err - } else { - c.fillInExtraProperties(r) - return r.Body, nil - } -} - -func (c *Client) GetFilePreview(fileId string) (io.ReadCloser, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_preview", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return r.Body, nil - } -} - -func (c *Client) GetFileInfo(fileId string) (*FileInfo, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_info", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return FileInfoFromJson(r.Body), nil - } -} - -func (c *Client) GetPublicLink(fileId string) (string, *AppError) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_public_link", "", ""); err != nil { - return "", err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return StringFromJson(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 { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateUserRoles(userId string, roles string) (*Result, *AppError) { - data := make(map[string]string) - data["new_roles"] = roles - - if r, err := c.DoApiPost(c.GetUserRequiredRoute(userId)+"/update_roles", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateTeamRoles(userId string, roles string) (*Result, *AppError) { - data := make(map[string]string) - data["new_roles"] = roles - data["user_id"] = userId - - if r, err := c.DoApiPost(c.GetTeamRoute()+"/update_member_roles", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -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 { - defer closeBody(r) - 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 { - defer closeBody(r) - 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 { - defer closeBody(r) - 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 { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) SendPasswordReset(email string) (*Result, *AppError) { - data := map[string]string{} - data["email"] = email - if r, err := c.DoApiPost("/users/send_password_reset", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) ResetPassword(code, newPassword string) (*Result, *AppError) { - data := map[string]string{} - data["code"] = code - data["new_password"] = newPassword - if r, err := c.DoApiPost("/users/reset_password", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppError) { - data := map[string]string{} - data["user_id"] = userId - data["new_password"] = newPassword - if r, err := c.DoApiPost("/admin/reset_password", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetStatuses returns a map of string statuses using user id as the key -func (c *Client) GetStatuses() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/status", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetStatusesByIds returns a map of string statuses using user id as the key, -// based on the provided user ids -func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// GetTeamMembers will return a page of team member objects as an array paged based on the -// team id, offset and limit provided. Must be authenticated. -func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil - } -} - -// GetMyTeamMembers will return an array with team member objects that the current user -// is a member of. Must be authenticated. -func (c *Client) GetMyTeamMembers() (*Result, *AppError) { - if r, err := c.DoApiGet("/teams/members", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil - } -} - -// GetMyTeamsUnread will return an array with TeamUnread objects that contain the amount of -// unread messages and mentions the current user has for the teams it belongs to. -// An optional team ID can be set to exclude that team from the results. Must be authenticated. -func (c *Client) GetMyTeamsUnread(teamId string) (*Result, *AppError) { - endpoint := "/teams/unread" - - if teamId != "" { - endpoint += fmt.Sprintf("?id=%s", url.QueryEscape(teamId)) - } - if r, err := c.DoApiGet(endpoint, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamsUnreadFromJson(r.Body)}, nil - } -} - -// GetTeamMember will return a team member object based on the team id and user id provided. -// Must be authenticated. -func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil - } -} - -// GetTeamStats will return a team stats object containing the number of users on the team -// based on the team id provided. Must be authenticated. -func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil - } -} - -// GetTeamByName will return a team object based on the team name provided. Must be authenticated. -func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) { - if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil - } -} - -// GetTeamMembersByIds will return team member objects as an array based on the -// team id and a list of user ids provided. Must be authenticated. -func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) { - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil - } -} - -// RegisterApp creates a new OAuth2 app to be used with the OAuth2 Provider. On success -// it returns the created app. Must be authenticated as a user. -func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { - if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil - } -} - -// AllowOAuth allows a new session by an OAuth2 App. On success -// it returns the url to be redirected back to the app which initiated the oauth2 flow. -// Must be authenticated as a user. -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 { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetOAuthAppsByUser returns the OAuth2 Apps registered by the user. On success -// it returns a list of OAuth2 Apps from the same user or all the registered apps if the user -// is a System Administrator. Must be authenticated as a user. -func (c *Client) GetOAuthAppsByUser() (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil - } -} - -// GetOAuthAppInfo lookup an OAuth2 App using the client_id. On success -// it returns a Sanitized OAuth2 App. Must be authenticated as a user. -func (c *Client) GetOAuthAppInfo(clientId string) (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/app/"+clientId, "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil - } -} - -// DeleteOAuthApp deletes an OAuth2 app, the app must be deleted by the same user who created it or -// a System Administrator. On success returs Status OK. Must be authenticated as a user. -func (c *Client) DeleteOAuthApp(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost("/oauth/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -// GetOAuthAuthorizedApps returns the OAuth2 Apps authorized by the user. On success -// it returns a list of sanitized OAuth2 Authorized Apps by the user. -func (c *Client) GetOAuthAuthorizedApps() (*Result, *AppError) { - if r, err := c.DoApiGet("/oauth/authorized", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil - } -} - -// OAuthDeauthorizeApp deauthorize a user an OAuth 2.0 app. On success -// it returns status OK or an AppError on fail. -func (c *Client) OAuthDeauthorizeApp(clientId string) *AppError { - if r, err := c.DoApiPost("/oauth/"+clientId+"/deauthorize", ""); err != nil { - return err - } else { - defer closeBody(r) - return nil - } -} - -// RegenerateOAuthAppSecret generates a new OAuth App Client Secret. On success -// it returns an OAuth2 App. Must be authenticated as a user and the same user who -// registered the app or a System Admin. -func (c *Client) RegenerateOAuthAppSecret(clientId string) (*Result, *AppError) { - if r, err := c.DoApiPost("/oauth/"+clientId+"/regen_secret", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { - if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), AccessResponseFromJson(r.Body)}, nil - } -} - -func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/create", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/update", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) PostToWebhook(id, payload string) (*Result, *AppError) { - if r, err := c.DoPost("/hooks/"+id, payload, "application/x-www-form-urlencoded"); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), nil}, nil - } -} - -func (c *Client) DeleteIncomingWebhook(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) ListIncomingWebhooks() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/incoming/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookListFromJson(r.Body)}, nil - } -} - -func (c *Client) GetAllPreferences() (*Result, *AppError) { - if r, err := c.DoApiGet("/preferences/", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - preferences, _ := PreferencesFromJson(r.Body) - return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil - } -} - -func (c *Client) SetPreferences(preferences *Preferences) (*Result, *AppError) { - if r, err := c.DoApiPost("/preferences/save", preferences.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - 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 { - defer closeBody(r) - 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 { - defer closeBody(r) - preferences, _ := PreferencesFromJson(r.Body) - return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil - } -} - -// DeletePreferences deletes a list of preferences owned by the current user. If successful, -// it will return status=ok. Otherwise, an error will be returned. -func (c *Client) DeletePreferences(preferences *Preferences) (bool, *AppError) { - if r, err := c.DoApiPost("/preferences/delete", preferences.ToJson()); err != nil { - return false, err - } else { - return c.CheckStatusOK(r), nil - } -} - -func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) UpdateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/update", hook.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) DeleteOutgoingWebhook(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/delete", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) { - if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/outgoing/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookListFromJson(r.Body)}, nil - } -} - -func (c *Client) RegenOutgoingWebhookToken(id string) (*Result, *AppError) { - data := make(map[string]string) - data["id"] = id - if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/regen_token", MapToJson(data)); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil - } -} - -func (c *Client) MockSession(sessionToken string) { - c.AuthToken = sessionToken - c.AuthType = HEADER_BEARER -} - -func (c *Client) GetClientLicenceConfig(etag string) (*Result, *AppError) { - if r, err := c.DoApiGet("/license/client_config", "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil - } -} - -func (c *Client) GetInitialLoad() (*Result, *AppError) { - if r, err := c.DoApiGet("/users/initial_load", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil - } -} - -// ListEmoji returns a list of all user-created emoji for the server. -func (c *Client) ListEmoji() ([]*Emoji, *AppError) { - if r, err := c.DoApiGet(c.GetEmojiRoute()+"/list", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return EmojiListFromJson(r.Body), nil - } -} - -// CreateEmoji will save an emoji to the server if the current user has permission -// to do so. If successful, the provided emoji will be returned with its Id field -// filled in. Otherwise, an error will be returned. -func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoji, *AppError) { - c.clearExtraProperties() - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("image", filename); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0) - } else if _, err = io.Copy(part, bytes.NewBuffer(image)); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0) - } - - if err := writer.WriteField("emoji", emoji.ToJson()); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.emoji.app_error", nil, err.Error(), 0) - } - - if err := writer.Close(); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.create_emoji.writer.app_error", nil, err.Error(), 0) - } - - rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body) - rq.Header.Set("Content-Type", writer.FormDataContentType()) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if r, err := c.HttpClient.Do(rq); err != nil { - return nil, NewAppError("CreateEmoji", "model.client.connecting.app_error", nil, err.Error(), 0) - } else if r.StatusCode >= 300 { - return nil, AppErrorFromJson(r.Body) - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return EmojiFromJson(r.Body), nil - } -} - -// DeleteEmoji will delete an emoji from the server if the current user has permission -// to do so. If successful, it will return status=ok. Otherwise, an error will be returned. -func (c *Client) DeleteEmoji(id string) (bool, *AppError) { - data := map[string]string{"id": id} - - if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil { - return false, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return c.CheckStatusOK(r), nil - } -} - -// GetCustomEmojiImageUrl returns the API route that can be used to get the image used by -// the given emoji. -func (c *Client) GetCustomEmojiImageUrl(id string) string { - return c.GetEmojiRoute() + "/" + id -} - -// Uploads a x509 base64 Certificate or Private Key file to be used with SAML. -// data byte array is required and needs to be a Multi-Part with 'certificate' as the field name -// contentType is also required. Returns nil if succesful, otherwise returns an AppError -func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppError { - url := c.ApiUrl + "/admin/add_certificate" - rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil { - return NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode >= 300 { - return AppErrorFromJson(rp.Body) - } else { - defer closeBody(rp) - c.fillInExtraProperties(rp) - return nil - } -} - -// Removes a x509 base64 Certificate or Private Key file used with SAML. -// filename is required. Returns nil if successful, otherwise returns an AppError -func (c *Client) RemoveCertificateFile(filename string) *AppError { - if r, err := c.DoApiPost("/admin/remove_certificate", MapToJson(map[string]string{"filename": filename})); err != nil { - return err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return nil - } -} - -// Checks if the x509 base64 Certificates and Private Key files used with SAML exists on the file system. -// Returns a map[string]interface{} if successful, otherwise returns an AppError. Must be System Admin authenticated. -func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, *AppError) { - if r, err := c.DoApiGet("/admin/remove_certificate", "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return StringInterfaceFromJson(r.Body), nil - } -} - -// GetWebrtcToken if Successful returns a map with a valid token, stun server and turn server with credentials to use with -// the Mattermost WebRTC service, otherwise returns an AppError. Must be authenticated user. -func (c *Client) GetWebrtcToken() (map[string]string, *AppError) { - if r, err := c.DoApiPost("/webrtc/token", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return MapFromJson(r.Body), nil - } -} - -// GetFileInfosForPost returns a list of FileInfo objects for a given post id, if successful. -// Otherwise, it returns an error. -func (c *Client) GetFileInfosForPost(channelId string, postId string, etag string) ([]*FileInfo, *AppError) { - c.clearExtraProperties() - - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get_file_infos", postId), "", etag); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return FileInfosFromJson(r.Body), nil - } -} - -// Saves an emoji reaction for a post in the given channel. Returns the saved reaction if successful, otherwise returns an AppError. -func (c *Client) SaveReaction(channelId string, reaction *Reaction) (*Reaction, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/save", reaction.PostId), reaction.ToJson()); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return ReactionFromJson(r.Body), nil - } -} - -// Removes an emoji reaction for a post in the given channel. Returns nil if successful, otherwise returns an AppError. -func (c *Client) DeleteReaction(channelId string, reaction *Reaction) *AppError { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/delete", reaction.PostId), reaction.ToJson()); err != nil { - return err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return nil - } -} - -// Lists all emoji reactions made for the given post in the given channel. Returns a list of Reactions if successful, otherwise returns an AppError. -func (c *Client) ListReactions(channelId string, postId string) ([]*Reaction, *AppError) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions", postId), "", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - c.fillInExtraProperties(r) - return ReactionsFromJson(r.Body), nil - } -} - -// Updates the user's roles in the channel by replacing them with the roles provided. -func (c *Client) UpdateChannelRoles(channelId string, userId string, roles string) (map[string]string, *ResponseMetadata) { - data := make(map[string]string) - data["new_roles"] = roles - data["user_id"] = userId - - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_member_roles", MapToJson(data)); err != nil { - metadata := ResponseMetadata{Error: err} - if r != nil { - metadata.StatusCode = r.StatusCode - } - return nil, &metadata - } else { - defer closeBody(r) - return MapFromJson(r.Body), - &ResponseMetadata{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - } - } -} - -func (c *Client) PinPost(channelId string, postId string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/pin", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} - -func (c *Client) UnpinPost(channelId string, postId string) (*Result, *AppError) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/unpin", ""); err != nil { - return nil, err - } else { - defer closeBody(r) - return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/client4.go b/vendor/github.com/mattermost/platform/model/client4.go deleted file mode 100644 index e84a23e5..00000000 --- a/vendor/github.com/mattermost/platform/model/client4.go +++ /dev/null @@ -1,3225 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/url" - "strconv" - "strings" -) - -type Response struct { - StatusCode int - Error *AppError - RequestId string - Etag string - ServerVersion string - Header http.Header -} - -type Client4 struct { - Url string // The location of the server, for example "http://localhost:8065" - ApiUrl string // The api location of the server, for example "http://localhost:8065/api/v4" - HttpClient *http.Client // The http client - AuthToken string - AuthType string -} - -func NewAPIv4Client(url string) *Client4 { - return &Client4{url, url + API_URL_SUFFIX, &http.Client{}, "", ""} -} - -func BuildErrorResponse(r *http.Response, err *AppError) *Response { - var statusCode int - var header http.Header - if r != nil { - statusCode = r.StatusCode - header = r.Header - } else { - statusCode = 0 - header = make(http.Header) - } - - return &Response{ - StatusCode: statusCode, - Error: err, - Header: header, - } -} - -func BuildResponse(r *http.Response) *Response { - return &Response{ - StatusCode: r.StatusCode, - RequestId: r.Header.Get(HEADER_REQUEST_ID), - Etag: r.Header.Get(HEADER_ETAG_SERVER), - ServerVersion: r.Header.Get(HEADER_VERSION_ID), - Header: r.Header, - } -} - -func (c *Client4) SetOAuthToken(token string) { - c.AuthToken = token - c.AuthType = HEADER_TOKEN -} - -func (c *Client4) ClearOAuthToken() { - c.AuthToken = "" - c.AuthType = HEADER_BEARER -} - -func (c *Client4) GetUsersRoute() string { - return fmt.Sprintf("/users") -} - -func (c *Client4) GetUserRoute(userId string) string { - return fmt.Sprintf(c.GetUsersRoute()+"/%v", userId) -} - -func (c *Client4) GetUserAccessTokenRoute(tokenId string) string { - return fmt.Sprintf(c.GetUsersRoute()+"/tokens/%v", tokenId) -} - -func (c *Client4) GetUserByUsernameRoute(userName string) string { - return fmt.Sprintf(c.GetUsersRoute()+"/username/%v", userName) -} - -func (c *Client4) GetUserByEmailRoute(email string) string { - return fmt.Sprintf(c.GetUsersRoute()+"/email/%v", email) -} - -func (c *Client4) GetTeamsRoute() string { - return fmt.Sprintf("/teams") -} - -func (c *Client4) GetTeamRoute(teamId string) string { - return fmt.Sprintf(c.GetTeamsRoute()+"/%v", teamId) -} - -func (c *Client4) GetTeamAutoCompleteCommandsRoute(teamId string) string { - return fmt.Sprintf(c.GetTeamsRoute()+"/%v/commands/autocomplete", teamId) -} - -func (c *Client4) GetTeamByNameRoute(teamName string) string { - return fmt.Sprintf(c.GetTeamsRoute()+"/name/%v", teamName) -} - -func (c *Client4) GetTeamMemberRoute(teamId, userId string) string { - return fmt.Sprintf(c.GetTeamRoute(teamId)+"/members/%v", userId) -} - -func (c *Client4) GetTeamMembersRoute(teamId string) string { - return fmt.Sprintf(c.GetTeamRoute(teamId) + "/members") -} - -func (c *Client4) GetTeamStatsRoute(teamId string) string { - return fmt.Sprintf(c.GetTeamRoute(teamId) + "/stats") -} - -func (c *Client4) GetTeamImportRoute(teamId string) string { - return fmt.Sprintf(c.GetTeamRoute(teamId) + "/import") -} - -func (c *Client4) GetChannelsRoute() string { - return fmt.Sprintf("/channels") -} - -func (c *Client4) GetChannelsForTeamRoute(teamId string) string { - return fmt.Sprintf(c.GetTeamRoute(teamId) + "/channels") -} - -func (c *Client4) GetChannelRoute(channelId string) string { - return fmt.Sprintf(c.GetChannelsRoute()+"/%v", channelId) -} - -func (c *Client4) GetChannelByNameRoute(channelName, teamId string) string { - return fmt.Sprintf(c.GetTeamRoute(teamId)+"/channels/name/%v", channelName) -} - -func (c *Client4) GetChannelByNameForTeamNameRoute(channelName, teamName string) string { - return fmt.Sprintf(c.GetTeamByNameRoute(teamName)+"/channels/name/%v", channelName) -} - -func (c *Client4) GetChannelMembersRoute(channelId string) string { - return fmt.Sprintf(c.GetChannelRoute(channelId) + "/members") -} - -func (c *Client4) GetChannelMemberRoute(channelId, userId string) string { - return fmt.Sprintf(c.GetChannelMembersRoute(channelId)+"/%v", userId) -} - -func (c *Client4) GetPostsRoute() string { - return fmt.Sprintf("/posts") -} - -func (c *Client4) GetConfigRoute() string { - return fmt.Sprintf("/config") -} - -func (c *Client4) GetLicenseRoute() string { - return fmt.Sprintf("/license") -} - -func (c *Client4) GetPostRoute(postId string) string { - return fmt.Sprintf(c.GetPostsRoute()+"/%v", postId) -} - -func (c *Client4) GetFilesRoute() string { - return fmt.Sprintf("/files") -} - -func (c *Client4) GetFileRoute(fileId string) string { - return fmt.Sprintf(c.GetFilesRoute()+"/%v", fileId) -} - -func (c *Client4) GetPluginsRoute() string { - return fmt.Sprintf("/plugins") -} - -func (c *Client4) GetPluginRoute(pluginId string) string { - return fmt.Sprintf(c.GetPluginsRoute()+"/%v", pluginId) -} - -func (c *Client4) GetSystemRoute() string { - return fmt.Sprintf("/system") -} - -func (c *Client4) GetTestEmailRoute() string { - return fmt.Sprintf("/email/test") -} - -func (c *Client4) GetDatabaseRoute() string { - return fmt.Sprintf("/database") -} - -func (c *Client4) GetCacheRoute() string { - return fmt.Sprintf("/caches") -} - -func (c *Client4) GetClusterRoute() string { - return fmt.Sprintf("/cluster") -} - -func (c *Client4) GetIncomingWebhooksRoute() string { - return fmt.Sprintf("/hooks/incoming") -} - -func (c *Client4) GetIncomingWebhookRoute(hookID string) string { - return fmt.Sprintf(c.GetIncomingWebhooksRoute()+"/%v", hookID) -} - -func (c *Client4) GetComplianceReportsRoute() string { - return fmt.Sprintf("/compliance/reports") -} - -func (c *Client4) GetComplianceReportRoute(reportId string) string { - return fmt.Sprintf("/compliance/reports/%v", reportId) -} - -func (c *Client4) GetOutgoingWebhooksRoute() string { - return fmt.Sprintf("/hooks/outgoing") -} - -func (c *Client4) GetOutgoingWebhookRoute(hookID string) string { - return fmt.Sprintf(c.GetOutgoingWebhooksRoute()+"/%v", hookID) -} - -func (c *Client4) GetPreferencesRoute(userId string) string { - return fmt.Sprintf(c.GetUserRoute(userId) + "/preferences") -} - -func (c *Client4) GetUserStatusRoute(userId string) string { - return fmt.Sprintf(c.GetUserRoute(userId) + "/status") -} - -func (c *Client4) GetUserStatusesRoute() string { - return fmt.Sprintf(c.GetUsersRoute() + "/status") -} - -func (c *Client4) GetSamlRoute() string { - return fmt.Sprintf("/saml") -} - -func (c *Client4) GetLdapRoute() string { - return fmt.Sprintf("/ldap") -} - -func (c *Client4) GetBrandRoute() string { - return fmt.Sprintf("/brand") -} - -func (c *Client4) GetDataRetentionRoute() string { - return fmt.Sprintf("/data_retention") -} - -func (c *Client4) GetElasticsearchRoute() string { - return fmt.Sprintf("/elasticsearch") -} - -func (c *Client4) GetCommandsRoute() string { - return fmt.Sprintf("/commands") -} - -func (c *Client4) GetCommandRoute(commandId string) string { - return fmt.Sprintf(c.GetCommandsRoute()+"/%v", commandId) -} - -func (c *Client4) GetEmojisRoute() string { - return fmt.Sprintf("/emoji") -} - -func (c *Client4) GetEmojiRoute(emojiId string) string { - return fmt.Sprintf(c.GetEmojisRoute()+"/%v", emojiId) -} - -func (c *Client4) GetReactionsRoute() string { - return fmt.Sprintf("/reactions") -} - -func (c *Client4) GetOAuthAppsRoute() string { - return fmt.Sprintf("/oauth/apps") -} - -func (c *Client4) GetOAuthAppRoute(appId string) string { - return fmt.Sprintf("/oauth/apps/%v", appId) -} - -func (c *Client4) GetOpenGraphRoute() string { - return fmt.Sprintf("/opengraph") -} - -func (c *Client4) GetJobsRoute() string { - return fmt.Sprintf("/jobs") -} - -func (c *Client4) GetAnalyticsRoute() string { - return fmt.Sprintf("/analytics") -} - -func (c *Client4) DoApiGet(url string, etag string) (*http.Response, *AppError) { - return c.DoApiRequest(http.MethodGet, c.ApiUrl+url, "", etag) -} - -func (c *Client4) DoApiPost(url string, data string) (*http.Response, *AppError) { - return c.DoApiRequest(http.MethodPost, c.ApiUrl+url, data, "") -} - -func (c *Client4) DoApiPut(url string, data string) (*http.Response, *AppError) { - return c.DoApiRequest(http.MethodPut, c.ApiUrl+url, data, "") -} - -func (c *Client4) DoApiDelete(url string) (*http.Response, *AppError) { - return c.DoApiRequest(http.MethodDelete, c.ApiUrl+url, "", "") -} - -func (c *Client4) DoApiRequest(method, url, data, etag string) (*http.Response, *AppError) { - rq, _ := http.NewRequest(method, url, strings.NewReader(data)) - rq.Close = true - - if len(etag) > 0 { - rq.Header.Set(HEADER_ETAG_CLIENT, etag) - } - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return nil, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) - } else if rp.StatusCode == 304 { - return rp, nil - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return rp, AppErrorFromJson(rp.Body) - } else { - return rp, nil - } -} - -func (c *Client4) DoUploadFile(url string, data []byte, contentType string) (*FileUploadResponse, *Response) { - rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return nil, BuildErrorResponse(rp, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return FileUploadResponseFromJson(rp.Body), BuildResponse(rp) - } - } -} - -func (c *Client4) DoEmojiUploadFile(url string, data []byte, contentType string) (*Emoji, *Response) { - rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return nil, BuildErrorResponse(rp, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return EmojiFromJson(rp.Body), BuildResponse(rp) - } - } -} - -func (c *Client4) DoUploadImportTeam(url string, data []byte, contentType string) (map[string]string, *Response) { - rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data)) - rq.Header.Set("Content-Type", contentType) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return nil, BuildErrorResponse(rp, NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return MapFromJson(rp.Body), BuildResponse(rp) - } - } -} - -// CheckStatusOK is a convenience function for checking the standard OK response -// from the web service. -func CheckStatusOK(r *http.Response) bool { - m := MapFromJson(r.Body) - defer closeBody(r) - - if m != nil && m[STATUS] == STATUS_OK { - return true - } - - return false -} - -// Authentication Section - -// LoginById authenticates a user by user id and password. -func (c *Client4) LoginById(id string, password string) (*User, *Response) { - m := make(map[string]string) - m["id"] = id - m["password"] = password - return c.login(m) -} - -// Login authenticates a user by login id, which can be username, email or some sort -// of SSO identifier based on server configuration, and a password. -func (c *Client4) Login(loginId string, password string) (*User, *Response) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - return c.login(m) -} - -// LoginByLdap authenticates a user by LDAP id and password. -func (c *Client4) LoginByLdap(loginId string, password string) (*User, *Response) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - m["ldap_only"] = "true" - return c.login(m) -} - -// LoginWithDevice authenticates a user by login id (username, email or some sort -// of SSO identifier based on configuration), password and attaches a device id to -// the session. -func (c *Client4) LoginWithDevice(loginId string, password string, deviceId string) (*User, *Response) { - m := make(map[string]string) - m["login_id"] = loginId - m["password"] = password - m["device_id"] = deviceId - return c.login(m) -} - -func (c *Client4) login(m map[string]string) (*User, *Response) { - if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - c.AuthToken = r.Header.Get(HEADER_TOKEN) - c.AuthType = HEADER_BEARER - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// Logout terminates the current user's session. -func (c *Client4) Logout() (bool, *Response) { - if r, err := c.DoApiPost("/users/logout", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - c.AuthToken = "" - c.AuthType = HEADER_BEARER - - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// SwitchAccountType changes a user's login type from one type to another. -func (c *Client4) SwitchAccountType(switchRequest *SwitchRequest) (string, *Response) { - if r, err := c.DoApiPost(c.GetUsersRoute()+"/login/switch", switchRequest.ToJson()); err != nil { - return "", BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body)["follow_link"], BuildResponse(r) - } -} - -// User Section - -// CreateUser creates a user in the system based on the provided user struct. -func (c *Client4) CreateUser(user *User) (*User, *Response) { - if r, err := c.DoApiPost(c.GetUsersRoute(), user.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// CreateUserWithHash creates a user in the system based on the provided user struct and hash created. -func (c *Client4) CreateUserWithHash(user *User, hash, data string) (*User, *Response) { - var query string - if hash != "" && data != "" { - query = fmt.Sprintf("?d=%v&h=%v", url.QueryEscape(data), hash) - } else { - err := NewAppError("MissingHashOrData", "api.user.create_user.missing_hash_or_data.app_error", nil, "", http.StatusBadRequest) - return nil, &Response{StatusCode: err.StatusCode, Error: err} - } - if r, err := c.DoApiPost(c.GetUsersRoute()+query, user.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// CreateUserWithInviteId creates a user in the system based on the provided invited id. -func (c *Client4) CreateUserWithInviteId(user *User, inviteId string) (*User, *Response) { - var query string - if inviteId != "" { - query = fmt.Sprintf("?iid=%v", url.QueryEscape(inviteId)) - } else { - err := NewAppError("MissingInviteId", "api.user.create_user.missing_invite_id.app_error", nil, "", http.StatusBadRequest) - return nil, &Response{StatusCode: err.StatusCode, Error: err} - } - if r, err := c.DoApiPost(c.GetUsersRoute()+query, user.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// GetMe returns the logged in user. -func (c *Client4) GetMe(etag string) (*User, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(ME), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// GetUser returns a user based on the provided user id string. -func (c *Client4) GetUser(userId, etag string) (*User, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// GetUserByUsername returns a user based on the provided user name string. -func (c *Client4) GetUserByUsername(userName, etag string) (*User, *Response) { - if r, err := c.DoApiGet(c.GetUserByUsernameRoute(userName), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// GetUserByEmail returns a user based on the provided user email string. -func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response) { - if r, err := c.DoApiGet(c.GetUserByEmailRoute(email), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// AutocompleteUsersInTeam returns the users on a team based on search term. -func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag string) (*UserAutocomplete, *Response) { - query := fmt.Sprintf("?in_team=%v&name=%v", teamId, username) - if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAutocompleteFromJson(r.Body), BuildResponse(r) - } -} - -// AutocompleteUsersInChannel returns the users in a channel based on search term. -func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, etag string) (*UserAutocomplete, *Response) { - query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v", teamId, channelId, username) - if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAutocompleteFromJson(r.Body), BuildResponse(r) - } -} - -// AutocompleteUsers returns the users in the system based on search term. -func (c *Client4) AutocompleteUsers(username string, etag string) (*UserAutocomplete, *Response) { - query := fmt.Sprintf("?name=%v", username) - if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAutocompleteFromJson(r.Body), BuildResponse(r) - } -} - -// GetProfileImage gets user's profile image. Must be logged in or be a system administrator. -func (c *Client4) GetProfileImage(userId, etag string) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/image", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetProfileImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// GetUsers returns a page of users on the system. Page counting starts at 0. -func (c *Client4) GetUsers(page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersInTeam returns a page of users on a team. Page counting starts at 0. -func (c *Client4) GetUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetNewUsersInTeam returns a page of users on a team. Page counting starts at 0. -func (c *Client4) GetNewUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?sort=create_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetRecentlyActiveUsersInTeam returns a page of users on a team. Page counting starts at 0. -func (c *Client4) GetRecentlyActiveUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?sort=last_activity_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersNotInTeam returns a page of users who are not in a team. Page counting starts at 0. -func (c *Client4) GetUsersNotInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?not_in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersInChannel returns a page of users on a team. Page counting starts at 0. -func (c *Client4) GetUsersInChannel(channelId string, page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersNotInChannel returns a page of users on a team. Page counting starts at 0. -func (c *Client4) GetUsersNotInChannel(teamId, channelId string, page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?in_team=%v¬_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersWithoutTeam returns a page of users on the system that aren't on any teams. Page counting starts at 0. -func (c *Client4) GetUsersWithoutTeam(page int, perPage int, etag string) ([]*User, *Response) { - query := fmt.Sprintf("?without_team=1&page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetUsersRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersByIds returns a list of users based on the provided user ids. -func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response) { - if r, err := c.DoApiPost(c.GetUsersRoute()+"/ids", ArrayToJson(userIds)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersByUsernames returns a list of users based on the provided usernames. -func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response) { - if r, err := c.DoApiPost(c.GetUsersRoute()+"/usernames", ArrayToJson(usernames)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// SearchUsers returns a list of users based on some search criteria. -func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response) { - if r, err := c.DoApiPost(c.GetUsersRoute()+"/search", search.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserListFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateUser updates a user in the system based on the provided user struct. -func (c *Client4) UpdateUser(user *User) (*User, *Response) { - if r, err := c.DoApiPut(c.GetUserRoute(user.Id), user.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// PatchUser partially updates a user in the system. Any missing fields are not updated. -func (c *Client4) PatchUser(userId string, patch *UserPatch) (*User, *Response) { - if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/patch", patch.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateUserAuth updates a user AuthData (uthData, authService and password) in the system. -func (c *Client4) UpdateUserAuth(userId string, userAuth *UserAuth) (*UserAuth, *Response) { - if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/auth", userAuth.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAuthFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateUserMfa activates multi-factor authentication for a user if activate -// is true and a valid code is provided. If activate is false, then code is not -// required and multi-factor authentication is disabled for the user. -func (c *Client4) UpdateUserMfa(userId, code string, activate bool) (bool, *Response) { - requestBody := make(map[string]interface{}) - requestBody["activate"] = activate - requestBody["code"] = code - - if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/mfa", StringInterfaceToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// CheckUserMfa checks whether a user has MFA active on their account or not based on the -// provided login id. -func (c *Client4) CheckUserMfa(loginId string) (bool, *Response) { - requestBody := make(map[string]interface{}) - requestBody["login_id"] = loginId - - if r, err := c.DoApiPost(c.GetUsersRoute()+"/mfa", StringInterfaceToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - data := StringInterfaceFromJson(r.Body) - if mfaRequired, ok := data["mfa_required"].(bool); !ok { - return false, BuildResponse(r) - } else { - return mfaRequired, BuildResponse(r) - } - } -} - -// GenerateMfaSecret will generate a new MFA secret for a user and return it as a string and -// as a base64 encoded image QR code. -func (c *Client4) GenerateMfaSecret(userId string) (*MfaSecret, *Response) { - if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/mfa/generate", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MfaSecretFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateUserPassword updates a user's password. Must be logged in as the user or be a system administrator. -func (c *Client4) UpdateUserPassword(userId, currentPassword, newPassword string) (bool, *Response) { - requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword} - if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/password", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// UpdateUserRoles updates a user's roles in the system. A user can have "system_user" and "system_admin" roles. -func (c *Client4) UpdateUserRoles(userId, roles string) (bool, *Response) { - requestBody := map[string]string{"roles": roles} - if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/roles", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// UpdateUserActive updates status of a user whether active or not. -func (c *Client4) UpdateUserActive(userId string, active bool) (bool, *Response) { - requestBody := make(map[string]interface{}) - requestBody["active"] = active - - if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/active", StringInterfaceToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// DeleteUser deactivates a user in the system based on the provided user id string. -func (c *Client4) DeleteUser(userId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetUserRoute(userId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// SendPasswordResetEmail will send a link for password resetting to a user with the -// provided email. -func (c *Client4) SendPasswordResetEmail(email string) (bool, *Response) { - requestBody := map[string]string{"email": email} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset/send", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// ResetPassword uses a recovery code to update reset a user's password. -func (c *Client4) ResetPassword(token, newPassword string) (bool, *Response) { - requestBody := map[string]string{"token": token, "new_password": newPassword} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/password/reset", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetSessions returns a list of sessions based on the provided user id string. -func (c *Client4) GetSessions(userId, etag string) ([]*Session, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/sessions", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return SessionsFromJson(r.Body), BuildResponse(r) - } -} - -// RevokeSession revokes a user session based on the provided user id and session id strings. -func (c *Client4) RevokeSession(userId, sessionId string) (bool, *Response) { - requestBody := map[string]string{"session_id": sessionId} - if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/sessions/revoke", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// RevokeAllSessions revokes all sessions for the provided user id string. -func (c *Client4) RevokeAllSessions(userId string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/sessions/revoke/all", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// AttachDeviceId attaches a mobile device ID to the current session. -func (c *Client4) AttachDeviceId(deviceId string) (bool, *Response) { - requestBody := map[string]string{"device_id": deviceId} - if r, err := c.DoApiPut(c.GetUsersRoute()+"/sessions/device", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetTeamsUnreadForUser will return an array with TeamUnread objects that contain the amount -// of unread messages and mentions the current user has for the teams it belongs to. -// An optional team ID can be set to exclude that team from the results. Must be authenticated. -func (c *Client4) GetTeamsUnreadForUser(userId, teamIdToExclude string) ([]*TeamUnread, *Response) { - optional := "" - if teamIdToExclude != "" { - optional += fmt.Sprintf("?exclude_team=%s", url.QueryEscape(teamIdToExclude)) - } - - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/unread"+optional, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamsUnreadFromJson(r.Body), BuildResponse(r) - } -} - -// GetUserAudits returns a list of audit based on the provided user id string. -func (c *Client4) GetUserAudits(userId string, page int, perPage int, etag string) (Audits, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/audits"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return AuditsFromJson(r.Body), BuildResponse(r) - } -} - -// VerifyUserEmail will verify a user's email using the supplied token. -func (c *Client4) VerifyUserEmail(token string) (bool, *Response) { - requestBody := map[string]string{"token": token} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// SendVerificationEmail will send an email to the user with the provided email address, if -// that user exists. The email will contain a link that can be used to verify the user's -// email address. -func (c *Client4) SendVerificationEmail(email string) (bool, *Response) { - requestBody := map[string]string{"email": email} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/email/verify/send", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// SetProfileImage sets profile image of the user -func (c *Client4) SetProfileImage(userId string, data []byte) (bool, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("image", "profile.png"); err != nil { - return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if err := writer.Close(); err != nil { - return false, &Response{Error: NewAppError("SetProfileImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetUserRoute(userId)+"/image", bytes.NewReader(body.Bytes())) - rq.Header.Set("Content-Type", writer.FormDataContentType()) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - // set to http.StatusForbidden(403) - return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetUserRoute(userId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)} - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return CheckStatusOK(rp), BuildResponse(rp) - } - } -} - -// CreateUserAccessToken will generate a user access token that can be used in place -// of a session token to access the REST API. Must have the 'create_user_access_token' -// permission and if generating for another user, must have the 'edit_other_users' -// permission. A non-blank description is required. -func (c *Client4) CreateUserAccessToken(userId, description string) (*UserAccessToken, *Response) { - requestBody := map[string]string{"description": description} - if r, err := c.DoApiPost(c.GetUserRoute(userId)+"/tokens", MapToJson(requestBody)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAccessTokenFromJson(r.Body), BuildResponse(r) - } -} - -// GetUserAccessToken will get a user access token's id, description and the user_id -// of the user it is for. The actual token will not be returned. Must have the -// 'read_user_access_token' permission and if getting for another user, must have the -// 'edit_other_users' permission. -func (c *Client4) GetUserAccessToken(tokenId string) (*UserAccessToken, *Response) { - if r, err := c.DoApiGet(c.GetUserAccessTokenRoute(tokenId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAccessTokenFromJson(r.Body), BuildResponse(r) - } -} - -// GetUserAccessTokensForUser will get a paged list of user access tokens showing id, -// description and user_id for each. The actual tokens will not be returned. Must have -// the 'read_user_access_token' permission and if getting for another user, must have the -// 'edit_other_users' permission. -func (c *Client4) GetUserAccessTokensForUser(userId string, page, perPage int) ([]*UserAccessToken, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/tokens"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return UserAccessTokenListFromJson(r.Body), BuildResponse(r) - } -} - -// RevokeUserAccessToken will revoke a user access token by id. Must have the -// 'revoke_user_access_token' permission and if revoking for another user, must have the -// 'edit_other_users' permission. -func (c *Client4) RevokeUserAccessToken(tokenId string) (bool, *Response) { - requestBody := map[string]string{"token_id": tokenId} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/revoke", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// DisableUserAccessToken will disable a user access token by id. Must have the -// 'revoke_user_access_token' permission and if disabling for another user, must have the -// 'edit_other_users' permission. -func (c *Client4) DisableUserAccessToken(tokenId string) (bool, *Response) { - requestBody := map[string]string{"token_id": tokenId} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/disable", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// EnableUserAccessToken will enable a user access token by id. Must have the -// 'create_user_access_token' permission and if enabling for another user, must have the -// 'edit_other_users' permission. -func (c *Client4) EnableUserAccessToken(tokenId string) (bool, *Response) { - requestBody := map[string]string{"token_id": tokenId} - if r, err := c.DoApiPost(c.GetUsersRoute()+"/tokens/enable", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Team Section - -// CreateTeam creates a team in the system based on the provided team struct. -func (c *Client4) CreateTeam(team *Team) (*Team, *Response) { - if r, err := c.DoApiPost(c.GetTeamsRoute(), team.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamFromJson(r.Body), BuildResponse(r) - } -} - -// GetTeam returns a team based on the provided team id string. -func (c *Client4) GetTeam(teamId, etag string) (*Team, *Response) { - if r, err := c.DoApiGet(c.GetTeamRoute(teamId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamFromJson(r.Body), BuildResponse(r) - } -} - -// GetAllTeams returns all teams based on permissions. -func (c *Client4) GetAllTeams(etag string, page int, perPage int) ([]*Team, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetTeamsRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamListFromJson(r.Body), BuildResponse(r) - } -} - -// GetTeamByName returns a team based on the provided team name string. -func (c *Client4) GetTeamByName(name, etag string) (*Team, *Response) { - if r, err := c.DoApiGet(c.GetTeamByNameRoute(name), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamFromJson(r.Body), BuildResponse(r) - } -} - -// SearchTeams returns teams matching the provided search term. -func (c *Client4) SearchTeams(search *TeamSearch) ([]*Team, *Response) { - if r, err := c.DoApiPost(c.GetTeamsRoute()+"/search", search.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamListFromJson(r.Body), BuildResponse(r) - } -} - -// TeamExists returns true or false if the team exist or not. -func (c *Client4) TeamExists(name, etag string) (bool, *Response) { - if r, err := c.DoApiGet(c.GetTeamByNameRoute(name)+"/exists", etag); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapBoolFromJson(r.Body)["exists"], BuildResponse(r) - } -} - -// GetTeamsForUser returns a list of teams a user is on. Must be logged in as the user -// or be a system administrator. -func (c *Client4) GetTeamsForUser(userId, etag string) ([]*Team, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamListFromJson(r.Body), BuildResponse(r) - } -} - -// GetTeamMember returns a team member based on the provided team and user id strings. -func (c *Client4) GetTeamMember(teamId, userId, etag string) (*TeamMember, *Response) { - if r, err := c.DoApiGet(c.GetTeamMemberRoute(teamId, userId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMemberFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateTeamMemberRoles will update the roles on a team for a user. -func (c *Client4) UpdateTeamMemberRoles(teamId, userId, newRoles string) (bool, *Response) { - requestBody := map[string]string{"roles": newRoles} - if r, err := c.DoApiPut(c.GetTeamMemberRoute(teamId, userId)+"/roles", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// UpdateTeam will update a team. -func (c *Client4) UpdateTeam(team *Team) (*Team, *Response) { - if r, err := c.DoApiPut(c.GetTeamRoute(team.Id), team.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamFromJson(r.Body), BuildResponse(r) - } -} - -// PatchTeam partially updates a team. Any missing fields are not updated. -func (c *Client4) PatchTeam(teamId string, patch *TeamPatch) (*Team, *Response) { - if r, err := c.DoApiPut(c.GetTeamRoute(teamId)+"/patch", patch.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamFromJson(r.Body), BuildResponse(r) - } -} - -// SoftDeleteTeam deletes the team softly (archive only, not permanent delete). -func (c *Client4) SoftDeleteTeam(teamId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetTeamRoute(teamId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// PermanentDeleteTeam deletes the team, should only be used when needed for -// compliance and the like -func (c *Client4) PermanentDeleteTeam(teamId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetTeamRoute(teamId) + "?permanent=true"); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetTeamMembers returns team members based on the provided team id string. -func (c *Client4) GetTeamMembers(teamId string, page int, perPage int, etag string) ([]*TeamMember, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetTeamMembersRoute(teamId)+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMembersFromJson(r.Body), BuildResponse(r) - } -} - -// GetTeamMembersForUser returns the team members for a user. -func (c *Client4) GetTeamMembersForUser(userId string, etag string) ([]*TeamMember, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/teams/members", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMembersFromJson(r.Body), BuildResponse(r) - } -} - -// GetTeamMembersByIds will return an array of team members based on the -// team id and a list of user ids provided. Must be authenticated. -func (c *Client4) GetTeamMembersByIds(teamId string, userIds []string) ([]*TeamMember, *Response) { - if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMembersFromJson(r.Body), BuildResponse(r) - } -} - -// AddTeamMember adds user to a team and return a team member. -func (c *Client4) AddTeamMember(teamId, userId string) (*TeamMember, *Response) { - member := &TeamMember{TeamId: teamId, UserId: userId} - - if r, err := c.DoApiPost(c.GetTeamMembersRoute(teamId), member.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMemberFromJson(r.Body), BuildResponse(r) - } -} - -// AddTeamMemberFromInvite adds a user to a team and return a team member using an invite id -// or an invite hash/data pair. -func (c *Client4) AddTeamMemberFromInvite(hash, dataToHash, inviteId string) (*TeamMember, *Response) { - var query string - - if inviteId != "" { - query += fmt.Sprintf("?invite_id=%v", inviteId) - } - - if hash != "" && dataToHash != "" { - query += fmt.Sprintf("?hash=%v&data=%v", hash, dataToHash) - } - - if r, err := c.DoApiPost(c.GetTeamsRoute()+"/members/invite"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMemberFromJson(r.Body), BuildResponse(r) - } -} - -// AddTeamMembers adds a number of users to a team and returns the team members. -func (c *Client4) AddTeamMembers(teamId string, userIds []string) ([]*TeamMember, *Response) { - var members []*TeamMember - for _, userId := range userIds { - member := &TeamMember{TeamId: teamId, UserId: userId} - members = append(members, member) - } - - if r, err := c.DoApiPost(c.GetTeamMembersRoute(teamId)+"/batch", TeamMembersToJson(members)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamMembersFromJson(r.Body), BuildResponse(r) - } -} - -// RemoveTeamMember will remove a user from a team. -func (c *Client4) RemoveTeamMember(teamId, userId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetTeamMemberRoute(teamId, userId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetTeamStats returns a team stats based on the team id string. -// Must be authenticated. -func (c *Client4) GetTeamStats(teamId, etag string) (*TeamStats, *Response) { - if r, err := c.DoApiGet(c.GetTeamStatsRoute(teamId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamStatsFromJson(r.Body), BuildResponse(r) - } -} - -// GetTeamUnread will return a TeamUnread object that contains the amount of -// unread messages and mentions the user has for the specified team. -// Must be authenticated. -func (c *Client4) GetTeamUnread(teamId, userId string) (*TeamUnread, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetTeamRoute(teamId)+"/unread", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamUnreadFromJson(r.Body), BuildResponse(r) - } -} - -// ImportTeam will import an exported team from other app into a existing team. -func (c *Client4) ImportTeam(data []byte, filesize int, importFrom, filename, teamId string) (map[string]string, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("file", filename); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if part, err := writer.CreateFormField("filesize"); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file_size.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, strings.NewReader(strconv.Itoa(filesize))); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.file_size.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if part, err := writer.CreateFormField("importFrom"); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.import_from.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, strings.NewReader(importFrom)); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.import_from.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if err := writer.Close(); err != nil { - return nil, &Response{Error: NewAppError("UploadImportTeam", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - return c.DoUploadImportTeam(c.GetTeamImportRoute(teamId), body.Bytes(), writer.FormDataContentType()) -} - -// InviteUsersToTeam invite users by email to the team. -func (c *Client4) InviteUsersToTeam(teamId string, userEmails []string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/invite/email", ArrayToJson(userEmails)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetTeamInviteInfo returns a team object from an invite id containing sanitized information. -func (c *Client4) GetTeamInviteInfo(inviteId string) (*Team, *Response) { - if r, err := c.DoApiGet(c.GetTeamsRoute()+"/invite/"+inviteId, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return TeamFromJson(r.Body), BuildResponse(r) - } -} - -// Channel Section - -// CreateChannel creates a channel based on the provided channel struct. -func (c *Client4) CreateChannel(channel *Channel) (*Channel, *Response) { - if r, err := c.DoApiPost(c.GetChannelsRoute(), channel.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateChannel update a channel based on the provided channel struct. -func (c *Client4) UpdateChannel(channel *Channel) (*Channel, *Response) { - if r, err := c.DoApiPut(c.GetChannelRoute(channel.Id), channel.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// PatchChannel partially updates a channel. Any missing fields are not updated. -func (c *Client4) PatchChannel(channelId string, patch *ChannelPatch) (*Channel, *Response) { - if r, err := c.DoApiPut(c.GetChannelRoute(channelId)+"/patch", patch.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// RestoreChannel restores a previously deleted channel. Any missing fields are not updated. -func (c *Client4) RestoreChannel(channelId string) (*Channel, *Response) { - if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/restore", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// CreateDirectChannel creates a direct message channel based on the two user -// ids provided. -func (c *Client4) CreateDirectChannel(userId1, userId2 string) (*Channel, *Response) { - requestBody := []string{userId1, userId2} - if r, err := c.DoApiPost(c.GetChannelsRoute()+"/direct", ArrayToJson(requestBody)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// CreateGroupChannel creates a group message channel based on userIds provided -func (c *Client4) CreateGroupChannel(userIds []string) (*Channel, *Response) { - if r, err := c.DoApiPost(c.GetChannelsRoute()+"/group", ArrayToJson(userIds)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannel returns a channel based on the provided channel id string. -func (c *Client4) GetChannel(channelId, etag string) (*Channel, *Response) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelStats returns statistics for a channel. -func (c *Client4) GetChannelStats(channelId string, etag string) (*ChannelStats, *Response) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/stats", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelStatsFromJson(r.Body), BuildResponse(r) - } -} - -// GetPinnedPosts gets a list of pinned posts. -func (c *Client4) GetPinnedPosts(channelId string, etag string) (*PostList, *Response) { - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetPublicChannelsForTeam returns a list of public channels based on the provided team id string. -func (c *Client4) GetPublicChannelsForTeam(teamId string, page int, perPage int, etag string) ([]*Channel, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetChannelsForTeamRoute(teamId)+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelSliceFromJson(r.Body), BuildResponse(r) - } -} - -// GetDeletedChannelsForTeam returns a list of public channels based on the provided team id string. -func (c *Client4) GetDeletedChannelsForTeam(teamId string, page int, perPage int, etag string) ([]*Channel, *Response) { - query := fmt.Sprintf("/deleted?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetChannelsForTeamRoute(teamId)+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelSliceFromJson(r.Body), BuildResponse(r) - } -} - -// GetPublicChannelsByIdsForTeam returns a list of public channels based on provided team id string -func (c *Client4) GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) ([]*Channel, *Response) { - if r, err := c.DoApiPost(c.GetChannelsForTeamRoute(teamId)+"/ids", ArrayToJson(channelIds)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelSliceFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelsForTeamForUser returns a list channels of on a team for a user. -func (c *Client4) GetChannelsForTeamForUser(teamId, userId, etag string) ([]*Channel, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetTeamRoute(teamId)+"/channels", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelSliceFromJson(r.Body), BuildResponse(r) - } -} - -// SearchChannels returns the channels on a team matching the provided search term. -func (c *Client4) SearchChannels(teamId string, search *ChannelSearch) ([]*Channel, *Response) { - if r, err := c.DoApiPost(c.GetChannelsForTeamRoute(teamId)+"/search", search.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelSliceFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteChannel deletes channel based on the provided channel id string. -func (c *Client4) DeleteChannel(channelId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetChannelRoute(channelId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetChannelByName returns a channel based on the provided channel name and team id strings. -func (c *Client4) GetChannelByName(channelName, teamId string, etag string) (*Channel, *Response) { - if r, err := c.DoApiGet(c.GetChannelByNameRoute(channelName, teamId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelByNameForTeamName returns a channel based on the provided channel name and team name strings. -func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag string) (*Channel, *Response) { - if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelMembers gets a page of channel members. -func (c *Client4) GetChannelMembers(channelId string, page, perPage int, etag string) (*ChannelMembers, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetChannelMembersRoute(channelId)+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelMembersFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelMembersByIds gets the channel members in a channel for a list of user ids. -func (c *Client4) GetChannelMembersByIds(channelId string, userIds []string) (*ChannelMembers, *Response) { - if r, err := c.DoApiPost(c.GetChannelMembersRoute(channelId)+"/ids", ArrayToJson(userIds)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelMembersFromJson(r.Body), BuildResponse(r) - - } -} - -// GetChannelMember gets a channel member. -func (c *Client4) GetChannelMember(channelId, userId, etag string) (*ChannelMember, *Response) { - if r, err := c.DoApiGet(c.GetChannelMemberRoute(channelId, userId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelMemberFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelMembersForUser gets all the channel members for a user on a team. -func (c *Client4) GetChannelMembersForUser(userId, teamId, etag string) (*ChannelMembers, *Response) { - if r, err := c.DoApiGet(fmt.Sprintf(c.GetUserRoute(userId)+"/teams/%v/channels/members", teamId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelMembersFromJson(r.Body), BuildResponse(r) - } -} - -// ViewChannel performs a view action for a user. Synonymous with switching channels or marking channels as read by a user. -func (c *Client4) ViewChannel(userId string, view *ChannelView) (*ChannelViewResponse, *Response) { - url := fmt.Sprintf(c.GetChannelsRoute()+"/members/%v/view", userId) - if r, err := c.DoApiPost(url, view.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelViewResponseFromJson(r.Body), BuildResponse(r) - } -} - -// GetChannelUnread will return a ChannelUnread object that contains the number of -// unread messages and mentions for a user. -func (c *Client4) GetChannelUnread(channelId, userId string) (*ChannelUnread, *Response) { - if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetChannelRoute(channelId)+"/unread", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelUnreadFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateChannelRoles will update the roles on a channel for a user. -func (c *Client4) UpdateChannelRoles(channelId, userId, roles string) (bool, *Response) { - requestBody := map[string]string{"roles": roles} - if r, err := c.DoApiPut(c.GetChannelMemberRoute(channelId, userId)+"/roles", MapToJson(requestBody)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// UpdateChannelNotifyProps will update the notification properties on a channel for a user. -func (c *Client4) UpdateChannelNotifyProps(channelId, userId string, props map[string]string) (bool, *Response) { - if r, err := c.DoApiPut(c.GetChannelMemberRoute(channelId, userId)+"/notify_props", MapToJson(props)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// AddChannelMember adds user to channel and return a channel member. -func (c *Client4) AddChannelMember(channelId, userId string) (*ChannelMember, *Response) { - requestBody := map[string]string{"user_id": userId} - if r, err := c.DoApiPost(c.GetChannelMembersRoute(channelId)+"", MapToJson(requestBody)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelMemberFromJson(r.Body), BuildResponse(r) - } -} - -// AddChannelMemberWithRootId adds user to channel and return a channel member. Post add to channel message has the postRootId. -func (c *Client4) AddChannelMemberWithRootId(channelId, userId, postRootId string) (*ChannelMember, *Response) { - requestBody := map[string]string{"user_id": userId, "post_root_id": postRootId} - if r, err := c.DoApiPost(c.GetChannelMembersRoute(channelId)+"", MapToJson(requestBody)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ChannelMemberFromJson(r.Body), BuildResponse(r) - } -} - -// RemoveUserFromChannel will delete the channel member object for a user, effectively removing the user from a channel. -func (c *Client4) RemoveUserFromChannel(channelId, userId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetChannelMemberRoute(channelId, userId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Post Section - -// CreatePost creates a post based on the provided post struct. -func (c *Client4) CreatePost(post *Post) (*Post, *Response) { - if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostFromJson(r.Body), BuildResponse(r) - } -} - -// UpdatePost updates a post based on the provided post struct. -func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response) { - if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostFromJson(r.Body), BuildResponse(r) - } -} - -// PatchPost partially updates a post. Any missing fields are not updated. -func (c *Client4) PatchPost(postId string, patch *PostPatch) (*Post, *Response) { - if r, err := c.DoApiPut(c.GetPostRoute(postId)+"/patch", patch.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostFromJson(r.Body), BuildResponse(r) - } -} - -// PinPost pin a post based on provided post id string. -func (c *Client4) PinPost(postId string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/pin", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// UnpinPost unpin a post based on provided post id string. -func (c *Client4) UnpinPost(postId string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/unpin", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetPost gets a single post. -func (c *Client4) GetPost(postId string, etag string) (*Post, *Response) { - if r, err := c.DoApiGet(c.GetPostRoute(postId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostFromJson(r.Body), BuildResponse(r) - } -} - -// DeletePost deletes a post from the provided post id string. -func (c *Client4) DeletePost(postId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetPostRoute(postId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetPostThread gets a post with all the other posts in the same thread. -func (c *Client4) GetPostThread(postId string, etag string) (*PostList, *Response) { - if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/thread", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetPostsForChannel gets a page of posts with an array for ordering for a channel. -func (c *Client4) GetPostsForChannel(channelId string, page, perPage int, etag string) (*PostList, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetFlaggedPostsForUser returns flagged posts of a user based on user id string. -func (c *Client4) GetFlaggedPostsForUser(userId string, page int, perPage int) (*PostList, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/posts/flagged"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetFlaggedPostsForUserInTeam returns flagged posts in team of a user based on user id string. -func (c *Client4) GetFlaggedPostsForUserInTeam(userId string, teamId string, page int, perPage int) (*PostList, *Response) { - if len(teamId) == 0 || len(teamId) != 26 { - return nil, &Response{StatusCode: http.StatusBadRequest, Error: NewAppError("GetFlaggedPostsForUserInTeam", "model.client.get_flagged_posts_in_team.missing_parameter.app_error", nil, "", http.StatusBadRequest)} - } - - query := fmt.Sprintf("?team_id=%v&page=%v&per_page=%v", teamId, page, perPage) - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/posts/flagged"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetFlaggedPostsForUserInChannel returns flagged posts in channel of a user based on user id string. -func (c *Client4) GetFlaggedPostsForUserInChannel(userId string, channelId string, page int, perPage int) (*PostList, *Response) { - if len(channelId) == 0 || len(channelId) != 26 { - return nil, &Response{StatusCode: http.StatusBadRequest, Error: NewAppError("GetFlaggedPostsForUserInChannel", "model.client.get_flagged_posts_in_channel.missing_parameter.app_error", nil, "", http.StatusBadRequest)} - } - - query := fmt.Sprintf("?channel_id=%v&page=%v&per_page=%v", channelId, page, perPage) - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/posts/flagged"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetPostsSince gets posts created after a specified time as Unix time in milliseconds. -func (c *Client4) GetPostsSince(channelId string, time int64) (*PostList, *Response) { - query := fmt.Sprintf("?since=%v", time) - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetPostsAfter gets a page of posts that were posted after the post provided. -func (c *Client4) GetPostsAfter(channelId, postId string, page, perPage int, etag string) (*PostList, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v&after=%v", page, perPage, postId) - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// GetPostsBefore gets a page of posts that were posted before the post provided. -func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, etag string) (*PostList, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v&before=%v", page, perPage, postId) - if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/posts"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// SearchPosts returns any posts with matching terms string. -func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*PostList, *Response) { - requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch} - if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search", StringInterfaceToJson(requestBody)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PostListFromJson(r.Body), BuildResponse(r) - } -} - -// DoPostAction performs a post action. -func (c *Client4) DoPostAction(postId, actionId string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/actions/"+actionId, ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// File Section - -// UploadFile will upload a file to a channel, to be later attached to a post. -func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("files", filename); err != nil { - return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if part, err := writer.CreateFormField("channel_id"); err != nil { - return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil { - return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if err := writer.Close(); err != nil { - return nil, &Response{Error: NewAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - return c.DoUploadFile(c.GetFilesRoute(), body.Bytes(), writer.FormDataContentType()) -} - -// GetFile gets the bytes for a file by id. -func (c *Client4) GetFile(fileId string) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// DownloadFile gets the bytes for a file by id, optionally adding headers to force the browser to download it -func (c *Client4) DownloadFile(fileId string, download bool) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+fmt.Sprintf("?download=%v", download), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("DownloadFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// GetFileThumbnail gets the bytes for a file by id. -func (c *Client4) GetFileThumbnail(fileId string) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/thumbnail", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// DownloadFileThumbnail gets the bytes for a file by id, optionally adding headers to force the browser to download it. -func (c *Client4) DownloadFileThumbnail(fileId string, download bool) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+fmt.Sprintf("/thumbnail?download=%v", download), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("DownloadFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// GetFileLink gets the public link of a file by id. -func (c *Client4) GetFileLink(fileId string) (string, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/link", ""); err != nil { - return "", BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - return MapFromJson(r.Body)["link"], BuildResponse(r) - } -} - -// GetFilePreview gets the bytes for a file by id. -func (c *Client4) GetFilePreview(fileId string) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/preview", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetFilePreview", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// DownloadFilePreview gets the bytes for a file by id. -func (c *Client4) DownloadFilePreview(fileId string, download bool) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+fmt.Sprintf("/preview?download=%v", download), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("DownloadFilePreview", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// GetFileInfo gets all the file info objects. -func (c *Client4) GetFileInfo(fileId string) (*FileInfo, *Response) { - if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/info", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return FileInfoFromJson(r.Body), BuildResponse(r) - } -} - -// GetFileInfosForPost gets all the file info objects attached to a post. -func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo, *Response) { - if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/files/info", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return FileInfosFromJson(r.Body), BuildResponse(r) - } -} - -// General/System Section - -// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above. -func (c *Client4) GetPing() (string, *Response) { - if r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", ""); r != nil && r.StatusCode == 500 { - defer r.Body.Close() - return "unhealthy", BuildErrorResponse(r, err) - } else if err != nil { - return "", BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body)["status"], BuildResponse(r) - } -} - -// TestEmail will attempt to connect to the configured SMTP server. -func (c *Client4) TestEmail() (bool, *Response) { - if r, err := c.DoApiPost(c.GetTestEmailRoute(), ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetConfig will retrieve the server config with some sanitized items. -func (c *Client4) GetConfig() (*Config, *Response) { - if r, err := c.DoApiGet(c.GetConfigRoute(), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ConfigFromJson(r.Body), BuildResponse(r) - } -} - -// ReloadConfig will reload the server configuration. -func (c *Client4) ReloadConfig() (bool, *Response) { - if r, err := c.DoApiPost(c.GetConfigRoute()+"/reload", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetOldClientConfig will retrieve the parts of the server configuration needed by the -// client, formatted in the old format. -func (c *Client4) GetOldClientConfig(etag string) (map[string]string, *Response) { - if r, err := c.DoApiGet(c.GetConfigRoute()+"/client?format=old", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body), BuildResponse(r) - } -} - -// GetOldClientLicense will retrieve the parts of the server license needed by the -// client, formatted in the old format. -func (c *Client4) GetOldClientLicense(etag string) (map[string]string, *Response) { - if r, err := c.DoApiGet(c.GetLicenseRoute()+"/client?format=old", etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body), BuildResponse(r) - } -} - -// DatabaseRecycle will recycle the connections. Discard current connection and get new one. -func (c *Client4) DatabaseRecycle() (bool, *Response) { - if r, err := c.DoApiPost(c.GetDatabaseRoute()+"/recycle", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// InvalidateCaches will purge the cache and can affect the performance while is cleaning. -func (c *Client4) InvalidateCaches() (bool, *Response) { - if r, err := c.DoApiPost(c.GetCacheRoute()+"/invalidate", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// UpdateConfig will update the server configuration. -func (c *Client4) UpdateConfig(config *Config) (*Config, *Response) { - if r, err := c.DoApiPut(c.GetConfigRoute(), config.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ConfigFromJson(r.Body), BuildResponse(r) - } -} - -// UploadLicenseFile will add a license file to the system. -func (c *Client4) UploadLicenseFile(data []byte) (bool, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("license", "test-license.mattermost-license"); err != nil { - return false, &Response{Error: NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return false, &Response{Error: NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if err := writer.Close(); err != nil { - return false, &Response{Error: NewAppError("UploadLicenseFile", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetLicenseRoute(), bytes.NewReader(body.Bytes())) - rq.Header.Set("Content-Type", writer.FormDataContentType()) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetLicenseRoute(), "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)} - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return CheckStatusOK(rp), BuildResponse(rp) - } - } -} - -// RemoveLicenseFile will remove the server license it exists. Note that this will -// disable all enterprise features. -func (c *Client4) RemoveLicenseFile() (bool, *Response) { - if r, err := c.DoApiDelete(c.GetLicenseRoute()); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetAnalyticsOld will retrieve analytics using the old format. New format is not -// available but the "/analytics" endpoint is reserved for it. The "name" argument is optional -// and defaults to "standard". The "teamId" argument is optional and will limit results -// to a specific team. -func (c *Client4) GetAnalyticsOld(name, teamId string) (AnalyticsRows, *Response) { - query := fmt.Sprintf("?name=%v&teamId=%v", name, teamId) - if r, err := c.DoApiGet(c.GetAnalyticsRoute()+"/old"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return AnalyticsRowsFromJson(r.Body), BuildResponse(r) - } -} - -// Webhooks Section - -// CreateIncomingWebhook creates an incoming webhook for a channel. -func (c *Client4) CreateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response) { - if r, err := c.DoApiPost(c.GetIncomingWebhooksRoute(), hook.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return IncomingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateIncomingWebhook updates an incoming webhook for a channel. -func (c *Client4) UpdateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response) { - if r, err := c.DoApiPut(c.GetIncomingWebhookRoute(hook.Id), hook.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return IncomingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// GetIncomingWebhooks returns a page of incoming webhooks on the system. Page counting starts at 0. -func (c *Client4) GetIncomingWebhooks(page int, perPage int, etag string) ([]*IncomingWebhook, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetIncomingWebhooksRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return IncomingWebhookListFromJson(r.Body), BuildResponse(r) - } -} - -// GetIncomingWebhooksForTeam returns a page of incoming webhooks for a team. Page counting starts at 0. -func (c *Client4) GetIncomingWebhooksForTeam(teamId string, page int, perPage int, etag string) ([]*IncomingWebhook, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId) - if r, err := c.DoApiGet(c.GetIncomingWebhooksRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return IncomingWebhookListFromJson(r.Body), BuildResponse(r) - } -} - -// GetIncomingWebhook returns an Incoming webhook given the hook ID -func (c *Client4) GetIncomingWebhook(hookID string, etag string) (*IncomingWebhook, *Response) { - if r, err := c.DoApiGet(c.GetIncomingWebhookRoute(hookID), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return IncomingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteIncomingWebhook deletes and Incoming Webhook given the hook ID -func (c *Client4) DeleteIncomingWebhook(hookID string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetIncomingWebhookRoute(hookID)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// CreateOutgoingWebhook creates an outgoing webhook for a team or channel. -func (c *Client4) CreateOutgoingWebhook(hook *OutgoingWebhook) (*OutgoingWebhook, *Response) { - if r, err := c.DoApiPost(c.GetOutgoingWebhooksRoute(), hook.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateOutgoingWebhook creates an outgoing webhook for a team or channel. -func (c *Client4) UpdateOutgoingWebhook(hook *OutgoingWebhook) (*OutgoingWebhook, *Response) { - if r, err := c.DoApiPut(c.GetOutgoingWebhookRoute(hook.Id), hook.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// GetOutgoingWebhooks returns a page of outgoing webhooks on the system. Page counting starts at 0. -func (c *Client4) GetOutgoingWebhooks(page int, perPage int, etag string) ([]*OutgoingWebhook, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetOutgoingWebhooksRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookListFromJson(r.Body), BuildResponse(r) - } -} - -// GetOutgoingWebhook outgoing webhooks on the system requested by Hook Id. -func (c *Client4) GetOutgoingWebhook(hookId string) (*OutgoingWebhook, *Response) { - if r, err := c.DoApiGet(c.GetOutgoingWebhookRoute(hookId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// GetOutgoingWebhooksForChannel returns a page of outgoing webhooks for a channel. Page counting starts at 0. -func (c *Client4) GetOutgoingWebhooksForChannel(channelId string, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v&channel_id=%v", page, perPage, channelId) - if r, err := c.DoApiGet(c.GetOutgoingWebhooksRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookListFromJson(r.Body), BuildResponse(r) - } -} - -// GetOutgoingWebhooksForTeam returns a page of outgoing webhooks for a team. Page counting starts at 0. -func (c *Client4) GetOutgoingWebhooksForTeam(teamId string, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId) - if r, err := c.DoApiGet(c.GetOutgoingWebhooksRoute()+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookListFromJson(r.Body), BuildResponse(r) - } -} - -// RegenOutgoingHookToken regenerate the outgoing webhook token. -func (c *Client4) RegenOutgoingHookToken(hookId string) (*OutgoingWebhook, *Response) { - if r, err := c.DoApiPost(c.GetOutgoingWebhookRoute(hookId)+"/regen_token", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OutgoingWebhookFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteOutgoingWebhook delete the outgoing webhook on the system requested by Hook Id. -func (c *Client4) DeleteOutgoingWebhook(hookId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetOutgoingWebhookRoute(hookId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Preferences Section - -// GetPreferences returns the user's preferences. -func (c *Client4) GetPreferences(userId string) (Preferences, *Response) { - if r, err := c.DoApiGet(c.GetPreferencesRoute(userId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - preferences, _ := PreferencesFromJson(r.Body) - defer closeBody(r) - return preferences, BuildResponse(r) - } -} - -// UpdatePreferences saves the user's preferences. -func (c *Client4) UpdatePreferences(userId string, preferences *Preferences) (bool, *Response) { - if r, err := c.DoApiPut(c.GetPreferencesRoute(userId), preferences.ToJson()); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return true, BuildResponse(r) - } -} - -// DeletePreferences deletes the user's preferences. -func (c *Client4) DeletePreferences(userId string, preferences *Preferences) (bool, *Response) { - if r, err := c.DoApiPost(c.GetPreferencesRoute(userId)+"/delete", preferences.ToJson()); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return true, BuildResponse(r) - } -} - -// GetPreferencesByCategory returns the user's preferences from the provided category string. -func (c *Client4) GetPreferencesByCategory(userId string, category string) (Preferences, *Response) { - url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s", category) - if r, err := c.DoApiGet(url, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - preferences, _ := PreferencesFromJson(r.Body) - defer closeBody(r) - return preferences, BuildResponse(r) - } -} - -// GetPreferenceByCategoryAndName returns the user's preferences from the provided category and preference name string. -func (c *Client4) GetPreferenceByCategoryAndName(userId string, category string, preferenceName string) (*Preference, *Response) { - url := fmt.Sprintf(c.GetPreferencesRoute(userId)+"/%s/name/%v", category, preferenceName) - if r, err := c.DoApiGet(url, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PreferenceFromJson(r.Body), BuildResponse(r) - } -} - -// SAML Section - -// GetSamlMetadata returns metadata for the SAML configuration. -func (c *Client4) GetSamlMetadata() (string, *Response) { - if r, err := c.DoApiGet(c.GetSamlRoute()+"/metadata", ""); err != nil { - return "", BuildErrorResponse(r, err) - } else { - defer closeBody(r) - buf := new(bytes.Buffer) - buf.ReadFrom(r.Body) - return buf.String(), BuildResponse(r) - } -} - -func samlFileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, error) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("certificate", filename); err != nil { - return nil, nil, err - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, nil, err - } - - if err := writer.Close(); err != nil { - return nil, nil, err - } - - return body.Bytes(), writer, nil -} - -// UploadSamlIdpCertificate will upload an IDP certificate for SAML and set the config to use it. -func (c *Client4) UploadSamlIdpCertificate(data []byte, filename string) (bool, *Response) { - body, writer, err := samlFileToMultipart(data, filename) - if err != nil { - return false, &Response{Error: NewAppError("UploadSamlIdpCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - _, resp := c.DoUploadFile(c.GetSamlRoute()+"/certificate/idp", body, writer.FormDataContentType()) - return resp.Error == nil, resp -} - -// UploadSamlPublicCertificate will upload a public certificate for SAML and set the config to use it. -func (c *Client4) UploadSamlPublicCertificate(data []byte, filename string) (bool, *Response) { - body, writer, err := samlFileToMultipart(data, filename) - if err != nil { - return false, &Response{Error: NewAppError("UploadSamlPublicCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - _, resp := c.DoUploadFile(c.GetSamlRoute()+"/certificate/public", body, writer.FormDataContentType()) - return resp.Error == nil, resp -} - -// UploadSamlPrivateCertificate will upload a private key for SAML and set the config to use it. -func (c *Client4) UploadSamlPrivateCertificate(data []byte, filename string) (bool, *Response) { - body, writer, err := samlFileToMultipart(data, filename) - if err != nil { - return false, &Response{Error: NewAppError("UploadSamlPrivateCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - _, resp := c.DoUploadFile(c.GetSamlRoute()+"/certificate/private", body, writer.FormDataContentType()) - return resp.Error == nil, resp -} - -// DeleteSamlIdpCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML. -func (c *Client4) DeleteSamlIdpCertificate() (bool, *Response) { - if r, err := c.DoApiDelete(c.GetSamlRoute() + "/certificate/idp"); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// DeleteSamlPublicCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML. -func (c *Client4) DeleteSamlPublicCertificate() (bool, *Response) { - if r, err := c.DoApiDelete(c.GetSamlRoute() + "/certificate/public"); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// DeleteSamlPrivateCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML. -func (c *Client4) DeleteSamlPrivateCertificate() (bool, *Response) { - if r, err := c.DoApiDelete(c.GetSamlRoute() + "/certificate/private"); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetSamlCertificateStatus returns metadata for the SAML configuration. -func (c *Client4) GetSamlCertificateStatus() (*SamlCertificateStatus, *Response) { - if r, err := c.DoApiGet(c.GetSamlRoute()+"/certificate/status", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return SamlCertificateStatusFromJson(r.Body), BuildResponse(r) - } -} - -// Compliance Section - -// CreateComplianceReport creates an incoming webhook for a channel. -func (c *Client4) CreateComplianceReport(report *Compliance) (*Compliance, *Response) { - if r, err := c.DoApiPost(c.GetComplianceReportsRoute(), report.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ComplianceFromJson(r.Body), BuildResponse(r) - } -} - -// GetComplianceReports returns list of compliance reports. -func (c *Client4) GetComplianceReports(page, perPage int) (Compliances, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetComplianceReportsRoute()+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CompliancesFromJson(r.Body), BuildResponse(r) - } -} - -// GetComplianceReport returns a compliance report. -func (c *Client4) GetComplianceReport(reportId string) (*Compliance, *Response) { - if r, err := c.DoApiGet(c.GetComplianceReportRoute(reportId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ComplianceFromJson(r.Body), BuildResponse(r) - } -} - -// DownloadComplianceReport returns a full compliance report as a file. -func (c *Client4) DownloadComplianceReport(reportId string) ([]byte, *Response) { - var rq *http.Request - rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetComplianceReportRoute(reportId), nil) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return nil, &Response{Error: NewAppError("DownloadComplianceReport", "model.client.connecting.app_error", nil, err.Error(), http.StatusBadRequest)} - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else if data, err := ioutil.ReadAll(rp.Body); err != nil { - return nil, BuildErrorResponse(rp, NewAppError("DownloadComplianceReport", "model.client.read_file.app_error", nil, err.Error(), rp.StatusCode)) - } else { - return data, BuildResponse(rp) - } - } -} - -// Cluster Section - -// GetClusterStatus returns the status of all the configured cluster nodes. -func (c *Client4) GetClusterStatus() ([]*ClusterInfo, *Response) { - if r, err := c.DoApiGet(c.GetClusterRoute()+"/status", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ClusterInfosFromJson(r.Body), BuildResponse(r) - } -} - -// LDAP Section - -// SyncLdap will force a sync with the configured LDAP server. -func (c *Client4) SyncLdap() (bool, *Response) { - if r, err := c.DoApiPost(c.GetLdapRoute()+"/sync", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// TestLdap will attempt to connect to the configured LDAP server and return OK if configured -// correctly. -func (c *Client4) TestLdap() (bool, *Response) { - if r, err := c.DoApiPost(c.GetLdapRoute()+"/test", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Audits Section - -// GetAudits returns a list of audits for the whole system. -func (c *Client4) GetAudits(page int, perPage int, etag string) (Audits, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet("/audits"+query, etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return AuditsFromJson(r.Body), BuildResponse(r) - } -} - -// Brand Section - -// GetBrandImage retrieves the previously uploaded brand image. -func (c *Client4) GetBrandImage() ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetBrandRoute()+"/image", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if r.StatusCode >= 300 { - return nil, BuildErrorResponse(r, AppErrorFromJson(r.Body)) - } else if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetBrandImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// UploadBrandImage sets the brand image for the system. -func (c *Client4) UploadBrandImage(data []byte) (bool, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("image", "brand.png"); err != nil { - return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - if err := writer.Close(); err != nil { - return false, &Response{Error: NewAppError("UploadBrandImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)} - } - - rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetBrandRoute()+"/image", bytes.NewReader(body.Bytes())) - rq.Header.Set("Content-Type", writer.FormDataContentType()) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetBrandRoute()+"/image", "model.client.connecting.app_error", nil, err.Error(), http.StatusForbidden)} - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return CheckStatusOK(rp), BuildResponse(rp) - } - } -} - -// Logs Section - -// GetLogs page of logs as a string array. -func (c *Client4) GetLogs(page, perPage int) ([]string, *Response) { - query := fmt.Sprintf("?page=%v&logs_per_page=%v", page, perPage) - if r, err := c.DoApiGet("/logs"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ArrayFromJson(r.Body), BuildResponse(r) - } -} - -// PostLog is a convenience Web Service call so clients can log messages into -// the server-side logs. For example we typically log javascript error messages -// into the server-side. It returns the log message if the logging was successful. -func (c *Client4) PostLog(message map[string]string) (map[string]string, *Response) { - if r, err := c.DoApiPost("/logs", MapToJson(message)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body), BuildResponse(r) - } -} - -// OAuth Section - -// CreateOAuthApp will register a new OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. -func (c *Client4) CreateOAuthApp(app *OAuthApp) (*OAuthApp, *Response) { - if r, err := c.DoApiPost(c.GetOAuthAppsRoute(), app.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateOAuthApp -func (c *Client4) UpdateOAuthApp(app *OAuthApp) (*OAuthApp, *Response) { - if r, err := c.DoApiPut(c.GetOAuthAppRoute(app.Id), app.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppFromJson(r.Body), BuildResponse(r) - } -} - -// GetOAuthApps gets a page of registered OAuth 2.0 client applications with Mattermost acting as an OAuth 2.0 service provider. -func (c *Client4) GetOAuthApps(page, perPage int) ([]*OAuthApp, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetOAuthAppsRoute()+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppListFromJson(r.Body), BuildResponse(r) - } -} - -// GetOAuthApp gets a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. -func (c *Client4) GetOAuthApp(appId string) (*OAuthApp, *Response) { - if r, err := c.DoApiGet(c.GetOAuthAppRoute(appId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppFromJson(r.Body), BuildResponse(r) - } -} - -// GetOAuthAppInfo gets a sanitized version of a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. -func (c *Client4) GetOAuthAppInfo(appId string) (*OAuthApp, *Response) { - if r, err := c.DoApiGet(c.GetOAuthAppRoute(appId)+"/info", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteOAuthApp deletes a registered OAuth 2.0 client application. -func (c *Client4) DeleteOAuthApp(appId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetOAuthAppRoute(appId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// RegenerateOAuthAppSecret regenerates the client secret for a registered OAuth 2.0 client application. -func (c *Client4) RegenerateOAuthAppSecret(appId string) (*OAuthApp, *Response) { - if r, err := c.DoApiPost(c.GetOAuthAppRoute(appId)+"/regen_secret", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppFromJson(r.Body), BuildResponse(r) - } -} - -// GetAuthorizedOAuthAppsForUser gets a page of OAuth 2.0 client applications the user has authorized to use access their account. -func (c *Client4) GetAuthorizedOAuthAppsForUser(userId string, page, perPage int) ([]*OAuthApp, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/oauth/apps/authorized"+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return OAuthAppListFromJson(r.Body), BuildResponse(r) - } -} - -// AuthorizeOAuthApp will authorize an OAuth 2.0 client application to access a user's account and provide a redirect link to follow. -func (c *Client4) AuthorizeOAuthApp(authRequest *AuthorizeRequest) (string, *Response) { - if r, err := c.DoApiRequest(http.MethodPost, c.Url+"/oauth/authorize", authRequest.ToJson(), ""); err != nil { - return "", BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body)["redirect"], BuildResponse(r) - } -} - -// DeauthorizeOAuthApp will deauthorize an OAuth 2.0 client application from accessing a user's account. -func (c *Client4) DeauthorizeOAuthApp(appId string) (bool, *Response) { - requestData := map[string]string{"client_id": appId} - if r, err := c.DoApiRequest(http.MethodPost, c.Url+"/oauth/deauthorize", MapToJson(requestData), ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Elasticsearch Section - -// TestElasticsearch will attempt to connect to the configured Elasticsearch server and return OK if configured -// correctly. -func (c *Client4) TestElasticsearch() (bool, *Response) { - if r, err := c.DoApiPost(c.GetElasticsearchRoute()+"/test", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// PurgeElasticsearchIndexes immediately deletes all Elasticsearch indexes. -func (c *Client4) PurgeElasticsearchIndexes() (bool, *Response) { - if r, err := c.DoApiPost(c.GetElasticsearchRoute()+"/purge_indexes", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Data Retention Section - -// GetDataRetentionPolicy will get the current server data retention policy details. -func (c *Client4) GetDataRetentionPolicy() (*DataRetentionPolicy, *Response) { - if r, err := c.DoApiGet(c.GetDataRetentionRoute()+"/policy", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return DataRetentionPolicyFromJson(r.Body), BuildResponse(r) - } -} - -// Commands Section - -// CreateCommand will create a new command if the user have the right permissions. -func (c *Client4) CreateCommand(cmd *Command) (*Command, *Response) { - if r, err := c.DoApiPost(c.GetCommandsRoute(), cmd.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CommandFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateCommand updates a command based on the provided Command struct -func (c *Client4) UpdateCommand(cmd *Command) (*Command, *Response) { - if r, err := c.DoApiPut(c.GetCommandRoute(cmd.Id), cmd.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CommandFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteCommand deletes a command based on the provided command id string -func (c *Client4) DeleteCommand(commandId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetCommandRoute(commandId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// ListCommands will retrieve a list of commands available in the team. -func (c *Client4) ListCommands(teamId string, customOnly bool) ([]*Command, *Response) { - query := fmt.Sprintf("?team_id=%v&custom_only=%v", teamId, customOnly) - if r, err := c.DoApiGet(c.GetCommandsRoute()+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CommandListFromJson(r.Body), BuildResponse(r) - } -} - -// ExecuteCommand executes a given slash command. -func (c *Client4) ExecuteCommand(channelId, command string) (*CommandResponse, *Response) { - commandArgs := &CommandArgs{ - ChannelId: channelId, - Command: command, - } - if r, err := c.DoApiPost(c.GetCommandsRoute()+"/execute", commandArgs.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CommandResponseFromJson(r.Body), BuildResponse(r) - } -} - -// ExecuteCommand executes a given slash command against the specified team -// Use this when executing slash commands in a DM/GM, since the team id cannot be inferred in that case -func (c *Client4) ExecuteCommandWithTeam(channelId, teamId, command string) (*CommandResponse, *Response) { - commandArgs := &CommandArgs{ - ChannelId: channelId, - TeamId: teamId, - Command: command, - } - if r, err := c.DoApiPost(c.GetCommandsRoute()+"/execute", commandArgs.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CommandResponseFromJson(r.Body), BuildResponse(r) - } -} - -// ListCommands will retrieve a list of commands available in the team. -func (c *Client4) ListAutocompleteCommands(teamId string) ([]*Command, *Response) { - if r, err := c.DoApiGet(c.GetTeamAutoCompleteCommandsRoute(teamId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CommandListFromJson(r.Body), BuildResponse(r) - } -} - -// RegenCommandToken will create a new token if the user have the right permissions. -func (c *Client4) RegenCommandToken(commandId string) (string, *Response) { - if r, err := c.DoApiPut(c.GetCommandRoute(commandId)+"/regen_token", ""); err != nil { - return "", BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body)["token"], BuildResponse(r) - } -} - -// Status Section - -// GetUserStatus returns a user based on the provided user id string. -func (c *Client4) GetUserStatus(userId, etag string) (*Status, *Response) { - if r, err := c.DoApiGet(c.GetUserStatusRoute(userId), etag); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return StatusFromJson(r.Body), BuildResponse(r) - } -} - -// GetUsersStatusesByIds returns a list of users status based on the provided user ids. -func (c *Client4) GetUsersStatusesByIds(userIds []string) ([]*Status, *Response) { - if r, err := c.DoApiPost(c.GetUserStatusesRoute()+"/ids", ArrayToJson(userIds)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return StatusListFromJson(r.Body), BuildResponse(r) - } -} - -// UpdateUserStatus sets a user's status based on the provided user id string. -func (c *Client4) UpdateUserStatus(userId string, userStatus *Status) (*Status, *Response) { - if r, err := c.DoApiPut(c.GetUserStatusRoute(userId), userStatus.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return StatusFromJson(r.Body), BuildResponse(r) - - } -} - -// Webrtc Section - -// GetWebrtcToken returns a valid token, stun server and turn server with credentials to -// use with the Mattermost WebRTC service. -func (c *Client4) GetWebrtcToken() (*WebrtcInfoResponse, *Response) { - if r, err := c.DoApiGet("/webrtc/token", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return WebrtcInfoResponseFromJson(r.Body), BuildResponse(r) - } -} - -// Emoji Section - -// CreateEmoji will save an emoji to the server if the current user has permission -// to do so. If successful, the provided emoji will be returned with its Id field -// filled in. Otherwise, an error will be returned. -func (c *Client4) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoji, *Response) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("image", filename); err != nil { - return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0)} - } else if _, err = io.Copy(part, bytes.NewBuffer(image)); err != nil { - return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error(), 0)} - } - - if err := writer.WriteField("emoji", emoji.ToJson()); err != nil { - return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.emoji.app_error", nil, err.Error(), 0)} - } - - if err := writer.Close(); err != nil { - return nil, &Response{StatusCode: http.StatusForbidden, Error: NewAppError("CreateEmoji", "model.client.create_emoji.writer.app_error", nil, err.Error(), 0)} - } - - return c.DoEmojiUploadFile(c.GetEmojisRoute(), body.Bytes(), writer.FormDataContentType()) -} - -// GetEmojiList returns a page of custom emoji on the system. -func (c *Client4) GetEmojiList(page, perPage int) ([]*Emoji, *Response) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if r, err := c.DoApiGet(c.GetEmojisRoute()+query, ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return EmojiListFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteEmoji delete an custom emoji on the provided emoji id string. -func (c *Client4) DeleteEmoji(emojiId string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetEmojiRoute(emojiId)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetEmoji returns a custom emoji in the system on the provided emoji id string. -func (c *Client4) GetEmoji(emojiId string) (*Emoji, *Response) { - if r, err := c.DoApiGet(c.GetEmojiRoute(emojiId), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return EmojiFromJson(r.Body), BuildResponse(r) - } -} - -// GetEmojiImage returns the emoji image. -func (c *Client4) GetEmojiImage(emojiId string) ([]byte, *Response) { - if r, err := c.DoApiGet(c.GetEmojiRoute(emojiId)+"/image", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - - if data, err := ioutil.ReadAll(r.Body); err != nil { - return nil, BuildErrorResponse(r, NewAppError("GetEmojiImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)) - } else { - return data, BuildResponse(r) - } - } -} - -// Reaction Section - -// SaveReaction saves an emoji reaction for a post. Returns the saved reaction if successful, otherwise an error will be returned. -func (c *Client4) SaveReaction(reaction *Reaction) (*Reaction, *Response) { - if r, err := c.DoApiPost(c.GetReactionsRoute(), reaction.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ReactionFromJson(r.Body), BuildResponse(r) - } -} - -// GetReactions returns a list of reactions to a post. -func (c *Client4) GetReactions(postId string) ([]*Reaction, *Response) { - if r, err := c.DoApiGet(c.GetPostRoute(postId)+"/reactions", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ReactionsFromJson(r.Body), BuildResponse(r) - } -} - -// DeleteReaction deletes reaction of a user in a post. -func (c *Client4) DeleteReaction(reaction *Reaction) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetUserRoute(reaction.UserId) + c.GetPostRoute(reaction.PostId) + fmt.Sprintf("/reactions/%v", reaction.EmojiName)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Open Graph Metadata Section - -// OpenGraph return the open graph metadata for a particular url if the site have the metadata -func (c *Client4) OpenGraph(url string) (map[string]string, *Response) { - requestBody := make(map[string]string) - requestBody["url"] = url - - if r, err := c.DoApiPost(c.GetOpenGraphRoute(), MapToJson(requestBody)); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return MapFromJson(r.Body), BuildResponse(r) - } -} - -// Jobs Section - -// GetJob gets a single job. -func (c *Client4) GetJob(id string) (*Job, *Response) { - if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("/%v", id), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return JobFromJson(r.Body), BuildResponse(r) - } -} - -// Get all jobs, sorted with the job that was created most recently first. -func (c *Client4) GetJobs(page int, perPage int) ([]*Job, *Response) { - if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("?page=%v&per_page=%v", page, perPage), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return JobsFromJson(r.Body), BuildResponse(r) - } -} - -// GetJobsByType gets all jobs of a given type, sorted with the job that was created most recently first. -func (c *Client4) GetJobsByType(jobType string, page int, perPage int) ([]*Job, *Response) { - if r, err := c.DoApiGet(c.GetJobsRoute()+fmt.Sprintf("/type/%v?page=%v&per_page=%v", jobType, page, perPage), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return JobsFromJson(r.Body), BuildResponse(r) - } -} - -// CreateJob creates a job based on the provided job struct. -func (c *Client4) CreateJob(job *Job) (*Job, *Response) { - if r, err := c.DoApiPost(c.GetJobsRoute(), job.ToJson()); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return JobFromJson(r.Body), BuildResponse(r) - } -} - -// CancelJob requests the cancellation of the job with the provided Id. -func (c *Client4) CancelJob(jobId string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetJobsRoute()+fmt.Sprintf("/%v/cancel", jobId), ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// Plugin Section - -// UploadPlugin takes an io.Reader stream pointing to the contents of a .tar.gz plugin. -// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. -func (c *Client4) UploadPlugin(file io.Reader) (*Manifest, *Response) { - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - - if part, err := writer.CreateFormFile("plugin", "plugin.tar.gz"); err != nil { - return nil, &Response{Error: NewAppError("UploadPlugin", "model.client.writer.app_error", nil, err.Error(), 0)} - } else if _, err = io.Copy(part, file); err != nil { - return nil, &Response{Error: NewAppError("UploadPlugin", "model.client.writer.app_error", nil, err.Error(), 0)} - } - - if err := writer.Close(); err != nil { - return nil, &Response{Error: NewAppError("UploadPlugin", "model.client.writer.app_error", nil, err.Error(), 0)} - } - - rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetPluginsRoute(), body) - rq.Header.Set("Content-Type", writer.FormDataContentType()) - rq.Close = true - - if len(c.AuthToken) > 0 { - rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) - } - - if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil { - return nil, BuildErrorResponse(rp, NewAppError("UploadPlugin", "model.client.connecting.app_error", nil, err.Error(), 0)) - } else { - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildErrorResponse(rp, AppErrorFromJson(rp.Body)) - } else { - return ManifestFromJson(rp.Body), BuildResponse(rp) - } - } -} - -// GetPlugins will return a list of plugin manifests for currently active plugins. -// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. -func (c *Client4) GetPlugins() (*PluginsResponse, *Response) { - if r, err := c.DoApiGet(c.GetPluginsRoute(), ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return PluginsResponseFromJson(r.Body), BuildResponse(r) - } -} - -// RemovePlugin will deactivate and delete a plugin. -// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. -func (c *Client4) RemovePlugin(id string) (bool, *Response) { - if r, err := c.DoApiDelete(c.GetPluginRoute(id)); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// GetWebappPlugins will return a list of plugins that the webapp should download. -// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. -func (c *Client4) GetWebappPlugins() ([]*Manifest, *Response) { - if r, err := c.DoApiGet(c.GetPluginsRoute()+"/webapp", ""); err != nil { - return nil, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return ManifestListFromJson(r.Body), BuildResponse(r) - } -} - -// ActivatePlugin will activate an plugin installed. -// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. -func (c *Client4) ActivatePlugin(id string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/activate", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} - -// DeactivatePlugin will deactivate an active plugin. -// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. -func (c *Client4) DeactivatePlugin(id string) (bool, *Response) { - if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/deactivate", ""); err != nil { - return false, BuildErrorResponse(r, err) - } else { - defer closeBody(r) - return CheckStatusOK(r), BuildResponse(r) - } -} diff --git a/vendor/github.com/mattermost/platform/model/cluster_discovery.go b/vendor/github.com/mattermost/platform/model/cluster_discovery.go deleted file mode 100644 index 89e5fc95..00000000 --- a/vendor/github.com/mattermost/platform/model/cluster_discovery.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "os" -) - -const ( - CDS_OFFLINE_AFTER_MILLIS = 1000 * 60 * 30 // 30 minutes - CDS_TYPE_APP = "mattermost_app" -) - -type ClusterDiscovery struct { - Id string `json:"id"` - Type string `json:"type"` - ClusterName string `json:"cluster_name"` - Hostname string `json:"hostname"` - GossipPort int32 `json:"gossip_port"` - Port int32 `json:"port"` - CreateAt int64 `json:"create_at"` - LastPingAt int64 `json:"last_ping_at"` -} - -func (o *ClusterDiscovery) PreSave() { - if o.Id == "" { - o.Id = NewId() - } - - if o.CreateAt == 0 { - o.CreateAt = GetMillis() - o.LastPingAt = o.CreateAt - } -} - -func (o *ClusterDiscovery) AutoFillHostname() { - // attempt to set the hostname from the OS - if len(o.Hostname) == 0 { - if hn, err := os.Hostname(); err == nil { - o.Hostname = hn - } - } -} - -func (o *ClusterDiscovery) AutoFillIpAddress() { - // attempt to set the hostname to the first non-local IP address - if len(o.Hostname) == 0 { - o.Hostname = GetServerIpAddress() - } -} - -func (o *ClusterDiscovery) IsEqual(in *ClusterDiscovery) bool { - if in == nil { - return false - } - - if o.Type != in.Type { - return false - } - - if o.ClusterName != in.ClusterName { - return false - } - - if o.Hostname != in.Hostname { - return false - } - - return true -} - -func FilterClusterDiscovery(vs []*ClusterDiscovery, f func(*ClusterDiscovery) bool) []*ClusterDiscovery { - copy := make([]*ClusterDiscovery, 0) - for _, v := range vs { - if f(v) { - copy = append(copy, v) - } - } - - return copy -} - -func (o *ClusterDiscovery) IsValid() *AppError { - if len(o.Id) != 26 { - return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ClusterName) == 0 { - return NewAppError("ClusterDiscovery.IsValid", "ClusterName must be set", nil, "", http.StatusBadRequest) - } - - if len(o.Type) == 0 { - return NewAppError("ClusterDiscovery.IsValid", "Type must be set", nil, "", http.StatusBadRequest) - } - - if len(o.Hostname) == 0 { - return NewAppError("ClusterDiscovery.IsValid", "Hostname must be set", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("ClusterDiscovery.IsValid", "CreateAt must be set", nil, "", http.StatusBadRequest) - } - - if o.LastPingAt == 0 { - return NewAppError("ClusterDiscovery.IsValid", "LastPingAt must be set", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (o *ClusterDiscovery) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } - - return string(b) -} - -func ClusterDiscoveryFromJson(data io.Reader) *ClusterDiscovery { - decoder := json.NewDecoder(data) - var me ClusterDiscovery - err := decoder.Decode(&me) - if err == nil { - return &me - } - - return nil -} diff --git a/vendor/github.com/mattermost/platform/model/cluster_info.go b/vendor/github.com/mattermost/platform/model/cluster_info.go deleted file mode 100644 index c4f7e89a..00000000 --- a/vendor/github.com/mattermost/platform/model/cluster_info.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "strings" -) - -type ClusterInfo struct { - Id string `json:"id"` - Version string `json:"version"` - ConfigHash string `json:"config_hash"` - IpAddress string `json:"ipaddress"` - Hostname string `json:"hostname"` -} - -func (me *ClusterInfo) ToJson() string { - b, err := json.Marshal(me) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (me *ClusterInfo) Copy() *ClusterInfo { - json := me.ToJson() - return ClusterInfoFromJson(strings.NewReader(json)) -} - -func ClusterInfoFromJson(data io.Reader) *ClusterInfo { - decoder := json.NewDecoder(data) - var me ClusterInfo - err := decoder.Decode(&me) - if err == nil { - return &me - } else { - return nil - } -} - -func ClusterInfosToJson(objmap []*ClusterInfo) string { - if b, err := json.Marshal(objmap); err != nil { - return "" - } else { - return string(b) - } -} - -func ClusterInfosFromJson(data io.Reader) []*ClusterInfo { - decoder := json.NewDecoder(data) - - var objmap []*ClusterInfo - if err := decoder.Decode(&objmap); err != nil { - return make([]*ClusterInfo, 0) - } else { - return objmap - } -} diff --git a/vendor/github.com/mattermost/platform/model/cluster_message.go b/vendor/github.com/mattermost/platform/model/cluster_message.go deleted file mode 100644 index a6dec2e7..00000000 --- a/vendor/github.com/mattermost/platform/model/cluster_message.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - CLUSTER_EVENT_PUBLISH = "publish" - CLUSTER_EVENT_UPDATE_STATUS = "update_status" - CLUSTER_EVENT_INVALIDATE_ALL_CACHES = "inv_all_caches" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS = "inv_reactions" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOK = "inv_webhook" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS = "inv_channel_posts" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS = "inv_channel_members_notify_props" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS = "inv_channel_members" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME = "inv_channel_name" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel" - CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER = "inv_user" - CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user" - - CLUSTER_SEND_BEST_EFFORT = "best_effort" - CLUSTER_SEND_RELIABLE = "reliable" -) - -type ClusterMessage struct { - Event string `json:"event"` - SendType string `json:"-"` - WaitForAllToSend bool `json:"-"` - Data string `json:"data,omitempty"` - Props map[string]string `json:"props,omitempty"` -} - -func (o *ClusterMessage) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ClusterMessageFromJson(data io.Reader) *ClusterMessage { - decoder := json.NewDecoder(data) - var o ClusterMessage - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/cluster_stats.go b/vendor/github.com/mattermost/platform/model/cluster_stats.go deleted file mode 100644 index 879487c6..00000000 --- a/vendor/github.com/mattermost/platform/model/cluster_stats.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type ClusterStats struct { - Id string `json:"id"` - TotalWebsocketConnections int `json:"total_websocket_connections"` - TotalReadDbConnections int `json:"total_read_db_connections"` - TotalMasterDbConnections int `json:"total_master_db_connections"` -} - -func (me *ClusterStats) ToJson() string { - b, err := json.Marshal(me) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ClusterStatsFromJson(data io.Reader) *ClusterStats { - decoder := json.NewDecoder(data) - var me ClusterStats - err := decoder.Decode(&me) - if err == nil { - return &me - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/command.go b/vendor/github.com/mattermost/platform/model/command.go deleted file mode 100644 index 69da41c1..00000000 --- a/vendor/github.com/mattermost/platform/model/command.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "strings" -) - -const ( - COMMAND_METHOD_POST = "P" - COMMAND_METHOD_GET = "G" - MIN_TRIGGER_LENGTH = 1 - MAX_TRIGGER_LENGTH = 128 -) - -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 NewAppError("Command.IsValid", "model.command.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Token) != 26 { - return NewAppError("Command.IsValid", "model.command.is_valid.token.app_error", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("Command.IsValid", "model.command.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("Command.IsValid", "model.command.is_valid.update_at.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.CreatorId) != 26 { - return NewAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.TeamId) != 26 { - return NewAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Trigger) < MIN_TRIGGER_LENGTH || len(o.Trigger) > MAX_TRIGGER_LENGTH || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") { - return NewAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.URL) == 0 || len(o.URL) > 1024 { - return NewAppError("Command.IsValid", "model.command.is_valid.url.app_error", nil, "", http.StatusBadRequest) - } - - if !IsValidHttpUrl(o.URL) { - return NewAppError("Command.IsValid", "model.command.is_valid.url_http.app_error", nil, "", http.StatusBadRequest) - } - - if !(o.Method == COMMAND_METHOD_GET || o.Method == COMMAND_METHOD_POST) { - return NewAppError("Command.IsValid", "model.command.is_valid.method.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.DisplayName) > 64 { - return NewAppError("Command.IsValid", "model.command.is_valid.display_name.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Description) > 128 { - return NewAppError("Command.IsValid", "model.command.is_valid.description.app_error", nil, "", http.StatusBadRequest) - } - - 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_args.go b/vendor/github.com/mattermost/platform/model/command_args.go deleted file mode 100644 index 76b03a2f..00000000 --- a/vendor/github.com/mattermost/platform/model/command_args.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - - goi18n "github.com/nicksnyder/go-i18n/i18n" -) - -type CommandArgs struct { - UserId string `json:"user_id"` - ChannelId string `json:"channel_id"` - TeamId string `json:"team_id"` - RootId string `json:"root_id"` - ParentId string `json:"parent_id"` - Command string `json:"command"` - SiteURL string `json:"-"` - T goi18n.TranslateFunc `json:"-"` - Session Session `json:"-"` -} - -func (o *CommandArgs) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func CommandArgsFromJson(data io.Reader) *CommandArgs { - decoder := json.NewDecoder(data) - var o CommandArgs - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/command_response.go b/vendor/github.com/mattermost/platform/model/command_response.go deleted file mode 100644 index a3a171ce..00000000 --- a/vendor/github.com/mattermost/platform/model/command_response.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "io/ioutil" - "strings" -) - -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"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - Type string `json:"type"` - Props StringInterface `json:"props"` - GotoLocation string `json:"goto_location"` - Attachments []*SlackAttachment `json:"attachments"` -} - -func (o *CommandResponse) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func CommandResponseFromHTTPBody(contentType string, body io.Reader) *CommandResponse { - if strings.TrimSpace(strings.Split(contentType, ";")[0]) == "application/json" { - return CommandResponseFromJson(body) - } - if b, err := ioutil.ReadAll(body); err == nil { - return CommandResponseFromPlainText(string(b)) - } - return nil -} - -func CommandResponseFromPlainText(text string) *CommandResponse { - return &CommandResponse{ - Text: text, - } -} - -func CommandResponseFromJson(data io.Reader) *CommandResponse { - decoder := json.NewDecoder(data) - var o CommandResponse - - if err := decoder.Decode(&o); err != nil { - return nil - } - - o.Attachments = StringifySlackFieldValue(o.Attachments) - - return &o -} diff --git a/vendor/github.com/mattermost/platform/model/command_webhook.go b/vendor/github.com/mattermost/platform/model/command_webhook.go deleted file mode 100644 index 0b00e00b..00000000 --- a/vendor/github.com/mattermost/platform/model/command_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "net/http" -) - -type CommandWebhook struct { - Id string - CreateAt int64 - CommandId string - UserId string - ChannelId string - RootId string - ParentId string - UseCount int -} - -const ( - COMMAND_WEBHOOK_LIFETIME = 1000 * 60 * 30 -) - -func (o *CommandWebhook) PreSave() { - if o.Id == "" { - o.Id = NewId() - } - - if o.CreateAt == 0 { - o.CreateAt = GetMillis() - } -} - -func (o *CommandWebhook) IsValid() *AppError { - if len(o.Id) != 26 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.id.app_error", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.CommandId) != 26 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.command_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.UserId) != 26 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ChannelId) != 26 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.channel_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.RootId) != 0 && len(o.RootId) != 26 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.root_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ParentId) != 0 && len(o.ParentId) != 26 { - return NewAppError("CommandWebhook.IsValid", "model.command_hook.parent_id.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} diff --git a/vendor/github.com/mattermost/platform/model/compliance.go b/vendor/github.com/mattermost/platform/model/compliance.go deleted file mode 100644 index 3134ddba..00000000 --- a/vendor/github.com/mattermost/platform/model/compliance.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "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 NewAppError("Compliance.IsValid", "model.compliance.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if me.CreateAt == 0 { - return NewAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) - } - - if len(me.Desc) > 512 || len(me.Desc) == 0 { - return NewAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "", http.StatusBadRequest) - } - - if me.StartAt == 0 { - return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_at.app_error", nil, "", http.StatusBadRequest) - } - - if me.EndAt == 0 { - return NewAppError("Compliance.IsValid", "model.compliance.is_valid.end_at.app_error", nil, "", http.StatusBadRequest) - } - - if me.EndAt <= me.StartAt { - return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_end_at.app_error", nil, "", http.StatusBadRequest) - } - - 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 deleted file mode 100644 index 3751c586..00000000 --- a/vendor/github.com/mattermost/platform/model/compliance_post.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "regexp" - "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 - PostFileIds string -} - -func CompliancePostHeader() []string { - return []string{ - "TeamName", - "TeamDisplayName", - - "ChannelName", - "ChannelDisplayName", - - "UserUsername", - "UserEmail", - "UserNickname", - - "PostId", - "PostCreateAt", - "PostUpdateAt", - "PostDeleteAt", - "PostRootId", - "PostParentId", - "PostOriginalId", - "PostMessage", - "PostType", - "PostProps", - "PostHashtags", - "PostFileIds", - } -} - -func cleanComplianceStrings(in string) string { - if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched { - return "'" + in - - } else { - return in - } -} - -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{ - cleanComplianceStrings(me.TeamName), - cleanComplianceStrings(me.TeamDisplayName), - - cleanComplianceStrings(me.ChannelName), - cleanComplianceStrings(me.ChannelDisplayName), - - cleanComplianceStrings(me.UserUsername), - cleanComplianceStrings(me.UserEmail), - cleanComplianceStrings(me.UserNickname), - - me.PostId, - time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339), - postUpdateAt, - postDeleteAt, - - me.PostRootId, - me.PostParentId, - me.PostOriginalId, - cleanComplianceStrings(me.PostMessage), - me.PostType, - me.PostProps, - me.PostHashtags, - me.PostFileIds, - } -} diff --git a/vendor/github.com/mattermost/platform/model/config.go b/vendor/github.com/mattermost/platform/model/config.go deleted file mode 100644 index fb34d1a0..00000000 --- a/vendor/github.com/mattermost/platform/model/config.go +++ /dev/null @@ -1,2168 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "net/url" - "strings" - "time" -) - -const ( - CONN_SECURITY_NONE = "" - CONN_SECURITY_PLAIN = "PLAIN" - CONN_SECURITY_TLS = "TLS" - CONN_SECURITY_STARTTLS = "STARTTLS" - - IMAGE_DRIVER_LOCAL = "local" - IMAGE_DRIVER_S3 = "amazons3" - - DATABASE_DRIVER_MYSQL = "mysql" - DATABASE_DRIVER_POSTGRES = "postgres" - - MINIO_ACCESS_KEY = "minioaccesskey" - MINIO_SECRET_KEY = "miniosecretkey" - MINIO_BUCKET = "mattermost-test" - - PASSWORD_MAXIMUM_LENGTH = 64 - PASSWORD_MINIMUM_LENGTH = 5 - - SERVICE_GITLAB = "gitlab" - SERVICE_GOOGLE = "google" - SERVICE_OFFICE365 = "office365" - - WEBSERVER_MODE_REGULAR = "regular" - WEBSERVER_MODE_GZIP = "gzip" - WEBSERVER_MODE_DISABLED = "disabled" - - GENERIC_NO_CHANNEL_NOTIFICATION = "generic_no_channel" - GENERIC_NOTIFICATION = "generic" - FULL_NOTIFICATION = "full" - - DIRECT_MESSAGE_ANY = "any" - DIRECT_MESSAGE_TEAM = "team" - - SHOW_USERNAME = "username" - SHOW_NICKNAME_FULLNAME = "nickname_full_name" - SHOW_FULLNAME = "full_name" - - PERMISSIONS_ALL = "all" - PERMISSIONS_CHANNEL_ADMIN = "channel_admin" - PERMISSIONS_TEAM_ADMIN = "team_admin" - PERMISSIONS_SYSTEM_ADMIN = "system_admin" - - FAKE_SETTING = "********************************" - - RESTRICT_EMOJI_CREATION_ALL = "all" - RESTRICT_EMOJI_CREATION_ADMIN = "admin" - RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" - - PERMISSIONS_DELETE_POST_ALL = "all" - PERMISSIONS_DELETE_POST_TEAM_ADMIN = "team_admin" - PERMISSIONS_DELETE_POST_SYSTEM_ADMIN = "system_admin" - - ALLOW_EDIT_POST_ALWAYS = "always" - ALLOW_EDIT_POST_NEVER = "never" - ALLOW_EDIT_POST_TIME_LIMIT = "time_limit" - - EMAIL_BATCHING_BUFFER_SIZE = 256 - EMAIL_BATCHING_INTERVAL = 30 - - EMAIL_NOTIFICATION_CONTENTS_FULL = "full" - EMAIL_NOTIFICATION_CONTENTS_GENERIC = "generic" - - SITENAME_MAX_LENGTH = 30 - - SERVICE_SETTINGS_DEFAULT_SITE_URL = "" - SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE = "" - SERVICE_SETTINGS_DEFAULT_TLS_KEY_FILE = "" - SERVICE_SETTINGS_DEFAULT_READ_TIMEOUT = 300 - SERVICE_SETTINGS_DEFAULT_WRITE_TIMEOUT = 300 - SERVICE_SETTINGS_DEFAULT_MAX_LOGIN_ATTEMPTS = 10 - SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM = "" - SERVICE_SETTINGS_DEFAULT_LISTEN_AND_ADDRESS = ":8065" - - TEAM_SETTINGS_DEFAULT_MAX_USERS_PER_TEAM = 50 - TEAM_SETTINGS_DEFAULT_CUSTOM_BRAND_TEXT = "" - TEAM_SETTINGS_DEFAULT_CUSTOM_DESCRIPTION_TEXT = "" - TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT = 300 - - SQL_SETTINGS_DEFAULT_DATA_SOURCE = "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s" - - EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION = "" - - SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/" - SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/" - SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/" - SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/" - SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/" - SUPPORT_SETTINGS_DEFAULT_ADMINISTRATORS_GUIDE_LINK = "https://about.mattermost.com/administrators-guide/" - SUPPORT_SETTINGS_DEFAULT_TROUBLESHOOTING_FORUM_LINK = "https://about.mattermost.com/troubleshooting-forum/" - SUPPORT_SETTINGS_DEFAULT_COMMERCIAL_SUPPORT_LINK = "https://about.mattermost.com/commercial-support/" - SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com" - - LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = "" - LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME = "" - - SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = "" - SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = "" - SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE = "" - SAML_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE = "" - SAML_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE = "" - SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE = "" - SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = "" - - NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://about.mattermost.com/downloads/" - NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-android-app/" - NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-ios-app/" - - WEBRTC_SETTINGS_DEFAULT_STUN_URI = "" - WEBRTC_SETTINGS_DEFAULT_TURN_URI = "" - - ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS = 2500 - - ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR = "#f2a93b" - ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR = "#333333" - - TEAM_SETTINGS_DEFAULT_TEAM_TEXT = "default" - - ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL = "" - ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME = "" - ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD = "" - ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS = 1 - ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS = 1 - ELASTICSEARCH_SETTINGS_DEFAULT_AGGREGATE_POSTS_AFTER_DAYS = 365 - ELASTICSEARCH_SETTINGS_DEFAULT_POSTS_AGGREGATOR_JOB_START_TIME = "03:00" - ELASTICSEARCH_SETTINGS_DEFAULT_INDEX_PREFIX = "" - ELASTICSEARCH_SETTINGS_DEFAULT_LIVE_INDEXING_BATCH_SIZE = 1 - ELASTICSEARCH_SETTINGS_DEFAULT_BULK_INDEXING_TIME_WINDOW_SECONDS = 3600 - ELASTICSEARCH_SETTINGS_DEFAULT_REQUEST_TIMEOUT_SECONDS = 30 - - DATA_RETENTION_SETTINGS_DEFAULT_MESSAGE_RETENTION_DAYS = 365 - DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS = 365 - DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME = "02:00" - - PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins" - PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins" -) - -type ServiceSettings struct { - SiteURL *string - LicenseFileLocation *string - ListenAddress *string - ConnectionSecurity *string - TLSCertFile *string - TLSKeyFile *string - UseLetsEncrypt *bool - LetsEncryptCertificateCacheFile *string - Forward80To443 *bool - ReadTimeout *int - WriteTimeout *int - MaximumLoginAttempts *int - GoroutineHealthThreshold *int - GoogleDeveloperKey string - EnableOAuthServiceProvider bool - EnableIncomingWebhooks bool - EnableOutgoingWebhooks bool - EnableCommands *bool - EnableOnlyAdminIntegrations *bool - EnablePostUsernameOverride bool - EnablePostIconOverride bool - EnableAPIv3 *bool - EnableLinkPreviews *bool - EnableTesting bool - EnableDeveloper *bool - EnableSecurityFixAlert *bool - EnableInsecureOutgoingConnections *bool - AllowedUntrustedInternalConnections *string - EnableMultifactorAuthentication *bool - EnforceMultifactorAuthentication *bool - EnableUserAccessTokens *bool - AllowCorsFrom *string - SessionLengthWebInDays *int - SessionLengthMobileInDays *int - SessionLengthSSOInDays *int - SessionCacheInMinutes *int - SessionIdleTimeoutInMinutes *int - WebsocketSecurePort *int - WebsocketPort *int - WebserverMode *string - EnableCustomEmoji *bool - EnableEmojiPicker *bool - RestrictCustomEmojiCreation *string - RestrictPostDelete *string - AllowEditPost *string - PostEditTimeLimit *int - TimeBetweenUserTypingUpdatesMilliseconds *int64 - EnablePostSearch *bool - EnableUserTypingMessages *bool - EnableChannelViewedMessages *bool - EnableUserStatuses *bool - ExperimentalEnableAuthenticationTransfer *bool - ClusterLogTimeoutMilliseconds *int - CloseUnusedDirectMessages *bool - EnablePreviewFeatures *bool - EnableTutorial *bool -} - -func (s *ServiceSettings) SetDefaults() { - if s.SiteURL == nil { - s.SiteURL = NewString(SERVICE_SETTINGS_DEFAULT_SITE_URL) - } - - if s.LicenseFileLocation == nil { - s.LicenseFileLocation = NewString("") - } - - if s.ListenAddress == nil { - s.ListenAddress = NewString(SERVICE_SETTINGS_DEFAULT_LISTEN_AND_ADDRESS) - } - - if s.EnableAPIv3 == nil { - s.EnableAPIv3 = NewBool(true) - } - - if s.EnableLinkPreviews == nil { - s.EnableLinkPreviews = NewBool(false) - } - - if s.EnableDeveloper == nil { - s.EnableDeveloper = NewBool(false) - } - - if s.EnableSecurityFixAlert == nil { - s.EnableSecurityFixAlert = NewBool(true) - } - - if s.EnableInsecureOutgoingConnections == nil { - s.EnableInsecureOutgoingConnections = NewBool(false) - } - - if s.AllowedUntrustedInternalConnections == nil { - s.AllowedUntrustedInternalConnections = new(string) - } - - if s.EnableMultifactorAuthentication == nil { - s.EnableMultifactorAuthentication = NewBool(false) - } - - if s.EnforceMultifactorAuthentication == nil { - s.EnforceMultifactorAuthentication = NewBool(false) - } - - if s.EnableUserAccessTokens == nil { - s.EnableUserAccessTokens = NewBool(false) - } - - if s.GoroutineHealthThreshold == nil { - s.GoroutineHealthThreshold = NewInt(-1) - } - - if s.ConnectionSecurity == nil { - s.ConnectionSecurity = NewString("") - } - - if s.TLSKeyFile == nil { - s.TLSKeyFile = NewString(SERVICE_SETTINGS_DEFAULT_TLS_KEY_FILE) - } - - if s.TLSCertFile == nil { - s.TLSCertFile = NewString(SERVICE_SETTINGS_DEFAULT_TLS_CERT_FILE) - } - - if s.UseLetsEncrypt == nil { - s.UseLetsEncrypt = NewBool(false) - } - - if s.LetsEncryptCertificateCacheFile == nil { - s.LetsEncryptCertificateCacheFile = NewString("./config/letsencrypt.cache") - } - - if s.ReadTimeout == nil { - s.ReadTimeout = NewInt(SERVICE_SETTINGS_DEFAULT_READ_TIMEOUT) - } - - if s.WriteTimeout == nil { - s.WriteTimeout = NewInt(SERVICE_SETTINGS_DEFAULT_WRITE_TIMEOUT) - } - - if s.MaximumLoginAttempts == nil { - s.MaximumLoginAttempts = NewInt(SERVICE_SETTINGS_DEFAULT_MAX_LOGIN_ATTEMPTS) - } - - if s.Forward80To443 == nil { - s.Forward80To443 = NewBool(false) - } - - if s.TimeBetweenUserTypingUpdatesMilliseconds == nil { - s.TimeBetweenUserTypingUpdatesMilliseconds = NewInt64(5000) - } - - if s.EnablePostSearch == nil { - s.EnablePostSearch = NewBool(true) - } - - if s.EnableUserTypingMessages == nil { - s.EnableUserTypingMessages = NewBool(true) - } - - if s.EnableChannelViewedMessages == nil { - s.EnableChannelViewedMessages = NewBool(true) - } - - if s.EnableUserStatuses == nil { - s.EnableUserStatuses = NewBool(true) - } - - if s.ClusterLogTimeoutMilliseconds == nil { - s.ClusterLogTimeoutMilliseconds = NewInt(2000) - } - - if s.CloseUnusedDirectMessages == nil { - s.CloseUnusedDirectMessages = NewBool(false) - } - - if s.EnableTutorial == nil { - s.EnableTutorial = NewBool(true) - } - - if s.SessionLengthWebInDays == nil { - s.SessionLengthWebInDays = NewInt(30) - } - - if s.SessionLengthMobileInDays == nil { - s.SessionLengthMobileInDays = NewInt(30) - } - - if s.SessionLengthSSOInDays == nil { - s.SessionLengthSSOInDays = NewInt(30) - } - - if s.SessionCacheInMinutes == nil { - s.SessionCacheInMinutes = NewInt(10) - } - - if s.SessionIdleTimeoutInMinutes == nil { - s.SessionIdleTimeoutInMinutes = NewInt(0) - } - - if s.EnableCommands == nil { - s.EnableCommands = NewBool(false) - } - - if s.EnableOnlyAdminIntegrations == nil { - s.EnableOnlyAdminIntegrations = NewBool(true) - } - - if s.WebsocketPort == nil { - s.WebsocketPort = NewInt(80) - } - - if s.WebsocketSecurePort == nil { - s.WebsocketSecurePort = NewInt(443) - } - - if s.AllowCorsFrom == nil { - s.AllowCorsFrom = NewString(SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM) - } - - if s.WebserverMode == nil { - s.WebserverMode = NewString("gzip") - } else if *s.WebserverMode == "regular" { - *s.WebserverMode = "gzip" - } - - if s.EnableCustomEmoji == nil { - s.EnableCustomEmoji = NewBool(false) - } - - if s.EnableEmojiPicker == nil { - s.EnableEmojiPicker = NewBool(true) - } - - if s.RestrictCustomEmojiCreation == nil { - s.RestrictCustomEmojiCreation = NewString(RESTRICT_EMOJI_CREATION_ALL) - } - - if s.RestrictPostDelete == nil { - s.RestrictPostDelete = NewString(PERMISSIONS_DELETE_POST_ALL) - } - - if s.AllowEditPost == nil { - s.AllowEditPost = NewString(ALLOW_EDIT_POST_ALWAYS) - } - - if s.ExperimentalEnableAuthenticationTransfer == nil { - s.ExperimentalEnableAuthenticationTransfer = NewBool(true) - } - - if s.PostEditTimeLimit == nil { - s.PostEditTimeLimit = NewInt(300) - } - - if s.EnablePreviewFeatures == nil { - s.EnablePreviewFeatures = NewBool(true) - } -} - -type ClusterSettings struct { - Enable *bool - ClusterName *string - OverrideHostname *string - UseIpAddress *bool - UseExperimentalGossip *bool - ReadOnlyConfig *bool - GossipPort *int - StreamingPort *int -} - -func (s *ClusterSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(false) - } - - if s.ClusterName == nil { - s.ClusterName = NewString("") - } - - if s.OverrideHostname == nil { - s.OverrideHostname = NewString("") - } - - if s.UseIpAddress == nil { - s.UseIpAddress = NewBool(true) - } - - if s.UseExperimentalGossip == nil { - s.UseExperimentalGossip = NewBool(false) - } - - if s.ReadOnlyConfig == nil { - s.ReadOnlyConfig = NewBool(true) - } - - if s.GossipPort == nil { - s.GossipPort = NewInt(8074) - } - - if s.StreamingPort == nil { - s.StreamingPort = NewInt(8075) - } -} - -type MetricsSettings struct { - Enable *bool - BlockProfileRate *int - ListenAddress *string -} - -func (s *MetricsSettings) SetDefaults() { - if s.ListenAddress == nil { - s.ListenAddress = NewString(":8067") - } - - if s.Enable == nil { - s.Enable = NewBool(false) - } - - if s.BlockProfileRate == nil { - s.BlockProfileRate = NewInt(0) - } -} - -type AnalyticsSettings struct { - MaxUsersForStatistics *int -} - -func (s *AnalyticsSettings) SetDefaults() { - if s.MaxUsersForStatistics == nil { - s.MaxUsersForStatistics = NewInt(ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS) - } -} - -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 - DataSourceSearchReplicas []string - MaxIdleConns *int - MaxOpenConns *int - Trace bool - AtRestEncryptKey string - QueryTimeout *int -} - -func (s *SqlSettings) SetDefaults() { - if s.DriverName == nil { - s.DriverName = NewString(DATABASE_DRIVER_MYSQL) - } - - if s.DataSource == nil { - s.DataSource = NewString(SQL_SETTINGS_DEFAULT_DATA_SOURCE) - } - - if len(s.AtRestEncryptKey) == 0 { - s.AtRestEncryptKey = NewRandomString(32) - } - - if s.MaxIdleConns == nil { - s.MaxIdleConns = NewInt(20) - } - - if s.MaxOpenConns == nil { - s.MaxOpenConns = NewInt(300) - } - - if s.QueryTimeout == nil { - s.QueryTimeout = NewInt(30) - } -} - -type LogSettings struct { - EnableConsole bool - ConsoleLevel string - EnableFile bool - FileLevel string - FileFormat string - FileLocation string - EnableWebhookDebugging bool - EnableDiagnostics *bool -} - -func (s *LogSettings) SetDefaults() { - if s.EnableDiagnostics == nil { - s.EnableDiagnostics = NewBool(true) - } -} - -type PasswordSettings struct { - MinimumLength *int - Lowercase *bool - Number *bool - Uppercase *bool - Symbol *bool -} - -func (s *PasswordSettings) SetDefaults() { - if s.MinimumLength == nil { - s.MinimumLength = NewInt(PASSWORD_MINIMUM_LENGTH) - } - - if s.Lowercase == nil { - s.Lowercase = NewBool(false) - } - - if s.Number == nil { - s.Number = NewBool(false) - } - - if s.Uppercase == nil { - s.Uppercase = NewBool(false) - } - - if s.Symbol == nil { - s.Symbol = NewBool(false) - } -} - -type FileSettings struct { - EnableFileAttachments *bool - EnableMobileUpload *bool - EnableMobileDownload *bool - MaxFileSize *int64 - DriverName *string - Directory string - EnablePublicLink bool - PublicLinkSalt *string - InitialFont string - AmazonS3AccessKeyId string - AmazonS3SecretAccessKey string - AmazonS3Bucket string - AmazonS3Region string - AmazonS3Endpoint string - AmazonS3SSL *bool - AmazonS3SignV2 *bool - AmazonS3SSE *bool - AmazonS3Trace *bool -} - -func (s *FileSettings) SetDefaults() { - if s.DriverName == nil { - s.DriverName = NewString(IMAGE_DRIVER_LOCAL) - } - - if s.AmazonS3Endpoint == "" { - // Defaults to "s3.amazonaws.com" - s.AmazonS3Endpoint = "s3.amazonaws.com" - } - - if s.AmazonS3SSL == nil { - s.AmazonS3SSL = NewBool(true) // Secure by default. - } - - if s.AmazonS3SignV2 == nil { - s.AmazonS3SignV2 = new(bool) - // Signature v2 is not enabled by default. - } - - if s.AmazonS3SSE == nil { - s.AmazonS3SSE = NewBool(false) // Not Encrypted by default. - } - - if s.AmazonS3Trace == nil { - s.AmazonS3Trace = NewBool(false) - } - - if s.EnableFileAttachments == nil { - s.EnableFileAttachments = NewBool(true) - } - - if s.EnableMobileUpload == nil { - s.EnableMobileUpload = NewBool(true) - } - - if s.EnableMobileDownload == nil { - s.EnableMobileDownload = NewBool(true) - } - - if s.MaxFileSize == nil { - s.MaxFileSize = NewInt64(52428800) // 50 MB - } - - if s.PublicLinkSalt == nil || len(*s.PublicLinkSalt) == 0 { - s.PublicLinkSalt = NewString(NewRandomString(32)) - } - - if s.InitialFont == "" { - // Defaults to "luximbi.ttf" - s.InitialFont = "luximbi.ttf" - } - - if s.Directory == "" { - s.Directory = "./data/" - } -} - -type EmailSettings struct { - EnableSignUpWithEmail bool - EnableSignInWithEmail *bool - EnableSignInWithUsername *bool - SendEmailNotifications bool - UseChannelInEmailNotifications *bool - RequireEmailVerification bool - FeedbackName string - FeedbackEmail string - FeedbackOrganization *string - EnableSMTPAuth *bool - SMTPUsername string - SMTPPassword string - SMTPServer string - SMTPPort string - ConnectionSecurity string - InviteSalt string - SendPushNotifications *bool - PushNotificationServer *string - PushNotificationContents *string - EnableEmailBatching *bool - EmailBatchingBufferSize *int - EmailBatchingInterval *int - SkipServerCertificateVerification *bool - EmailNotificationContentsType *string - LoginButtonColor *string - LoginButtonBorderColor *string - LoginButtonTextColor *string -} - -func (s *EmailSettings) SetDefaults() { - if len(s.InviteSalt) == 0 { - s.InviteSalt = NewRandomString(32) - } - - if s.EnableSignInWithEmail == nil { - s.EnableSignInWithEmail = NewBool(s.EnableSignUpWithEmail) - } - - if s.EnableSignInWithUsername == nil { - s.EnableSignInWithUsername = NewBool(false) - } - - if s.UseChannelInEmailNotifications == nil { - s.UseChannelInEmailNotifications = NewBool(false) - } - - if s.SendPushNotifications == nil { - s.SendPushNotifications = NewBool(false) - } - - if s.PushNotificationServer == nil { - s.PushNotificationServer = NewString("") - } - - if s.PushNotificationContents == nil { - s.PushNotificationContents = NewString(GENERIC_NOTIFICATION) - } - - if s.FeedbackOrganization == nil { - s.FeedbackOrganization = NewString(EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION) - } - - if s.EnableEmailBatching == nil { - s.EnableEmailBatching = NewBool(false) - } - - if s.EmailBatchingBufferSize == nil { - s.EmailBatchingBufferSize = NewInt(EMAIL_BATCHING_BUFFER_SIZE) - } - - if s.EmailBatchingInterval == nil { - s.EmailBatchingInterval = NewInt(EMAIL_BATCHING_INTERVAL) - } - - if s.EnableSMTPAuth == nil { - s.EnableSMTPAuth = new(bool) - if s.ConnectionSecurity == CONN_SECURITY_NONE { - *s.EnableSMTPAuth = false - } else { - *s.EnableSMTPAuth = true - } - } - - if s.ConnectionSecurity == CONN_SECURITY_PLAIN { - s.ConnectionSecurity = CONN_SECURITY_NONE - } - - if s.SkipServerCertificateVerification == nil { - s.SkipServerCertificateVerification = NewBool(false) - } - - if s.EmailNotificationContentsType == nil { - s.EmailNotificationContentsType = NewString(EMAIL_NOTIFICATION_CONTENTS_FULL) - } - - if s.LoginButtonColor == nil { - s.LoginButtonColor = NewString("#0000") - } - - if s.LoginButtonBorderColor == nil { - s.LoginButtonBorderColor = NewString("#2389D7") - } - - if s.LoginButtonTextColor == nil { - s.LoginButtonTextColor = NewString("#2389D7") - } -} - -type RateLimitSettings struct { - Enable *bool - PerSec *int - MaxBurst *int - MemoryStoreSize *int - VaryByRemoteAddr bool - VaryByHeader string -} - -func (s *RateLimitSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(false) - } - - if s.PerSec == nil { - s.PerSec = NewInt(10) - } - - if s.MaxBurst == nil { - s.MaxBurst = NewInt(100) - } - - if s.MemoryStoreSize == nil { - s.MemoryStoreSize = NewInt(10000) - } -} - -type PrivacySettings struct { - ShowEmailAddress bool - ShowFullName bool -} - -type SupportSettings struct { - TermsOfServiceLink *string - PrivacyPolicyLink *string - AboutLink *string - HelpLink *string - ReportAProblemLink *string - SupportEmail *string -} - -func (s *SupportSettings) SetDefaults() { - if !IsSafeLink(s.TermsOfServiceLink) { - *s.TermsOfServiceLink = SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK - } - - if s.TermsOfServiceLink == nil { - s.TermsOfServiceLink = NewString(SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK) - } - - if !IsSafeLink(s.PrivacyPolicyLink) { - *s.PrivacyPolicyLink = "" - } - - if s.PrivacyPolicyLink == nil { - s.PrivacyPolicyLink = NewString(SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK) - } - - if !IsSafeLink(s.AboutLink) { - *s.AboutLink = "" - } - - if s.AboutLink == nil { - s.AboutLink = NewString(SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK) - } - - if !IsSafeLink(s.HelpLink) { - *s.HelpLink = "" - } - - if s.HelpLink == nil { - s.HelpLink = NewString(SUPPORT_SETTINGS_DEFAULT_HELP_LINK) - } - - if !IsSafeLink(s.ReportAProblemLink) { - *s.ReportAProblemLink = "" - } - - if s.ReportAProblemLink == nil { - s.ReportAProblemLink = NewString(SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK) - } - - if s.SupportEmail == nil { - s.SupportEmail = NewString(SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL) - } -} - -type AnnouncementSettings struct { - EnableBanner *bool - BannerText *string - BannerColor *string - BannerTextColor *string - AllowBannerDismissal *bool -} - -func (s *AnnouncementSettings) SetDefaults() { - if s.EnableBanner == nil { - s.EnableBanner = NewBool(false) - } - - if s.BannerText == nil { - s.BannerText = NewString("") - } - - if s.BannerColor == nil { - s.BannerColor = NewString(ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR) - } - - if s.BannerTextColor == nil { - s.BannerTextColor = NewString(ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR) - } - - if s.AllowBannerDismissal == nil { - s.AllowBannerDismissal = NewBool(true) - } -} - -type ThemeSettings struct { - EnableThemeSelection *bool - DefaultTheme *string - AllowCustomThemes *bool - AllowedThemes []string -} - -func (s *ThemeSettings) SetDefaults() { - if s.EnableThemeSelection == nil { - s.EnableThemeSelection = NewBool(true) - } - - if s.DefaultTheme == nil { - s.DefaultTheme = NewString(TEAM_SETTINGS_DEFAULT_TEAM_TEXT) - } - - if s.AllowCustomThemes == nil { - s.AllowCustomThemes = NewBool(true) - } - - if s.AllowedThemes == nil { - s.AllowedThemes = []string{} - } -} - -type TeamSettings struct { - SiteName string - MaxUsersPerTeam *int - EnableTeamCreation bool - EnableUserCreation bool - EnableOpenServer *bool - RestrictCreationToDomains string - EnableCustomBrand *bool - CustomBrandText *string - CustomDescriptionText *string - RestrictDirectMessage *string - RestrictTeamInvite *string - RestrictPublicChannelManagement *string - RestrictPrivateChannelManagement *string - RestrictPublicChannelCreation *string - RestrictPrivateChannelCreation *string - RestrictPublicChannelDeletion *string - RestrictPrivateChannelDeletion *string - RestrictPrivateChannelManageMembers *string - EnableXToLeaveChannelsFromLHS *bool - UserStatusAwayTimeout *int64 - MaxChannelsPerTeam *int64 - MaxNotificationsPerChannel *int64 - EnableConfirmNotificationsToChannel *bool - TeammateNameDisplay *string - ExperimentalTownSquareIsReadOnly *bool - ExperimentalPrimaryTeam *string -} - -func (s *TeamSettings) SetDefaults() { - if s.MaxUsersPerTeam == nil { - s.MaxUsersPerTeam = NewInt(TEAM_SETTINGS_DEFAULT_MAX_USERS_PER_TEAM) - } - - if s.EnableCustomBrand == nil { - s.EnableCustomBrand = NewBool(false) - } - - if s.CustomBrandText == nil { - s.CustomBrandText = NewString(TEAM_SETTINGS_DEFAULT_CUSTOM_BRAND_TEXT) - } - - if s.CustomDescriptionText == nil { - s.CustomDescriptionText = NewString(TEAM_SETTINGS_DEFAULT_CUSTOM_DESCRIPTION_TEXT) - } - - if s.EnableOpenServer == nil { - s.EnableOpenServer = NewBool(false) - } - - if s.RestrictDirectMessage == nil { - s.RestrictDirectMessage = NewString(DIRECT_MESSAGE_ANY) - } - - if s.RestrictTeamInvite == nil { - s.RestrictTeamInvite = NewString(PERMISSIONS_ALL) - } - - if s.RestrictPublicChannelManagement == nil { - s.RestrictPublicChannelManagement = NewString(PERMISSIONS_ALL) - } - - if s.RestrictPrivateChannelManagement == nil { - s.RestrictPrivateChannelManagement = NewString(PERMISSIONS_ALL) - } - - if s.RestrictPublicChannelCreation == nil { - s.RestrictPublicChannelCreation = new(string) - // If this setting does not exist, assume migration from <3.6, so use management setting as default. - if *s.RestrictPublicChannelManagement == PERMISSIONS_CHANNEL_ADMIN { - *s.RestrictPublicChannelCreation = PERMISSIONS_TEAM_ADMIN - } else { - *s.RestrictPublicChannelCreation = *s.RestrictPublicChannelManagement - } - } - - if s.RestrictPrivateChannelCreation == nil { - // If this setting does not exist, assume migration from <3.6, so use management setting as default. - if *s.RestrictPrivateChannelManagement == PERMISSIONS_CHANNEL_ADMIN { - s.RestrictPrivateChannelCreation = NewString(PERMISSIONS_TEAM_ADMIN) - } else { - s.RestrictPrivateChannelCreation = NewString(*s.RestrictPrivateChannelManagement) - } - } - - if s.RestrictPublicChannelDeletion == nil { - // If this setting does not exist, assume migration from <3.6, so use management setting as default. - s.RestrictPublicChannelDeletion = NewString(*s.RestrictPublicChannelManagement) - } - - if s.RestrictPrivateChannelDeletion == nil { - // If this setting does not exist, assume migration from <3.6, so use management setting as default. - s.RestrictPrivateChannelDeletion = NewString(*s.RestrictPrivateChannelManagement) - } - - if s.RestrictPrivateChannelManageMembers == nil { - s.RestrictPrivateChannelManageMembers = NewString(PERMISSIONS_ALL) - } - - if s.EnableXToLeaveChannelsFromLHS == nil { - s.EnableXToLeaveChannelsFromLHS = NewBool(false) - } - - if s.UserStatusAwayTimeout == nil { - s.UserStatusAwayTimeout = NewInt64(TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT) - } - - if s.MaxChannelsPerTeam == nil { - s.MaxChannelsPerTeam = NewInt64(2000) - } - - if s.MaxNotificationsPerChannel == nil { - s.MaxNotificationsPerChannel = NewInt64(1000) - } - - if s.EnableConfirmNotificationsToChannel == nil { - s.EnableConfirmNotificationsToChannel = NewBool(true) - } - - if s.ExperimentalTownSquareIsReadOnly == nil { - s.ExperimentalTownSquareIsReadOnly = NewBool(false) - } - - if s.ExperimentalPrimaryTeam == nil { - s.ExperimentalPrimaryTeam = NewString("") - } -} - -type ClientRequirements struct { - AndroidLatestVersion string - AndroidMinVersion string - DesktopLatestVersion string - DesktopMinVersion string - IosLatestVersion string - IosMinVersion string -} - -type LdapSettings struct { - // Basic - Enable *bool - EnableSync *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 - NicknameAttribute *string - IdAttribute *string - PositionAttribute *string - - // Syncronization - SyncIntervalMinutes *int - - // Advanced - SkipCertificateVerification *bool - QueryTimeout *int - MaxPageSize *int - - // Customization - LoginFieldName *string - - LoginButtonColor *string - LoginButtonBorderColor *string - LoginButtonTextColor *string -} - -func (s *LdapSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(false) - } - - // When unset should default to LDAP Enabled - if s.EnableSync == nil { - s.EnableSync = NewBool(*s.Enable) - } - - if s.LdapServer == nil { - s.LdapServer = NewString("") - } - - if s.LdapPort == nil { - s.LdapPort = NewInt(389) - } - - if s.ConnectionSecurity == nil { - s.ConnectionSecurity = NewString("") - } - - if s.BaseDN == nil { - s.BaseDN = NewString("") - } - - if s.BindUsername == nil { - s.BindUsername = NewString("") - } - - if s.BindPassword == nil { - s.BindPassword = NewString("") - } - - if s.UserFilter == nil { - s.UserFilter = NewString("") - } - - if s.FirstNameAttribute == nil { - s.FirstNameAttribute = NewString(LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE) - } - - if s.LastNameAttribute == nil { - s.LastNameAttribute = NewString(LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE) - } - - if s.EmailAttribute == nil { - s.EmailAttribute = NewString(LDAP_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE) - } - - if s.UsernameAttribute == nil { - s.UsernameAttribute = NewString(LDAP_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE) - } - - if s.NicknameAttribute == nil { - s.NicknameAttribute = NewString(LDAP_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE) - } - - if s.IdAttribute == nil { - s.IdAttribute = NewString(LDAP_SETTINGS_DEFAULT_ID_ATTRIBUTE) - } - - if s.PositionAttribute == nil { - s.PositionAttribute = NewString(LDAP_SETTINGS_DEFAULT_POSITION_ATTRIBUTE) - } - - if s.SyncIntervalMinutes == nil { - s.SyncIntervalMinutes = NewInt(60) - } - - if s.SkipCertificateVerification == nil { - s.SkipCertificateVerification = NewBool(false) - } - - if s.QueryTimeout == nil { - s.QueryTimeout = NewInt(60) - } - - if s.MaxPageSize == nil { - s.MaxPageSize = NewInt(0) - } - - if s.LoginFieldName == nil { - s.LoginFieldName = NewString(LDAP_SETTINGS_DEFAULT_LOGIN_FIELD_NAME) - } - - if s.LoginButtonColor == nil { - s.LoginButtonColor = NewString("#0000") - } - - if s.LoginButtonBorderColor == nil { - s.LoginButtonBorderColor = NewString("#2389D7") - } - - if s.LoginButtonTextColor == nil { - s.LoginButtonTextColor = NewString("#2389D7") - } -} - -type ComplianceSettings struct { - Enable *bool - Directory *string - EnableDaily *bool -} - -func (s *ComplianceSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(false) - } - - if s.Directory == nil { - s.Directory = NewString("./data/") - } - - if s.EnableDaily == nil { - s.EnableDaily = NewBool(false) - } -} - -type LocalizationSettings struct { - DefaultServerLocale *string - DefaultClientLocale *string - AvailableLocales *string -} - -func (s *LocalizationSettings) SetDefaults() { - if s.DefaultServerLocale == nil { - s.DefaultServerLocale = NewString(DEFAULT_LOCALE) - } - - if s.DefaultClientLocale == nil { - s.DefaultClientLocale = NewString(DEFAULT_LOCALE) - } - - if s.AvailableLocales == nil { - s.AvailableLocales = NewString("") - } -} - -type SamlSettings struct { - // Basic - Enable *bool - EnableSyncWithLdap *bool - - Verify *bool - Encrypt *bool - - IdpUrl *string - IdpDescriptorUrl *string - AssertionConsumerServiceURL *string - - IdpCertificateFile *string - PublicCertificateFile *string - PrivateKeyFile *string - - // User Mapping - FirstNameAttribute *string - LastNameAttribute *string - EmailAttribute *string - UsernameAttribute *string - NicknameAttribute *string - LocaleAttribute *string - PositionAttribute *string - - LoginButtonText *string - - LoginButtonColor *string - LoginButtonBorderColor *string - LoginButtonTextColor *string -} - -func (s *SamlSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(false) - } - - if s.EnableSyncWithLdap == nil { - s.EnableSyncWithLdap = NewBool(false) - } - - if s.Verify == nil { - s.Verify = NewBool(true) - } - - if s.Encrypt == nil { - s.Encrypt = NewBool(true) - } - - if s.IdpUrl == nil { - s.IdpUrl = NewString("") - } - - if s.IdpDescriptorUrl == nil { - s.IdpDescriptorUrl = NewString("") - } - - if s.IdpCertificateFile == nil { - s.IdpCertificateFile = NewString("") - } - - if s.PublicCertificateFile == nil { - s.PublicCertificateFile = NewString("") - } - - if s.PrivateKeyFile == nil { - s.PrivateKeyFile = NewString("") - } - - if s.AssertionConsumerServiceURL == nil { - s.AssertionConsumerServiceURL = NewString("") - } - - if s.LoginButtonText == nil || *s.LoginButtonText == "" { - s.LoginButtonText = NewString(USER_AUTH_SERVICE_SAML_TEXT) - } - - if s.FirstNameAttribute == nil { - s.FirstNameAttribute = NewString(SAML_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE) - } - - if s.LastNameAttribute == nil { - s.LastNameAttribute = NewString(SAML_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE) - } - - if s.EmailAttribute == nil { - s.EmailAttribute = NewString(SAML_SETTINGS_DEFAULT_EMAIL_ATTRIBUTE) - } - - if s.UsernameAttribute == nil { - s.UsernameAttribute = NewString(SAML_SETTINGS_DEFAULT_USERNAME_ATTRIBUTE) - } - - if s.NicknameAttribute == nil { - s.NicknameAttribute = NewString(SAML_SETTINGS_DEFAULT_NICKNAME_ATTRIBUTE) - } - - if s.PositionAttribute == nil { - s.PositionAttribute = NewString(SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE) - } - - if s.LocaleAttribute == nil { - s.LocaleAttribute = NewString(SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE) - } - - if s.LoginButtonColor == nil { - s.LoginButtonColor = NewString("#34a28b") - } - - if s.LoginButtonBorderColor == nil { - s.LoginButtonBorderColor = NewString("#2389D7") - } - - if s.LoginButtonTextColor == nil { - s.LoginButtonTextColor = NewString("#ffffff") - } -} - -type NativeAppSettings struct { - AppDownloadLink *string - AndroidAppDownloadLink *string - IosAppDownloadLink *string -} - -func (s *NativeAppSettings) SetDefaults() { - if s.AppDownloadLink == nil { - s.AppDownloadLink = NewString(NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK) - } - - if s.AndroidAppDownloadLink == nil { - s.AndroidAppDownloadLink = NewString(NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK) - } - - if s.IosAppDownloadLink == nil { - s.IosAppDownloadLink = NewString(NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK) - } -} - -type WebrtcSettings struct { - Enable *bool - GatewayWebsocketUrl *string - GatewayAdminUrl *string - GatewayAdminSecret *string - StunURI *string - TurnURI *string - TurnUsername *string - TurnSharedKey *string -} - -func (s *WebrtcSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(false) - } - - if s.GatewayWebsocketUrl == nil { - s.GatewayWebsocketUrl = NewString("") - } - - if s.GatewayAdminUrl == nil { - s.GatewayAdminUrl = NewString("") - } - - if s.GatewayAdminSecret == nil { - s.GatewayAdminSecret = NewString("") - } - - if s.StunURI == nil { - s.StunURI = NewString(WEBRTC_SETTINGS_DEFAULT_STUN_URI) - } - - if s.TurnURI == nil { - s.TurnURI = NewString(WEBRTC_SETTINGS_DEFAULT_TURN_URI) - } - - if s.TurnUsername == nil { - s.TurnUsername = NewString("") - } - - if s.TurnSharedKey == nil { - s.TurnSharedKey = NewString("") - } -} - -type ElasticsearchSettings struct { - ConnectionUrl *string - Username *string - Password *string - EnableIndexing *bool - EnableSearching *bool - Sniff *bool - PostIndexReplicas *int - PostIndexShards *int - AggregatePostsAfterDays *int - PostsAggregatorJobStartTime *string - IndexPrefix *string - LiveIndexingBatchSize *int - BulkIndexingTimeWindowSeconds *int - RequestTimeoutSeconds *int -} - -func (s *ElasticsearchSettings) SetDefaults() { - if s.ConnectionUrl == nil { - s.ConnectionUrl = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL) - } - - if s.Username == nil { - s.Username = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME) - } - - if s.Password == nil { - s.Password = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD) - } - - if s.EnableIndexing == nil { - s.EnableIndexing = NewBool(false) - } - - if s.EnableSearching == nil { - s.EnableSearching = NewBool(false) - } - - if s.Sniff == nil { - s.Sniff = NewBool(true) - } - - if s.PostIndexReplicas == nil { - s.PostIndexReplicas = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS) - } - - if s.PostIndexShards == nil { - s.PostIndexShards = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS) - } - - if s.AggregatePostsAfterDays == nil { - s.AggregatePostsAfterDays = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_AGGREGATE_POSTS_AFTER_DAYS) - } - - if s.PostsAggregatorJobStartTime == nil { - s.PostsAggregatorJobStartTime = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_POSTS_AGGREGATOR_JOB_START_TIME) - } - - if s.IndexPrefix == nil { - s.IndexPrefix = NewString(ELASTICSEARCH_SETTINGS_DEFAULT_INDEX_PREFIX) - } - - if s.LiveIndexingBatchSize == nil { - s.LiveIndexingBatchSize = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_LIVE_INDEXING_BATCH_SIZE) - } - - if s.BulkIndexingTimeWindowSeconds == nil { - s.BulkIndexingTimeWindowSeconds = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_BULK_INDEXING_TIME_WINDOW_SECONDS) - } - - if s.RequestTimeoutSeconds == nil { - s.RequestTimeoutSeconds = NewInt(ELASTICSEARCH_SETTINGS_DEFAULT_REQUEST_TIMEOUT_SECONDS) - } -} - -type DataRetentionSettings struct { - EnableMessageDeletion *bool - EnableFileDeletion *bool - MessageRetentionDays *int - FileRetentionDays *int - DeletionJobStartTime *string -} - -func (s *DataRetentionSettings) SetDefaults() { - if s.EnableMessageDeletion == nil { - s.EnableMessageDeletion = NewBool(false) - } - - if s.EnableFileDeletion == nil { - s.EnableFileDeletion = NewBool(false) - } - - if s.MessageRetentionDays == nil { - s.MessageRetentionDays = NewInt(DATA_RETENTION_SETTINGS_DEFAULT_MESSAGE_RETENTION_DAYS) - } - - if s.FileRetentionDays == nil { - s.FileRetentionDays = NewInt(DATA_RETENTION_SETTINGS_DEFAULT_FILE_RETENTION_DAYS) - } - - if s.DeletionJobStartTime == nil { - s.DeletionJobStartTime = NewString(DATA_RETENTION_SETTINGS_DEFAULT_DELETION_JOB_START_TIME) - } -} - -type JobSettings struct { - RunJobs *bool - RunScheduler *bool -} - -func (s *JobSettings) SetDefaults() { - if s.RunJobs == nil { - s.RunJobs = NewBool(true) - } - - if s.RunScheduler == nil { - s.RunScheduler = NewBool(true) - } -} - -type PluginState struct { - Enable bool -} - -type PluginSettings struct { - Enable *bool - EnableUploads *bool - Directory *string - ClientDirectory *string - Plugins map[string]interface{} - PluginStates map[string]*PluginState -} - -func (s *PluginSettings) SetDefaults() { - if s.Enable == nil { - s.Enable = NewBool(true) - } - - if s.EnableUploads == nil { - s.EnableUploads = NewBool(false) - } - - if s.Directory == nil { - s.Directory = NewString(PLUGIN_SETTINGS_DEFAULT_DIRECTORY) - } - - if *s.Directory == "" { - *s.Directory = PLUGIN_SETTINGS_DEFAULT_DIRECTORY - } - - if s.ClientDirectory == nil { - s.ClientDirectory = NewString(PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY) - } - - if *s.ClientDirectory == "" { - *s.ClientDirectory = PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY - } - - if s.Plugins == nil { - s.Plugins = make(map[string]interface{}) - } - - if s.PluginStates == nil { - s.PluginStates = make(map[string]*PluginState) - } -} - -type MessageExportSettings struct { - EnableExport *bool - DailyRunTime *string - ExportFromTimestamp *int64 - BatchSize *int -} - -func (s *MessageExportSettings) SetDefaults() { - if s.EnableExport == nil { - s.EnableExport = NewBool(false) - } - - if s.DailyRunTime == nil { - s.DailyRunTime = NewString("01:00") - } - - if s.ExportFromTimestamp == nil { - s.ExportFromTimestamp = NewInt64(0) - } - - if s.EnableExport != nil && *s.EnableExport && *s.ExportFromTimestamp == int64(0) { - // when the feature is enabled via the System Console, use the current timestamp as the start time for future exports - s.ExportFromTimestamp = NewInt64(GetMillis()) - } else if s.EnableExport != nil && !*s.EnableExport { - // when the feature is disabled, reset the timestamp so that the timestamp will be set if the feature is re-enabled - s.ExportFromTimestamp = NewInt64(0) - } - - if s.BatchSize == nil { - s.BatchSize = NewInt(10000) - } -} - -type ConfigFunc func() *Config - -type Config struct { - ServiceSettings ServiceSettings - TeamSettings TeamSettings - ClientRequirements ClientRequirements - SqlSettings SqlSettings - LogSettings LogSettings - PasswordSettings PasswordSettings - FileSettings FileSettings - EmailSettings EmailSettings - RateLimitSettings RateLimitSettings - PrivacySettings PrivacySettings - SupportSettings SupportSettings - AnnouncementSettings AnnouncementSettings - ThemeSettings ThemeSettings - GitLabSettings SSOSettings - GoogleSettings SSOSettings - Office365Settings SSOSettings - LdapSettings LdapSettings - ComplianceSettings ComplianceSettings - LocalizationSettings LocalizationSettings - SamlSettings SamlSettings - NativeAppSettings NativeAppSettings - ClusterSettings ClusterSettings - MetricsSettings MetricsSettings - AnalyticsSettings AnalyticsSettings - WebrtcSettings WebrtcSettings - ElasticsearchSettings ElasticsearchSettings - DataRetentionSettings DataRetentionSettings - MessageExportSettings MessageExportSettings - JobSettings JobSettings - PluginSettings PluginSettings -} - -func (o *Config) Clone() *Config { - var ret Config - if err := json.Unmarshal([]byte(o.ToJson()), &ret); err != nil { - panic(err) - } - return &ret -} - -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 - case SERVICE_OFFICE365: - return &o.Office365Settings - } - - 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() { - o.LdapSettings.SetDefaults() - o.SamlSettings.SetDefaults() - - if o.TeamSettings.TeammateNameDisplay == nil { - o.TeamSettings.TeammateNameDisplay = NewString(SHOW_USERNAME) - - if *o.SamlSettings.Enable || *o.LdapSettings.Enable { - *o.TeamSettings.TeammateNameDisplay = SHOW_FULLNAME - } - } - - o.SqlSettings.SetDefaults() - o.FileSettings.SetDefaults() - o.EmailSettings.SetDefaults() - o.ServiceSettings.SetDefaults() - o.PasswordSettings.SetDefaults() - o.TeamSettings.SetDefaults() - o.MetricsSettings.SetDefaults() - o.SupportSettings.SetDefaults() - o.AnnouncementSettings.SetDefaults() - o.ThemeSettings.SetDefaults() - o.ClusterSettings.SetDefaults() - o.PluginSettings.SetDefaults() - o.AnalyticsSettings.SetDefaults() - o.ComplianceSettings.SetDefaults() - o.LocalizationSettings.SetDefaults() - o.ElasticsearchSettings.SetDefaults() - o.NativeAppSettings.SetDefaults() - o.DataRetentionSettings.SetDefaults() - o.RateLimitSettings.SetDefaults() - o.LogSettings.SetDefaults() - o.JobSettings.SetDefaults() - o.WebrtcSettings.SetDefaults() - o.MessageExportSettings.SetDefaults() -} - -func (o *Config) IsValid() *AppError { - if len(*o.ServiceSettings.SiteURL) == 0 && *o.EmailSettings.EnableEmailBatching { - return NewAppError("Config.IsValid", "model.config.is_valid.site_url_email_batching.app_error", nil, "", http.StatusBadRequest) - } - - if *o.ClusterSettings.Enable && *o.EmailSettings.EnableEmailBatching { - return NewAppError("Config.IsValid", "model.config.is_valid.cluster_email_batching.app_error", nil, "", http.StatusBadRequest) - } - - if err := o.TeamSettings.isValid(); err != nil { - return err - } - - if err := o.SqlSettings.isValid(); err != nil { - return err - } - - if err := o.FileSettings.isValid(); err != nil { - return err - } - - if err := o.EmailSettings.isValid(); err != nil { - return err - } - - if err := o.LdapSettings.isValid(); err != nil { - return err - } - - if err := o.SamlSettings.isValid(); err != nil { - return err - } - - if *o.PasswordSettings.MinimumLength < PASSWORD_MINIMUM_LENGTH || *o.PasswordSettings.MinimumLength > PASSWORD_MAXIMUM_LENGTH { - return NewAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "", http.StatusBadRequest) - } - - if err := o.RateLimitSettings.isValid(); err != nil { - return err - } - - if err := o.WebrtcSettings.isValid(); err != nil { - return err - } - - if err := o.ServiceSettings.isValid(); err != nil { - return err - } - - if err := o.ElasticsearchSettings.isValid(); err != nil { - return err - } - - if err := o.DataRetentionSettings.isValid(); err != nil { - return err - } - - if err := o.LocalizationSettings.isValid(); err != nil { - return err - } - - if err := o.MessageExportSettings.isValid(o.FileSettings); err != nil { - return err - } - - return nil -} - -func (ts *TeamSettings) isValid() *AppError { - if *ts.MaxUsersPerTeam <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "", http.StatusBadRequest) - } - - if *ts.MaxChannelsPerTeam <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "", http.StatusBadRequest) - } - - if *ts.MaxNotificationsPerChannel <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.max_notify_per_channel.app_error", nil, "", http.StatusBadRequest) - } - - if !(*ts.RestrictDirectMessage == DIRECT_MESSAGE_ANY || *ts.RestrictDirectMessage == DIRECT_MESSAGE_TEAM) { - return NewAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "", http.StatusBadRequest) - } - - if !(*ts.TeammateNameDisplay == SHOW_FULLNAME || *ts.TeammateNameDisplay == SHOW_NICKNAME_FULLNAME || *ts.TeammateNameDisplay == SHOW_USERNAME) { - return NewAppError("Config.IsValid", "model.config.is_valid.teammate_name_display.app_error", nil, "", http.StatusBadRequest) - } - - if len(ts.SiteName) > SITENAME_MAX_LENGTH { - return NewAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "", http.StatusBadRequest) - } - - return nil -} - -func (ss *SqlSettings) isValid() *AppError { - if len(ss.AtRestEncryptKey) < 32 { - return NewAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "", http.StatusBadRequest) - } - - if !(*ss.DriverName == DATABASE_DRIVER_MYSQL || *ss.DriverName == DATABASE_DRIVER_POSTGRES) { - return NewAppError("Config.IsValid", "model.config.is_valid.sql_driver.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.MaxIdleConns <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.QueryTimeout <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.DataSource) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.MaxOpenConns <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (fs *FileSettings) isValid() *AppError { - if *fs.MaxFileSize <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.max_file_size.app_error", nil, "", http.StatusBadRequest) - } - - if !(*fs.DriverName == IMAGE_DRIVER_LOCAL || *fs.DriverName == IMAGE_DRIVER_S3) { - return NewAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "", http.StatusBadRequest) - } - - if len(*fs.PublicLinkSalt) < 32 { - return NewAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (es *EmailSettings) isValid() *AppError { - if !(es.ConnectionSecurity == CONN_SECURITY_NONE || es.ConnectionSecurity == CONN_SECURITY_TLS || es.ConnectionSecurity == CONN_SECURITY_STARTTLS || es.ConnectionSecurity == CONN_SECURITY_PLAIN) { - return NewAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "", http.StatusBadRequest) - } - - if len(es.InviteSalt) < 32 { - return NewAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "", http.StatusBadRequest) - } - - if *es.EmailBatchingBufferSize <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "", http.StatusBadRequest) - } - - if *es.EmailBatchingInterval < 30 { - return NewAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "", http.StatusBadRequest) - } - - if !(*es.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_FULL || *es.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_GENERIC) { - return NewAppError("Config.IsValid", "model.config.is_valid.email_notification_contents_type.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (rls *RateLimitSettings) isValid() *AppError { - if *rls.MemoryStoreSize <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "", http.StatusBadRequest) - } - - if *rls.PerSec <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.rate_sec.app_error", nil, "", http.StatusBadRequest) - } - - if *rls.MaxBurst <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.max_burst.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (ls *LdapSettings) isValid() *AppError { - if !(*ls.ConnectionSecurity == CONN_SECURITY_NONE || *ls.ConnectionSecurity == CONN_SECURITY_TLS || *ls.ConnectionSecurity == CONN_SECURITY_STARTTLS) { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "", http.StatusBadRequest) - } - - if *ls.SyncIntervalMinutes <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "", http.StatusBadRequest) - } - - if *ls.MaxPageSize < 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_max_page_size.app_error", nil, "", http.StatusBadRequest) - } - - if *ls.Enable { - if *ls.LdapServer == "" { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "", http.StatusBadRequest) - } - - if *ls.BaseDN == "" { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "", http.StatusBadRequest) - } - - if *ls.EmailAttribute == "" { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_email", nil, "", http.StatusBadRequest) - } - - if *ls.UsernameAttribute == "" { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_username", nil, "", http.StatusBadRequest) - } - - if *ls.IdAttribute == "" { - return NewAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "", http.StatusBadRequest) - } - } - - return nil -} - -func (ss *SamlSettings) isValid() *AppError { - if *ss.Enable { - if len(*ss.IdpUrl) == 0 || !IsValidHttpUrl(*ss.IdpUrl) { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.IdpDescriptorUrl) == 0 || !IsValidHttpUrl(*ss.IdpDescriptorUrl) { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_descriptor_url.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.IdpCertificateFile) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_cert.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.EmailAttribute) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.UsernameAttribute) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_username_attribute.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.Verify { - if len(*ss.AssertionConsumerServiceURL) == 0 || !IsValidHttpUrl(*ss.AssertionConsumerServiceURL) { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "", http.StatusBadRequest) - } - } - - if *ss.Encrypt { - if len(*ss.PrivateKeyFile) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_private_key.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.PublicCertificateFile) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_public_cert.app_error", nil, "", http.StatusBadRequest) - } - } - - if len(*ss.EmailAttribute) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "", http.StatusBadRequest) - } - } - - return nil -} - -func (ws *WebrtcSettings) isValid() *AppError { - if *ws.Enable { - if len(*ws.GatewayWebsocketUrl) == 0 || !IsValidWebsocketUrl(*ws.GatewayWebsocketUrl) { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_ws_url.app_error", nil, "", http.StatusBadRequest) - } else if len(*ws.GatewayAdminUrl) == 0 || !IsValidHttpUrl(*ws.GatewayAdminUrl) { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_url.app_error", nil, "", http.StatusBadRequest) - } else if len(*ws.GatewayAdminSecret) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_secret.app_error", nil, "", http.StatusBadRequest) - } else if len(*ws.StunURI) != 0 && !IsValidTurnOrStunServer(*ws.StunURI) { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_stun_uri.app_error", nil, "", http.StatusBadRequest) - } else if len(*ws.TurnURI) != 0 { - if !IsValidTurnOrStunServer(*ws.TurnURI) { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_uri.app_error", nil, "", http.StatusBadRequest) - } - if len(*ws.TurnUsername) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_username.app_error", nil, "", http.StatusBadRequest) - } else if len(*ws.TurnSharedKey) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_shared_key.app_error", nil, "", http.StatusBadRequest) - } - } - } - - return nil -} - -func (ss *ServiceSettings) isValid() *AppError { - if !(*ss.ConnectionSecurity == CONN_SECURITY_NONE || *ss.ConnectionSecurity == CONN_SECURITY_TLS) { - return NewAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.ReadTimeout <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.WriteTimeout <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.TimeBetweenUserTypingUpdatesMilliseconds < 1000 { - return NewAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "", http.StatusBadRequest) - } - - if *ss.MaximumLoginAttempts <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "", http.StatusBadRequest) - } - - if len(*ss.SiteURL) != 0 { - if _, err := url.ParseRequestURI(*ss.SiteURL); err != nil { - return NewAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "", http.StatusBadRequest) - } - } - - if len(*ss.ListenAddress) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (ess *ElasticsearchSettings) isValid() *AppError { - if *ess.EnableIndexing { - if len(*ess.ConnectionUrl) == 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.connection_url.app_error", nil, "", http.StatusBadRequest) - } - } - - if *ess.EnableSearching && !*ess.EnableIndexing { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.enable_searching.app_error", nil, "", http.StatusBadRequest) - } - - if *ess.AggregatePostsAfterDays < 1 { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.aggregate_posts_after_days.app_error", nil, "", http.StatusBadRequest) - } - - if _, err := time.Parse("15:04", *ess.PostsAggregatorJobStartTime); err != nil { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.posts_aggregator_job_start_time.app_error", nil, err.Error(), http.StatusBadRequest) - } - - if *ess.LiveIndexingBatchSize < 1 { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.live_indexing_batch_size.app_error", nil, "", http.StatusBadRequest) - } - - if *ess.BulkIndexingTimeWindowSeconds < 1 { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.bulk_indexing_time_window_seconds.app_error", nil, "", http.StatusBadRequest) - } - - if *ess.RequestTimeoutSeconds < 1 { - return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.request_timeout_seconds.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (drs *DataRetentionSettings) isValid() *AppError { - if *drs.MessageRetentionDays <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", nil, "", http.StatusBadRequest) - } - - if *drs.FileRetentionDays <= 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_days_too_low.app_error", nil, "", http.StatusBadRequest) - } - - if _, err := time.Parse("15:04", *drs.DeletionJobStartTime); err != nil { - return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.deletion_job_start_time.app_error", nil, err.Error(), http.StatusBadRequest) - } - - return nil -} - -func (ls *LocalizationSettings) isValid() *AppError { - if len(*ls.AvailableLocales) > 0 { - if !strings.Contains(*ls.AvailableLocales, *ls.DefaultClientLocale) { - return NewAppError("Config.IsValid", "model.config.is_valid.localization.available_locales.app_error", nil, "", http.StatusBadRequest) - } - } - - return nil -} - -func (mes *MessageExportSettings) isValid(fs FileSettings) *AppError { - if mes.EnableExport == nil { - return NewAppError("Config.IsValid", "model.config.is_valid.message_export.enable.app_error", nil, "", http.StatusBadRequest) - } - if *mes.EnableExport { - if mes.ExportFromTimestamp == nil || *mes.ExportFromTimestamp < 0 || *mes.ExportFromTimestamp > GetMillis() { - return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_from.app_error", nil, "", http.StatusBadRequest) - } else if mes.DailyRunTime == nil { - return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, "", http.StatusBadRequest) - } else if _, err := time.Parse("15:04", *mes.DailyRunTime); err != nil { - return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, err.Error(), http.StatusBadRequest) - } else if mes.BatchSize == nil || *mes.BatchSize < 0 { - return NewAppError("Config.IsValid", "model.config.is_valid.message_export.batch_size.app_error", nil, "", http.StatusBadRequest) - } - } - return nil -} - -func (o *Config) GetSanitizeOptions() map[string]bool { - options := map[string]bool{} - options["fullname"] = o.PrivacySettings.ShowFullName - options["email"] = o.PrivacySettings.ShowEmailAddress - - return options -} - -func (o *Config) Sanitize() { - if o.LdapSettings.BindPassword != nil && len(*o.LdapSettings.BindPassword) > 0 { - *o.LdapSettings.BindPassword = FAKE_SETTING - } - - *o.FileSettings.PublicLinkSalt = FAKE_SETTING - if len(o.FileSettings.AmazonS3SecretAccessKey) > 0 { - o.FileSettings.AmazonS3SecretAccessKey = FAKE_SETTING - } - - o.EmailSettings.InviteSalt = FAKE_SETTING - if len(o.EmailSettings.SMTPPassword) > 0 { - o.EmailSettings.SMTPPassword = FAKE_SETTING - } - - if len(o.GitLabSettings.Secret) > 0 { - o.GitLabSettings.Secret = FAKE_SETTING - } - - *o.SqlSettings.DataSource = FAKE_SETTING - o.SqlSettings.AtRestEncryptKey = FAKE_SETTING - - for i := range o.SqlSettings.DataSourceReplicas { - o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING - } - - for i := range o.SqlSettings.DataSourceSearchReplicas { - o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING - } - - *o.ElasticsearchSettings.Password = FAKE_SETTING -} diff --git a/vendor/github.com/mattermost/platform/model/data_retention_policy.go b/vendor/github.com/mattermost/platform/model/data_retention_policy.go deleted file mode 100644 index 7284477e..00000000 --- a/vendor/github.com/mattermost/platform/model/data_retention_policy.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type DataRetentionPolicy struct { - MessageDeletionEnabled bool `json:"message_deletion_enabled"` - FileDeletionEnabled bool `json:"file_deletion_enabled"` - MessageRetentionCutoff int64 `json:"message_retention_cutoff"` - FileRetentionCutoff int64 `json:"file_retention_cutoff"` -} - -func (me *DataRetentionPolicy) ToJson() string { - b, err := json.Marshal(me) - if err != nil { - return "" - } else { - return string(b) - } -} - -func DataRetentionPolicyFromJson(data io.Reader) *DataRetentionPolicy { - decoder := json.NewDecoder(data) - var me DataRetentionPolicy - err := decoder.Decode(&me) - if err == nil { - return &me - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/emoji.go b/vendor/github.com/mattermost/platform/model/emoji.go deleted file mode 100644 index 272616d9..00000000 --- a/vendor/github.com/mattermost/platform/model/emoji.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" -) - -type Emoji struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - CreatorId string `json:"creator_id"` - Name string `json:"name"` -} - -func (emoji *Emoji) IsValid() *AppError { - if len(emoji.Id) != 26 { - return NewAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "", http.StatusBadRequest) - } - - if emoji.CreateAt == 0 { - return NewAppError("Emoji.IsValid", "model.emoji.create_at.app_error", nil, "id="+emoji.Id, http.StatusBadRequest) - } - - if emoji.UpdateAt == 0 { - return NewAppError("Emoji.IsValid", "model.emoji.update_at.app_error", nil, "id="+emoji.Id, http.StatusBadRequest) - } - - if len(emoji.CreatorId) != 26 { - return NewAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(emoji.Name) == 0 || len(emoji.Name) > 64 || !IsValidAlphaNumHyphenUnderscore(emoji.Name, false) { - return NewAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (emoji *Emoji) PreSave() { - if emoji.Id == "" { - emoji.Id = NewId() - } - - emoji.CreateAt = GetMillis() - emoji.UpdateAt = emoji.CreateAt -} - -func (emoji *Emoji) PreUpdate() { - emoji.UpdateAt = GetMillis() -} - -func (emoji *Emoji) ToJson() string { - b, err := json.Marshal(emoji) - if err != nil { - return "" - } else { - return string(b) - } -} - -func EmojiFromJson(data io.Reader) *Emoji { - decoder := json.NewDecoder(data) - var emoji Emoji - err := decoder.Decode(&emoji) - if err == nil { - return &emoji - } else { - return nil - } -} - -func EmojiListToJson(emojiList []*Emoji) string { - b, err := json.Marshal(emojiList) - if err != nil { - return "" - } else { - return string(b) - } -} - -func EmojiListFromJson(data io.Reader) []*Emoji { - decoder := json.NewDecoder(data) - var emojiList []*Emoji - err := decoder.Decode(&emojiList) - if err == nil { - return emojiList - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/file.go b/vendor/github.com/mattermost/platform/model/file.go deleted file mode 100644 index f9258146..00000000 --- a/vendor/github.com/mattermost/platform/model/file.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image -) - -var ( - IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"} - IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"} -) - -type FileUploadResponse struct { - FileInfos []*FileInfo `json:"file_infos"` - 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 deleted file mode 100644 index 0ee2c50d..00000000 --- a/vendor/github.com/mattermost/platform/model/file_info.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "bytes" - "encoding/json" - "image" - "image/gif" - "io" - "mime" - "net/http" - "path/filepath" - "strings" -) - -type FileInfo struct { - Id string `json:"id"` - CreatorId string `json:"user_id"` - PostId string `json:"post_id,omitempty"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - Path string `json:"-"` // not sent back to the client - ThumbnailPath string `json:"-"` // not sent back to the client - PreviewPath string `json:"-"` // not sent back to the client - Name string `json:"name"` - Extension string `json:"extension"` - Size int64 `json:"size"` - MimeType string `json:"mime_type"` - Width int `json:"width,omitempty"` - Height int `json:"height,omitempty"` - HasPreviewImage bool `json:"has_preview_image,omitempty"` -} - -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 - } -} - -func FileInfosToJson(infos []*FileInfo) string { - b, err := json.Marshal(infos) - if err != nil { - return "" - } else { - return string(b) - } -} - -func FileInfosFromJson(data io.Reader) []*FileInfo { - decoder := json.NewDecoder(data) - - var infos []*FileInfo - if err := decoder.Decode(&infos); err != nil { - return nil - } else { - return infos - } -} - -func (o *FileInfo) PreSave() { - if o.Id == "" { - o.Id = NewId() - } - - if o.CreateAt == 0 { - o.CreateAt = GetMillis() - } - - if o.UpdateAt < o.CreateAt { - o.UpdateAt = o.CreateAt - } -} - -func (o *FileInfo) IsValid() *AppError { - if len(o.Id) != 26 { - return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.CreatorId) != 26 { - return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.PostId) != 0 && len(o.PostId) != 26 { - return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.Path == "" { - return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - return nil -} - -func (o *FileInfo) IsImage() bool { - return strings.HasPrefix(o.MimeType, "image") -} - -func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) { - info := &FileInfo{ - Name: name, - Size: int64(len(data)), - } - var err *AppError - - extension := strings.ToLower(filepath.Ext(name)) - info.MimeType = mime.TypeByExtension(extension) - - if extension != "" && extension[0] == '.' { - // The client expects a file extension without the leading period - info.Extension = extension[1:] - } else { - info.Extension = extension - } - - if info.IsImage() { - // Only set the width and height if it's actually an image that we can understand - if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil { - info.Width = config.Width - info.Height = config.Height - - if info.MimeType == "image/gif" { - // Just show the gif itself instead of a preview image for animated gifs - if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { - // Still return the rest of the info even though it doesn't appear to be an actual gif - info.HasPreviewImage = true - err = NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name, http.StatusBadRequest) - } else { - info.HasPreviewImage = len(gifConfig.Image) == 1 - } - } else { - info.HasPreviewImage = true - } - } - } - - return info, err -} - -func GetEtagForFileInfos(infos []*FileInfo) string { - if len(infos) == 0 { - return Etag() - } - - var maxUpdateAt int64 - - for _, info := range infos { - if info.UpdateAt > maxUpdateAt { - maxUpdateAt = info.UpdateAt - } - } - - return Etag(infos[0].PostId, maxUpdateAt) -} diff --git a/vendor/github.com/mattermost/platform/model/gitlab.go b/vendor/github.com/mattermost/platform/model/gitlab.go deleted file mode 100644 index 8777614c..00000000 --- a/vendor/github.com/mattermost/platform/model/gitlab.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -const ( - USER_AUTH_SERVICE_GITLAB = "gitlab" -) diff --git a/vendor/github.com/mattermost/platform/model/incoming_webhook.go b/vendor/github.com/mattermost/platform/model/incoming_webhook.go deleted file mode 100644 index 3e0488d2..00000000 --- a/vendor/github.com/mattermost/platform/model/incoming_webhook.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - "regexp" -) - -const ( - DEFAULT_WEBHOOK_USERNAME = "webhook" -) - -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"` - Username string `json:"username"` - IconURL string `json:"icon_url"` -} - -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 []*SlackAttachment `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 NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "", http.StatusBadRequest) - - } - - if o.CreateAt == 0 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.UserId) != 26 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ChannelId) != 26 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.TeamId) != 26 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.DisplayName) > 64 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Description) > 128 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Username) > 64 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.username.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.IconURL) > 1024 { - return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.icon_url.app_error", nil, "", http.StatusBadRequest) - } - - 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() -} - -// escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. -// Context: -// JSON strings are not supposed to contain control characters such as \n, \t, -// ... but some incoming webhooks might still send invalid JSON and we want to -// try to handle that. An example invalid JSON string from an incoming webhook -// might look like this (strings for both "text" and "fallback" attributes are -// invalid JSON strings because they contain unescaped newlines and tabs): -// `{ -// "text": "this is a test -// that contains a newline and tabs", -// "attachments": [ -// { -// "fallback": "Required plain-text summary of the attachment -// that contains a newline and tabs", -// "color": "#36a64f", -// ... -// "text": "Optional text that appears within the attachment -// that contains a newline and tabs", -// ... -// "thumb_url": "http://example.com/path/to/thumb.png" -// } -// ] -// }` -// This function will search for `"key": "value"` pairs, and escape \n, \t -// from the value. -func escapeControlCharsFromPayload(by []byte) []byte { - // we'll search for `"text": "..."` or `"fallback": "..."`, ... - keys := "text|fallback|pretext|author_name|title|value" - - // the regexp reads like this: - // (?s): this flag let . match \n (default is false) - // "(keys)": we search for the keys defined above - // \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs - // ": a double-quote - // (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote - // ": a double-quote - r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` - re := regexp.MustCompile(r) - - // the function that will escape \n and \t on the regexp matches - repl := func(b []byte) []byte { - if bytes.Contains(b, []byte("\n")) { - b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) - } - if bytes.Contains(b, []byte("\t")) { - b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) - } - - return b - } - - return re.ReplaceAllFunc(by, repl) -} - -func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { - decoder := json.NewDecoder(bytes.NewReader(by)) - var o IncomingWebhookRequest - err := decoder.Decode(&o) - if err == nil { - return &o, nil - } else { - return nil, err - } -} - -func IncomingWebhookRequestFromJson(data io.Reader) (*IncomingWebhookRequest, *AppError) { - buf := new(bytes.Buffer) - buf.ReadFrom(data) - by := buf.Bytes() - - // Try to decode the JSON data. Only if it fails, try to escape control - // characters from the strings contained in the JSON data. - o, err := decodeIncomingWebhookRequest(by) - if err != nil { - o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) - if err != nil { - return nil, NewAppError("IncomingWebhookRequestFromJson", "Unable to parse incoming data", nil, err.Error(), http.StatusBadRequest) - } - } - - o.Attachments = StringifySlackFieldValue(o.Attachments) - - return o, nil -} diff --git a/vendor/github.com/mattermost/platform/model/initial_load.go b/vendor/github.com/mattermost/platform/model/initial_load.go deleted file mode 100644 index 71ba1769..00000000 --- a/vendor/github.com/mattermost/platform/model/initial_load.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type InitialLoad struct { - User *User `json:"user"` - TeamMembers []*TeamMember `json:"team_members"` - Teams []*Team `json:"teams"` - Preferences Preferences `json:"preferences"` - ClientCfg map[string]string `json:"client_cfg"` - LicenseCfg map[string]string `json:"license_cfg"` - NoAccounts bool `json:"no_accounts"` -} - -func (me *InitialLoad) ToJson() string { - b, err := json.Marshal(me) - if err != nil { - return "" - } else { - return string(b) - } -} - -func InitialLoadFromJson(data io.Reader) *InitialLoad { - decoder := json.NewDecoder(data) - var o InitialLoad - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/job.go b/vendor/github.com/mattermost/platform/model/job.go deleted file mode 100644 index 9a756602..00000000 --- a/vendor/github.com/mattermost/platform/model/job.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "time" -) - -const ( - JOB_TYPE_DATA_RETENTION = "data_retention" - JOB_TYPE_MESSAGE_EXPORT = "message_export" - JOB_TYPE_ELASTICSEARCH_POST_INDEXING = "elasticsearch_post_indexing" - JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION = "elasticsearch_post_aggregation" - JOB_TYPE_LDAP_SYNC = "ldap_sync" - - JOB_STATUS_PENDING = "pending" - JOB_STATUS_IN_PROGRESS = "in_progress" - JOB_STATUS_SUCCESS = "success" - JOB_STATUS_ERROR = "error" - JOB_STATUS_CANCEL_REQUESTED = "cancel_requested" - JOB_STATUS_CANCELED = "canceled" -) - -type Job struct { - Id string `json:"id"` - Type string `json:"type"` - Priority int64 `json:"priority"` - CreateAt int64 `json:"create_at"` - StartAt int64 `json:"start_at"` - LastActivityAt int64 `json:"last_activity_at"` - Status string `json:"status"` - Progress int64 `json:"progress"` - Data map[string]string `json:"data"` -} - -func (j *Job) IsValid() *AppError { - if len(j.Id) != 26 { - return NewAppError("Job.IsValid", "model.job.is_valid.id.app_error", nil, "id="+j.Id, http.StatusBadRequest) - } - - if j.CreateAt == 0 { - return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest) - } - - switch j.Type { - case JOB_TYPE_DATA_RETENTION: - case JOB_TYPE_ELASTICSEARCH_POST_INDEXING: - case JOB_TYPE_ELASTICSEARCH_POST_AGGREGATION: - case JOB_TYPE_LDAP_SYNC: - case JOB_TYPE_MESSAGE_EXPORT: - default: - return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest) - } - - switch j.Status { - case JOB_STATUS_PENDING: - case JOB_STATUS_IN_PROGRESS: - case JOB_STATUS_SUCCESS: - case JOB_STATUS_ERROR: - case JOB_STATUS_CANCEL_REQUESTED: - case JOB_STATUS_CANCELED: - default: - return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest) - } - - return nil -} - -func (js *Job) ToJson() string { - if b, err := json.Marshal(js); err != nil { - return "" - } else { - return string(b) - } -} - -func JobFromJson(data io.Reader) *Job { - var job Job - if err := json.NewDecoder(data).Decode(&job); err == nil { - return &job - } else { - return nil - } -} - -func JobsToJson(jobs []*Job) string { - if b, err := json.Marshal(jobs); err != nil { - return "" - } else { - return string(b) - } -} - -func JobsFromJson(data io.Reader) []*Job { - var jobs []*Job - if err := json.NewDecoder(data).Decode(&jobs); err == nil { - return jobs - } else { - return nil - } -} - -func (js *Job) DataToJson() string { - if b, err := json.Marshal(js.Data); err != nil { - return "" - } else { - return string(b) - } -} - -type Worker interface { - Run() - Stop() - JobChannel() chan<- Job -} - -type Scheduler interface { - Name() string - JobType() string - Enabled(cfg *Config) bool - NextScheduleTime(cfg *Config, now time.Time, pendingJobs bool, lastSuccessfulJob *Job) *time.Time - ScheduleJob(cfg *Config, pendingJobs bool, lastSuccessfulJob *Job) (*Job, *AppError) -} diff --git a/vendor/github.com/mattermost/platform/model/ldap.go b/vendor/github.com/mattermost/platform/model/ldap.go deleted file mode 100644 index 1453a4ad..00000000 --- a/vendor/github.com/mattermost/platform/model/ldap.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -const ( - USER_AUTH_SERVICE_LDAP = "ldap" - LDAP_SYNC_TASK_NAME = "LDAP Syncronization" -) diff --git a/vendor/github.com/mattermost/platform/model/license.go b/vendor/github.com/mattermost/platform/model/license.go deleted file mode 100644 index a81f882c..00000000 --- a/vendor/github.com/mattermost/platform/model/license.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" -) - -const ( - EXPIRED_LICENSE_ERROR = "api.license.add_license.expired.app_error" - INVALID_LICENSE_ERROR = "api.license.add_license.invalid.app_error" -) - -type LicenseRecord struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - 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"` - GoogleOAuth *bool `json:"google_oauth"` - Office365OAuth *bool `json:"office365_oauth"` - Compliance *bool `json:"compliance"` - Cluster *bool `json:"cluster"` - Metrics *bool `json:"metrics"` - CustomBrand *bool `json:"custom_brand"` - MHPNS *bool `json:"mhpns"` - SAML *bool `json:"saml"` - PasswordRequirements *bool `json:"password_requirements"` - Elasticsearch *bool `json:"elastic_search"` - Announcement *bool `json:"announcement"` - ThemeManagement *bool `json:"theme_management"` - EmailNotificationContents *bool `json:"email_notification_contents"` - DataRetention *bool `json:"data_retention"` - MessageExport *bool `json:"message_export"` - - // after we enabled more features for webrtc we'll need to control them with this - FutureFeatures *bool `json:"future_features"` -} - -func (f *Features) ToMap() map[string]interface{} { - return map[string]interface{}{ - "ldap": *f.LDAP, - "mfa": *f.MFA, - "google": *f.GoogleOAuth, - "office365": *f.Office365OAuth, - "compliance": *f.Compliance, - "cluster": *f.Cluster, - "metrics": *f.Metrics, - "custom_brand": *f.CustomBrand, - "mhpns": *f.MHPNS, - "saml": *f.SAML, - "password": *f.PasswordRequirements, - "elastic_search": *f.Elasticsearch, - "email_notification_contents": *f.EmailNotificationContents, - "data_retention": *f.DataRetention, - "message_export": *f.MessageExport, - "future": *f.FutureFeatures, - } -} - -func (f *Features) SetDefaults() { - if f.FutureFeatures == nil { - f.FutureFeatures = NewBool(true) - } - - if f.Users == nil { - f.Users = NewInt(0) - } - - if f.LDAP == nil { - f.LDAP = NewBool(*f.FutureFeatures) - } - - if f.MFA == nil { - f.MFA = NewBool(*f.FutureFeatures) - } - - if f.GoogleOAuth == nil { - f.GoogleOAuth = NewBool(*f.FutureFeatures) - } - - if f.Office365OAuth == nil { - f.Office365OAuth = NewBool(*f.FutureFeatures) - } - - if f.Compliance == nil { - f.Compliance = NewBool(*f.FutureFeatures) - } - - if f.Cluster == nil { - f.Cluster = NewBool(*f.FutureFeatures) - } - - if f.Metrics == nil { - f.Metrics = NewBool(*f.FutureFeatures) - } - - if f.CustomBrand == nil { - f.CustomBrand = NewBool(*f.FutureFeatures) - } - - if f.MHPNS == nil { - f.MHPNS = NewBool(*f.FutureFeatures) - } - - if f.SAML == nil { - f.SAML = NewBool(*f.FutureFeatures) - } - - if f.PasswordRequirements == nil { - f.PasswordRequirements = NewBool(*f.FutureFeatures) - } - - if f.Elasticsearch == nil { - f.Elasticsearch = NewBool(*f.FutureFeatures) - } - - if f.Announcement == nil { - f.Announcement = NewBool(true) - } - - if f.ThemeManagement == nil { - f.ThemeManagement = NewBool(true) - } - - if f.EmailNotificationContents == nil { - f.EmailNotificationContents = NewBool(*f.FutureFeatures) - } - - if f.DataRetention == nil { - f.DataRetention = NewBool(*f.FutureFeatures) - } - - if f.MessageExport == nil { - f.MessageExport = NewBool(*f.FutureFeatures) - } -} - -func (l *License) IsExpired() bool { - return l.ExpiresAt < GetMillis() -} - -func (l *License) IsStarted() bool { - return l.StartsAt < GetMillis() -} - -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 NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if lr.CreateAt == 0 { - return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) - } - - if len(lr.Bytes) == 0 || len(lr.Bytes) > 10000 { - return NewAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (lr *LicenseRecord) PreSave() { - lr.CreateAt = GetMillis() -} diff --git a/vendor/github.com/mattermost/platform/model/manifest.go b/vendor/github.com/mattermost/platform/model/manifest.go deleted file mode 100644 index 03d78f84..00000000 --- a/vendor/github.com/mattermost/platform/model/manifest.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "io/ioutil" - "os" - "path/filepath" - - "gopkg.in/yaml.v2" -) - -const ( - PLUGIN_CONFIG_TYPE_TEXT = "text" - PLUGIN_CONFIG_TYPE_BOOL = "bool" - PLUGIN_CONFIG_TYPE_RADIO = "radio" - PLUGIN_CONFIG_TYPE_DROPDOWN = "dropdown" - PLUGIN_CONFIG_TYPE_GENERATED = "generated" - PLUGIN_CONFIG_TYPE_USERNAME = "username" -) - -type PluginOption struct { - // The display name for the option. - DisplayName string `json:"display_name" yaml:"display_name"` - - // The string value for the option. - Value string `json:"value" yaml:"value"` -} - -type PluginSetting struct { - // The key that the setting will be assigned to in the configuration file. - Key string `json:"key" yaml:"key"` - - // The display name for the setting. - DisplayName string `json:"display_name" yaml:"display_name"` - - // The type of the setting. - // - // "bool" will result in a boolean true or false setting. - // - // "dropdown" will result in a string setting that allows the user to select from a list of - // pre-defined options. - // - // "generated" will result in a string setting that is set to a random, cryptographically secure - // string. - // - // "radio" will result in a string setting that allows the user to select from a short selection - // of pre-defined options. - // - // "text" will result in a string setting that can be typed in manually. - // - // "username" will result in a text setting that will autocomplete to a username. - Type string `json:"type" yaml:"type"` - - // The help text to display to the user. - HelpText string `json:"help_text" yaml:"help_text"` - - // The help text to display alongside the "Regenerate" button for settings of the "generated" type. - RegenerateHelpText string `json:"regenerate_help_text,omitempty" yaml:"regenerate_help_text,omitempty"` - - // The placeholder to display for "text", "generated" and "username" types when blank. - Placeholder string `json:"placeholder" yaml:"placeholder"` - - // The default value of the setting. - Default interface{} `json:"default" yaml:"default"` - - // For "radio" or "dropdown" settings, this is the list of pre-defined options that the user can choose - // from. - Options []*PluginOption `json:"options,omitempty" yaml:"options,omitempty"` -} - -type PluginSettingsSchema struct { - // Optional text to display above the settings. - Header string `json:"header" yaml:"header"` - - // Optional text to display below the settings. - Footer string `json:"footer" yaml:"footer"` - - // A list of setting definitions. - Settings []*PluginSetting `json:"settings" yaml:"settings"` -} - -// The plugin manifest defines the metadata required to load and present your plugin. The manifest -// file should be named plugin.json or plugin.yaml and placed in the top of your -// plugin bundle. -// -// Example plugin.yaml: -// -// id: com.mycompany.myplugin -// name: My Plugin -// description: This is my plugin. It does stuff. -// backend: -// executable: myplugin -// settings_schema: -// settings: -// - key: enable_extra_thing -// type: bool -// display_name: Enable Extra Thing -// help_text: When true, an extra thing will be enabled! -// default: false -type Manifest struct { - // The id is a globally unique identifier that represents your plugin. Ids are limited - // to 190 characters. Reverse-DNS notation using a name you control is a good option. - // For example, "com.mycompany.myplugin". - Id string `json:"id" yaml:"id"` - - // The name to be displayed for the plugin. - Name string `json:"name,omitempty" yaml:"name,omitempty"` - - // A description of what your plugin is and does. - Description string `json:"description,omitempty" yaml:"description,omitempty"` - - // A version number for your plugin. Semantic versioning is recommended: http://semver.org - Version string `json:"version" yaml:"version"` - - // If your plugin extends the server, you'll need define backend. - Backend *ManifestBackend `json:"backend,omitempty" yaml:"backend,omitempty"` - - // If your plugin extends the web app, you'll need to define webapp. - Webapp *ManifestWebapp `json:"webapp,omitempty" yaml:"webapp,omitempty"` - - // To allow administrators to configure your plugin via the Mattermost system console, you can - // provide your settings schema. - SettingsSchema *PluginSettingsSchema `json:"settings_schema,omitempty" yaml:"settings_schema,omitempty"` -} - -type ManifestBackend struct { - // The path to your executable binary. This should be relative to the root of your bundle and the - // location of the manifest file. - // - // On Windows, this file must have a ".exe" extension. - Executable string `json:"executable" yaml:"executable"` -} - -type ManifestWebapp struct { - // The path to your webapp bundle. This should be relative to the root of your bundle and the - // location of the manifest file. - BundlePath string `json:"bundle_path" yaml:"bundle_path"` -} - -func (m *Manifest) ToJson() string { - b, err := json.Marshal(m) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ManifestListToJson(m []*Manifest) string { - b, err := json.Marshal(m) - if err != nil { - return "" - } else { - return string(b) - } -} - -func ManifestFromJson(data io.Reader) *Manifest { - decoder := json.NewDecoder(data) - var m Manifest - err := decoder.Decode(&m) - if err == nil { - return &m - } else { - return nil - } -} - -func ManifestListFromJson(data io.Reader) []*Manifest { - decoder := json.NewDecoder(data) - var manifests []*Manifest - err := decoder.Decode(&manifests) - if err == nil { - return manifests - } else { - return nil - } -} - -func (m *Manifest) HasClient() bool { - return m.Webapp != nil -} - -func (m *Manifest) ClientManifest() *Manifest { - cm := new(Manifest) - *cm = *m - cm.Name = "" - cm.Description = "" - cm.Backend = nil - return cm -} - -// FindManifest will find and parse the manifest in a given directory. -// -// In all cases other than a does-not-exist error, path is set to the path of the manifest file that was -// found. -// -// Manifests are JSON or YAML files named plugin.json, plugin.yaml, or plugin.yml. -func FindManifest(dir string) (manifest *Manifest, path string, err error) { - for _, name := range []string{"plugin.yml", "plugin.yaml"} { - path = filepath.Join(dir, name) - f, ferr := os.Open(path) - if ferr != nil { - if !os.IsNotExist(ferr) { - err = ferr - return - } - continue - } - b, ioerr := ioutil.ReadAll(f) - f.Close() - if ioerr != nil { - err = ioerr - return - } - var parsed Manifest - err = yaml.Unmarshal(b, &parsed) - if err != nil { - return - } - manifest = &parsed - return - } - - path = filepath.Join(dir, "plugin.json") - f, ferr := os.Open(path) - if ferr != nil { - if os.IsNotExist(ferr) { - path = "" - } - err = ferr - return - } - defer f.Close() - var parsed Manifest - err = json.NewDecoder(f).Decode(&parsed) - if err != nil { - return - } - manifest = &parsed - return -} diff --git a/vendor/github.com/mattermost/platform/model/message_export.go b/vendor/github.com/mattermost/platform/model/message_export.go deleted file mode 100644 index b59b114d..00000000 --- a/vendor/github.com/mattermost/platform/model/message_export.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -type MessageExport struct { - ChannelId *string - ChannelDisplayName *string - - UserId *string - UserEmail *string - - PostId *string - PostCreateAt *int64 - PostMessage *string - PostType *string - PostFileIds StringArray -} diff --git a/vendor/github.com/mattermost/platform/model/mfa_secret.go b/vendor/github.com/mattermost/platform/model/mfa_secret.go deleted file mode 100644 index 99580f5f..00000000 --- a/vendor/github.com/mattermost/platform/model/mfa_secret.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type MfaSecret struct { - Secret string `json:"secret"` - QRCode string `json:"qr_code"` -} - -func (me *MfaSecret) ToJson() string { - b, err := json.Marshal(me) - if err != nil { - return "" - } else { - return string(b) - } -} - -func MfaSecretFromJson(data io.Reader) *MfaSecret { - decoder := json.NewDecoder(data) - var me MfaSecret - err := decoder.Decode(&me) - if err == nil { - return &me - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/oauth.go b/vendor/github.com/mattermost/platform/model/oauth.go deleted file mode 100644 index 3139aefe..00000000 --- a/vendor/github.com/mattermost/platform/model/oauth.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "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" - OAUTH_ACTION_MOBILE = "mobile" -) - -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"` - IconURL string `json:"icon_url"` - CallbackUrls StringArray `json:"callback_urls"` - Homepage string `json:"homepage"` - IsTrusted bool `json:"is_trusted"` -} - -// 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 NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "", http.StatusBadRequest) - } - - if a.CreateAt == 0 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if a.UpdateAt == 0 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if len(a.CreatorId) != 26 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if len(a.Name) == 0 || len(a.Name) > 64 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - for _, callback := range a.CallbackUrls { - if !IsValidHttpUrl(callback) { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest) - } - } - - if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(a.Description) > 512 { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - - if len(a.IconURL) > 0 { - if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { - return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest) - } - } - - 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 -} - -// 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 - } -} - -func OAuthAppListToJson(l []*OAuthApp) string { - b, err := json.Marshal(l) - if err != nil { - return "" - } else { - return string(b) - } -} - -func OAuthAppListFromJson(data io.Reader) []*OAuthApp { - decoder := json.NewDecoder(data) - var o []*OAuthApp - err := decoder.Decode(&o) - if err == nil { - return o - } 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 deleted file mode 100644 index 477a277d..00000000 --- a/vendor/github.com/mattermost/platform/model/outgoing_webhook.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" -) - -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"` - TriggerWhen int `json:"trigger_when"` - CallbackURLs StringArray `json:"callback_urls"` - DisplayName string `json:"display_name"` - Description string `json:"description"` - ContentType string `json:"content_type"` -} - -type OutgoingWebhookPayload struct { - Token string `json:"token"` - TeamId string `json:"team_id"` - TeamDomain string `json:"team_domain"` - ChannelId string `json:"channel_id"` - ChannelName string `json:"channel_name"` - Timestamp int64 `json:"timestamp"` - UserId string `json:"user_id"` - UserName string `json:"user_name"` - PostId string `json:"post_id"` - Text string `json:"text"` - TriggerWord string `json:"trigger_word"` - FileIds string `json:"file_ids"` -} - -type OutgoingWebhookResponse struct { - Text *string `json:"text"` - Username string `json:"username"` - IconURL string `json:"icon_url"` - Props StringInterface `json:"props"` - Type string `json:"type"` - ResponseType string `json:"response_type"` -} - -const OUTGOING_HOOK_RESPONSE_TYPE_COMMENT = "comment" - -func (o *OutgoingWebhookPayload) ToJSON() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (o *OutgoingWebhookPayload) ToFormValues() string { - v := url.Values{} - v.Set("token", o.Token) - v.Set("team_id", o.TeamId) - v.Set("team_domain", o.TeamDomain) - v.Set("channel_id", o.ChannelId) - v.Set("channel_name", o.ChannelName) - v.Set("timestamp", strconv.FormatInt(o.Timestamp/1000, 10)) - v.Set("user_id", o.UserId) - v.Set("user_name", o.UserName) - v.Set("post_id", o.PostId) - v.Set("text", o.Text) - v.Set("trigger_word", o.TriggerWord) - v.Set("file_ids", o.FileIds) - - return v.Encode() -} - -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 *OutgoingWebhookResponse) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func OutgoingWebhookResponseFromJson(data io.Reader) *OutgoingWebhookResponse { - decoder := json.NewDecoder(data) - var o OutgoingWebhookResponse - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func (o *OutgoingWebhook) IsValid() *AppError { - - if len(o.Id) != 26 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Token) != 26 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.CreatorId) != 26 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.TeamId) != 26 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.TriggerWords) != 0 { - for _, triggerWord := range o.TriggerWords { - if len(triggerWord) == 0 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest) - } - } - } - - if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest) - } - - for _, callback := range o.CallbackURLs { - if !IsValidHttpUrl(callback) { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest) - } - } - - if len(o.DisplayName) > 64 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.Description) > 128 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ContentType) > 128 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest) - } - - if o.TriggerWhen > 1 { - return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest) - } - - 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) TriggerWordExactMatch(word string) bool { - if len(word) == 0 { - return false - } - - for _, trigger := range o.TriggerWords { - if trigger == word { - return true - } - } - - return false -} - -func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { - if len(word) == 0 { - return false - } - - for _, trigger := range o.TriggerWords { - if strings.HasPrefix(word, trigger) { - return true - } - } - - return false -} - -func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) { - if len(word) == 0 { - return - } - - if isExactMatch { - for _, trigger := range o.TriggerWords { - if trigger == word { - triggerWord = trigger - break - } - } - } else { - for _, trigger := range o.TriggerWords { - if strings.HasPrefix(word, trigger) { - triggerWord = trigger - break - } - } - } - - return triggerWord -} diff --git a/vendor/github.com/mattermost/platform/model/plugin_key_value.go b/vendor/github.com/mattermost/platform/model/plugin_key_value.go deleted file mode 100644 index b7a7731c..00000000 --- a/vendor/github.com/mattermost/platform/model/plugin_key_value.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "net/http" - "unicode/utf8" -) - -const ( - KEY_VALUE_PLUGIN_ID_MAX_RUNES = 190 - KEY_VALUE_KEY_MAX_RUNES = 50 -) - -type PluginKeyValue struct { - PluginId string `json:"plugin_id"` - Key string `json:"key" db:"PKey"` - Value []byte `json:"value" db:"PValue"` -} - -func (kv *PluginKeyValue) IsValid() *AppError { - if len(kv.PluginId) == 0 || utf8.RuneCountInString(kv.PluginId) > KEY_VALUE_PLUGIN_ID_MAX_RUNES { - return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.plugin_id.app_error", map[string]interface{}{"Max": KEY_VALUE_KEY_MAX_RUNES, "Min": 0}, "key="+kv.Key, http.StatusBadRequest) - } - - if len(kv.Key) == 0 || utf8.RuneCountInString(kv.Key) > KEY_VALUE_KEY_MAX_RUNES { - return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.key.app_error", map[string]interface{}{"Max": KEY_VALUE_KEY_MAX_RUNES, "Min": 0}, "key="+kv.Key, http.StatusBadRequest) - } - - return nil -} diff --git a/vendor/github.com/mattermost/platform/model/plugins_response.go b/vendor/github.com/mattermost/platform/model/plugins_response.go deleted file mode 100644 index 74c89af2..00000000 --- a/vendor/github.com/mattermost/platform/model/plugins_response.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type PluginInfo struct { - Manifest - Prepackaged bool `json:"prepackaged"` -} - -type PluginsResponse struct { - Active []*PluginInfo `json:"active"` - Inactive []*PluginInfo `json:"inactive"` -} - -func (m *PluginsResponse) ToJson() string { - b, err := json.Marshal(m) - if err != nil { - return "" - } else { - return string(b) - } -} - -func PluginsResponseFromJson(data io.Reader) *PluginsResponse { - decoder := json.NewDecoder(data) - var m PluginsResponse - err := decoder.Decode(&m) - if err == nil { - return &m - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/post.go b/vendor/github.com/mattermost/platform/model/post.go deleted file mode 100644 index 6b282fbf..00000000 --- a/vendor/github.com/mattermost/platform/model/post.go +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "regexp" - "strings" - "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" // Deprecated, use POST_JOIN_CHANNEL or POST_LEAVE_CHANNEL instead - POST_JOIN_CHANNEL = "system_join_channel" - POST_LEAVE_CHANNEL = "system_leave_channel" - POST_JOIN_TEAM = "system_join_team" - POST_LEAVE_TEAM = "system_leave_team" - POST_ADD_REMOVE = "system_add_remove" // Deprecated, use POST_ADD_TO_CHANNEL or POST_REMOVE_FROM_CHANNEL instead - POST_ADD_TO_CHANNEL = "system_add_to_channel" - POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel" - POST_ADD_TO_TEAM = "system_add_to_team" - POST_REMOVE_FROM_TEAM = "system_remove_from_team" - POST_HEADER_CHANGE = "system_header_change" - POST_DISPLAYNAME_CHANGE = "system_displayname_change" - POST_PURPOSE_CHANGE = "system_purpose_change" - POST_CHANNEL_DELETED = "system_channel_deleted" - POST_EPHEMERAL = "system_ephemeral" - POST_FILEIDS_MAX_RUNES = 150 - POST_FILENAMES_MAX_RUNES = 4000 - POST_HASHTAGS_MAX_RUNES = 1000 - POST_MESSAGE_MAX_RUNES = 4000 - POST_PROPS_MAX_RUNES = 8000 - POST_PROPS_MAX_USER_RUNES = POST_PROPS_MAX_RUNES - 400 // Leave some room for system / pre-save modifications - POST_CUSTOM_TYPE_PREFIX = "custom_" - PROPS_ADD_CHANNEL_MEMBER = "add_channel_member" -) - -type Post struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - EditAt int64 `json:"edit_at"` - DeleteAt int64 `json:"delete_at"` - IsPinned bool `json:"is_pinned"` - 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,omitempty"` // Deprecated, do not use this field any more - FileIds StringArray `json:"file_ids,omitempty"` - PendingPostId string `json:"pending_post_id" db:"-"` - HasReactions bool `json:"has_reactions,omitempty"` -} - -type PostPatch struct { - IsPinned *bool `json:"is_pinned"` - Message *string `json:"message"` - Props *StringInterface `json:"props"` - FileIds *StringArray `json:"file_ids"` - HasReactions *bool `json:"has_reactions"` -} - -type PostForIndexing struct { - Post - TeamId string `json:"team_id"` - ParentCreateAt *int64 `json:"parent_create_at"` -} - -type PostAction struct { - Id string `json:"id"` - Name string `json:"name"` - Integration *PostActionIntegration `json:"integration,omitempty"` -} - -type PostActionIntegration struct { - URL string `json:"url,omitempty"` - Context StringInterface `json:"context,omitempty"` -} - -type PostActionIntegrationRequest struct { - UserId string `json:"user_id"` - Context StringInterface `json:"context,omitempty"` -} - -type PostActionIntegrationResponse struct { - Update *Post `json:"update"` - EphemeralText string `json:"ephemeral_text"` -} - -func (o *Post) ToJson() string { - copy := *o - copy.StripActionIntegrations() - b, err := json.Marshal(©) - 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 NewAppError("Post.IsValid", "model.post.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("Post.IsValid", "model.post.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("Post.IsValid", "model.post.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.UserId) != 26 { - return NewAppError("Post.IsValid", "model.post.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ChannelId) != 26 { - return NewAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) - } - - if !(len(o.RootId) == 26 || len(o.RootId) == 0) { - return NewAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "", http.StatusBadRequest) - } - - if !(len(o.ParentId) == 26 || len(o.ParentId) == 0) { - return NewAppError("Post.IsValid", "model.post.is_valid.parent_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.ParentId) == 26 && len(o.RootId) == 0 { - return NewAppError("Post.IsValid", "model.post.is_valid.root_parent.app_error", nil, "", http.StatusBadRequest) - } - - if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) { - return NewAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "", http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.Message) > POST_MESSAGE_MAX_RUNES { - return NewAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.Hashtags) > POST_HASHTAGS_MAX_RUNES { - return NewAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - switch o.Type { - case - POST_DEFAULT, - POST_JOIN_LEAVE, - POST_ADD_REMOVE, - POST_JOIN_CHANNEL, - POST_LEAVE_CHANNEL, - POST_JOIN_TEAM, - POST_LEAVE_TEAM, - POST_ADD_TO_CHANNEL, - POST_REMOVE_FROM_CHANNEL, - POST_ADD_TO_TEAM, - POST_REMOVE_FROM_TEAM, - POST_SLACK_ATTACHMENT, - POST_HEADER_CHANGE, - POST_PURPOSE_CHANGE, - POST_DISPLAYNAME_CHANGE, - POST_CHANNEL_DELETED: - default: - if !strings.HasPrefix(o.Type, POST_CUSTOM_TYPE_PREFIX) { - return NewAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type, http.StatusBadRequest) - } - } - - if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > POST_FILENAMES_MAX_RUNES { - return NewAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(ArrayToJson(o.FileIds)) > POST_FILEIDS_MAX_RUNES { - return NewAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > POST_PROPS_MAX_RUNES { - return NewAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - return nil -} - -func (o *Post) SanitizeProps() { - membersToSanitize := []string{ - PROPS_ADD_CHANNEL_MEMBER, - } - - for _, member := range membersToSanitize { - if _, ok := o.Props[member]; ok { - delete(o.Props, member) - } - } -} - -func (o *Post) PreSave() { - if o.Id == "" { - o.Id = NewId() - } - - o.OriginalId = "" - - if o.CreateAt == 0 { - o.CreateAt = GetMillis() - } - - o.UpdateAt = o.CreateAt - o.PreCommit() -} - -func (o *Post) PreCommit() { - if o.Props == nil { - o.Props = make(map[string]interface{}) - } - - if o.Filenames == nil { - o.Filenames = []string{} - } - - if o.FileIds == nil { - o.FileIds = []string{} - } - - o.GenerateActionIds() -} - -func (o *Post) MakeNonNil() { - if o.Props == nil { - o.Props = make(map[string]interface{}) - } -} - -func (o *Post) AddProp(key string, value interface{}) { - - o.MakeNonNil() - - o.Props[key] = value -} - -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 -} - -func (p *Post) Patch(patch *PostPatch) { - if patch.IsPinned != nil { - p.IsPinned = *patch.IsPinned - } - - if patch.Message != nil { - p.Message = *patch.Message - } - - if patch.Props != nil { - p.Props = *patch.Props - } - - if patch.FileIds != nil { - p.FileIds = *patch.FileIds - } - - if patch.HasReactions != nil { - p.HasReactions = *patch.HasReactions - } -} - -func (o *PostPatch) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } - - return string(b) -} - -func PostPatchFromJson(data io.Reader) *PostPatch { - decoder := json.NewDecoder(data) - var post PostPatch - err := decoder.Decode(&post) - if err != nil { - return nil - } - - return &post -} - -var channelMentionRegexp = regexp.MustCompile(`\B~[a-zA-Z0-9\-_]+`) - -func (o *Post) ChannelMentions() (names []string) { - if strings.Contains(o.Message, "~") { - alreadyMentioned := make(map[string]bool) - for _, match := range channelMentionRegexp.FindAllString(o.Message, -1) { - name := match[1:] - if !alreadyMentioned[name] { - names = append(names, name) - alreadyMentioned[name] = true - } - } - } - return -} - -func (r *PostActionIntegrationRequest) ToJson() string { - b, err := json.Marshal(r) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (o *Post) Attachments() []*SlackAttachment { - if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok { - return attachments - } - var ret []*SlackAttachment - if attachments, ok := o.Props["attachments"].([]interface{}); ok { - for _, attachment := range attachments { - if enc, err := json.Marshal(attachment); err == nil { - var decoded SlackAttachment - if json.Unmarshal(enc, &decoded) == nil { - ret = append(ret, &decoded) - } - } - } - } - return ret -} - -func (o *Post) StripActionIntegrations() { - attachments := o.Attachments() - if o.Props["attachments"] != nil { - o.Props["attachments"] = attachments - } - for _, attachment := range attachments { - for _, action := range attachment.Actions { - action.Integration = nil - } - } -} - -func (o *Post) GetAction(id string) *PostAction { - for _, attachment := range o.Attachments() { - for _, action := range attachment.Actions { - if action.Id == id { - return action - } - } - } - return nil -} - -func (o *Post) GenerateActionIds() { - if o.Props["attachments"] != nil { - o.Props["attachments"] = o.Attachments() - } - if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok { - for _, attachment := range attachments { - for _, action := range attachment.Actions { - if action.Id == "" { - action.Id = NewId() - } - } - } - } -} diff --git a/vendor/github.com/mattermost/platform/model/post_list.go b/vendor/github.com/mattermost/platform/model/post_list.go deleted file mode 100644 index 018f7d14..00000000 --- a/vendor/github.com/mattermost/platform/model/post_list.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "sort" -) - -type PostList struct { - Order []string `json:"order"` - Posts map[string]*Post `json:"posts"` -} - -func NewPostList() *PostList { - return &PostList{ - Order: make([]string, 0), - Posts: make(map[string]*Post), - } -} - -func (o *PostList) StripActionIntegrations() { - posts := o.Posts - o.Posts = make(map[string]*Post) - for id, post := range posts { - pcopy := *post - pcopy.StripActionIntegrations() - o.Posts[id] = &pcopy - } -} - -func (o *PostList) ToJson() string { - copy := *o - copy.StripActionIntegrations() - b, err := json.Marshal(©) - 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) SortByCreateAt() { - sort.Slice(o.Order, func(i, j int) bool { - return o.Posts[o.Order[i]].CreateAt > o.Posts[o.Order[j]].CreateAt - }) -} - -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 - } else if v.UpdateAt == t && v.Id > id { - t = v.UpdateAt - id = v.Id - } - } - - orderId := "" - if len(o.Order) > 0 { - orderId = o.Order[0] - } - - return Etag(orderId, 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 deleted file mode 100644 index 5d462de8..00000000 --- a/vendor/github.com/mattermost/platform/model/preference.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "regexp" - "strings" - "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_FLAGGED_POST = "flagged_post" - PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel" - - PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" - PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews" - - PREFERENCE_CATEGORY_THEME = "theme" - // the name for theme props is the team id - - PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app" - // the name for oauth_app is the client_id and value is the current scope - - PREFERENCE_CATEGORY_LAST = "last" - PREFERENCE_NAME_LAST_CHANNEL = "channel" - PREFERENCE_NAME_LAST_TEAM = "team" - - PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" - PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval" - - PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS = "30" // the "immediate" setting is actually 30s - PREFERENCE_EMAIL_INTERVAL_BATCHING_SECONDS = "900" // fifteen minutes is 900 seconds -) - -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 NewAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest) - } - - if len(o.Category) == 0 || len(o.Category) > 32 { - return NewAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category, http.StatusBadRequest) - } - - if len(o.Name) > 32 { - return NewAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name, http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.Value) > 2000 { - return NewAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value, http.StatusBadRequest) - } - - if o.Category == PREFERENCE_CATEGORY_THEME { - var unused map[string]string - if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil { - return NewAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value, http.StatusBadRequest) - } - } - - return nil -} - -func (o *Preference) PreUpdate() { - if o.Category == PREFERENCE_CATEGORY_THEME { - // decode the value of theme (a map of strings to string) and eliminate any invalid values - var props map[string]string - if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil { - // just continue, the invalid preference value should get caught by IsValid before saving - return - } - - colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) - - // blank out any invalid theme values - for name, value := range props { - if name == "image" || name == "type" || name == "codeTheme" { - continue - } - - if !colorPattern.MatchString(value) { - props[name] = "#ffffff" - } - } - - if b, err := json.Marshal(props); err == nil { - o.Value = string(b) - } - } -} diff --git a/vendor/github.com/mattermost/platform/model/preferences.go b/vendor/github.com/mattermost/platform/model/preferences.go deleted file mode 100644 index b982e309..00000000 --- a/vendor/github.com/mattermost/platform/model/preferences.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2015-present 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 deleted file mode 100644 index 69719e74..00000000 --- a/vendor/github.com/mattermost/platform/model/push_notification.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "strings" -) - -const ( - PUSH_NOTIFY_APPLE = "apple" - PUSH_NOTIFY_ANDROID = "android" - PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn" - PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn" - - PUSH_TYPE_MESSAGE = "message" - PUSH_TYPE_CLEAR = "clear" - - // The category is set to handle a set of interactive Actions - // with the push notifications - CATEGORY_CAN_REPLY = "CAN_REPLY" - - MHPNS = "https://push.mattermost.com" -) - -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"` - TeamId string `json:"team_id"` - ChannelId string `json:"channel_id"` - PostId string `json:"post_id"` - RootId string `json:"root_id"` - ChannelName string `json:"channel_name"` - Type string `json:"type"` - SenderId string `json:"sender_id"` - OverrideUsername string `json:"override_username"` - OverrideIconUrl string `json:"override_icon_url"` - FromWebhook string `json:"from_webhook"` -} - -func (me *PushNotification) ToJson() string { - b, err := json.Marshal(me) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) { - - index := strings.Index(deviceId, ":") - - if index > -1 { - me.Platform = deviceId[:index] - me.DeviceId = deviceId[index+1:] - } -} - -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/push_response.go b/vendor/github.com/mattermost/platform/model/push_response.go deleted file mode 100644 index 095986f3..00000000 --- a/vendor/github.com/mattermost/platform/model/push_response.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - PUSH_STATUS = "status" - PUSH_STATUS_OK = "OK" - PUSH_STATUS_FAIL = "FAIL" - PUSH_STATUS_REMOVE = "REMOVE" - PUSH_STATUS_ERROR_MSG = "error" -) - -type PushResponse map[string]string - -func NewOkPushResponse() PushResponse { - m := make(map[string]string) - m[PUSH_STATUS] = PUSH_STATUS_OK - return m -} - -func NewRemovePushResponse() PushResponse { - m := make(map[string]string) - m[PUSH_STATUS] = PUSH_STATUS_REMOVE - return m -} - -func NewErrorPushResponse(message string) PushResponse { - m := make(map[string]string) - m[PUSH_STATUS] = PUSH_STATUS_FAIL - m[PUSH_STATUS_ERROR_MSG] = message - return m -} - -func (me *PushResponse) ToJson() string { - if b, err := json.Marshal(me); err != nil { - return "" - } else { - return string(b) - } -} - -func PushResponseFromJson(data io.Reader) PushResponse { - decoder := json.NewDecoder(data) - - var objmap PushResponse - if err := decoder.Decode(&objmap); err != nil { - return make(map[string]string) - } else { - return objmap - } -} diff --git a/vendor/github.com/mattermost/platform/model/reaction.go b/vendor/github.com/mattermost/platform/model/reaction.go deleted file mode 100644 index 4b72dd44..00000000 --- a/vendor/github.com/mattermost/platform/model/reaction.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "regexp" -) - -type Reaction struct { - UserId string `json:"user_id"` - PostId string `json:"post_id"` - EmojiName string `json:"emoji_name"` - CreateAt int64 `json:"create_at"` -} - -func (o *Reaction) ToJson() string { - if b, err := json.Marshal(o); err != nil { - return "" - } else { - return string(b) - } -} - -func ReactionFromJson(data io.Reader) *Reaction { - var o Reaction - - if err := json.NewDecoder(data).Decode(&o); err != nil { - return nil - } else { - return &o - } -} - -func ReactionsToJson(o []*Reaction) string { - if b, err := json.Marshal(o); err != nil { - return "" - } else { - return string(b) - } -} - -func ReactionsFromJson(data io.Reader) []*Reaction { - var o []*Reaction - - if err := json.NewDecoder(data).Decode(&o); err != nil { - return nil - } else { - return o - } -} - -func (o *Reaction) IsValid() *AppError { - if len(o.UserId) != 26 { - return NewAppError("Reaction.IsValid", "model.reaction.is_valid.user_id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest) - } - - if len(o.PostId) != 26 { - return NewAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId, http.StatusBadRequest) - } - - validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`) - - if len(o.EmojiName) == 0 || len(o.EmojiName) > 64 || !validName.MatchString(o.EmojiName) { - return NewAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName, http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("Reaction.IsValid", "model.reaction.is_valid.create_at.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (o *Reaction) PreSave() { - if o.CreateAt == 0 { - o.CreateAt = GetMillis() - } -} diff --git a/vendor/github.com/mattermost/platform/model/saml.go b/vendor/github.com/mattermost/platform/model/saml.go deleted file mode 100644 index f5826a95..00000000 --- a/vendor/github.com/mattermost/platform/model/saml.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - USER_AUTH_SERVICE_SAML = "saml" - USER_AUTH_SERVICE_SAML_TEXT = "With SAML" - SAML_IDP_CERTIFICATE = 1 - SAML_PRIVATE_KEY = 2 - SAML_PUBLIC_CERT = 3 -) - -type SamlAuthRequest struct { - Base64AuthRequest string - URL string - RelayState string -} - -type SamlCertificateStatus struct { - IdpCertificateFile bool `json:"idp_certificate_file"` - PrivateKeyFile bool `json:"private_key_file"` - PublicCertificateFile bool `json:"public_certificate_file"` -} - -func (s *SamlCertificateStatus) ToJson() string { - b, err := json.Marshal(s) - if err != nil { - return "" - } else { - return string(b) - } -} - -func SamlCertificateStatusFromJson(data io.Reader) *SamlCertificateStatus { - decoder := json.NewDecoder(data) - var status SamlCertificateStatus - err := decoder.Decode(&status) - if err == nil { - return &status - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/scheduled_task.go b/vendor/github.com/mattermost/platform/model/scheduled_task.go deleted file mode 100644 index 453828bd..00000000 --- a/vendor/github.com/mattermost/platform/model/scheduled_task.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "fmt" - "sync" - "time" -) - -type TaskFunc func() - -type ScheduledTask struct { - Name string `json:"name"` - Interval time.Duration `json:"interval"` - Recurring bool `json:"recurring"` - function TaskFunc - timer *time.Timer -} - -var taskMutex = sync.Mutex{} -var tasks = make(map[string]*ScheduledTask) - -func addTask(task *ScheduledTask) { - taskMutex.Lock() - defer taskMutex.Unlock() - tasks[task.Name] = task -} - -func removeTaskByName(name string) { - taskMutex.Lock() - defer taskMutex.Unlock() - delete(tasks, name) -} - -func GetTaskByName(name string) *ScheduledTask { - taskMutex.Lock() - defer taskMutex.Unlock() - if task, ok := tasks[name]; ok { - return task - } - return nil -} - -func GetAllTasks() *map[string]*ScheduledTask { - taskMutex.Lock() - defer taskMutex.Unlock() - return &tasks -} - -func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask { - task := &ScheduledTask{ - Name: name, - Interval: timeToExecution, - Recurring: false, - function: function, - } - - taskRunner := func() { - go task.function() - removeTaskByName(task.Name) - } - - task.timer = time.AfterFunc(timeToExecution, taskRunner) - - addTask(task) - - return task -} - -func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask { - task := &ScheduledTask{ - Name: name, - Interval: interval, - Recurring: true, - function: function, - } - - taskRecurer := func() { - go task.function() - task.timer.Reset(task.Interval) - } - - task.timer = time.AfterFunc(interval, taskRecurer) - - addTask(task) - - return task -} - -func (task *ScheduledTask) Cancel() { - task.timer.Stop() - removeTaskByName(task.Name) -} - -// Executes the task immediatly. A recurring task will be run regularally after interval. -func (task *ScheduledTask) Execute() { - task.function() - task.timer.Reset(task.Interval) -} - -func (task *ScheduledTask) String() string { - return fmt.Sprintf( - "%s\nInterval: %s\nRecurring: %t\n", - task.Name, - task.Interval.String(), - task.Recurring, - ) -} diff --git a/vendor/github.com/mattermost/platform/model/search_params.go b/vendor/github.com/mattermost/platform/model/search_params.go deleted file mode 100644 index 2feea8da..00000000 --- a/vendor/github.com/mattermost/platform/model/search_params.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "regexp" - "strings" -) - -var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) -var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) - -type SearchParams struct { - Terms string - IsHashtag bool - InChannels []string - FromUsers []string - OrTerms bool -} - -func (o *SearchParams) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -var searchFlags = [...]string{"from", "channel", "in"} - -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, strings.Fields(text[location:i])...) - foundQuote = true - location = i - } - } - } - - words = append(words, strings.Fields(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 = searchTermPuncStart.ReplaceAllString(word, "") - word = searchTermPuncEnd.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: false, - 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 deleted file mode 100644 index b8c1dc48..00000000 --- a/vendor/github.com/mattermost/platform/model/security_bulletin.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2015-present 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 deleted file mode 100644 index 704af067..00000000 --- a/vendor/github.com/mattermost/platform/model/session.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "strings" -) - -const ( - SESSION_COOKIE_TOKEN = "MMAUTHTOKEN" - SESSION_COOKIE_USER = "MMUSERID" - SESSION_CACHE_SIZE = 35000 - SESSION_PROP_PLATFORM = "platform" - SESSION_PROP_OS = "os" - SESSION_PROP_BROWSER = "browser" - SESSION_PROP_TYPE = "type" - SESSION_PROP_USER_ACCESS_TOKEN_ID = "user_access_token_id" - SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken" - SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes - SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years -) - -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"` - DeviceId string `json:"device_id"` - Roles string `json:"roles"` - IsOAuth bool `json:"is_oauth"` - Props StringMap `json:"props"` - TeamMembers []*TeamMember `json:"team_members" db:"-"` -} - -func (me *Session) DeepCopy() *Session { - copy := *me - return © -} - -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() - } - - if me.Token == "" { - 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) { - if me.CreateAt == 0 { - me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) - } else { - me.ExpiresAt = me.CreateAt + (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 (me *Session) GetTeamByTeamId(teamId string) *TeamMember { - for _, team := range me.TeamMembers { - if team.TeamId == teamId { - return team - } - } - - return nil -} - -func (me *Session) IsMobileApp() bool { - return len(me.DeviceId) > 0 -} - -func (me *Session) GetUserRoles() []string { - return strings.Fields(me.Roles) -} - -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/slack_attachment.go b/vendor/github.com/mattermost/platform/model/slack_attachment.go deleted file mode 100644 index 197d3f0f..00000000 --- a/vendor/github.com/mattermost/platform/model/slack_attachment.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "fmt" -) - -type SlackAttachment struct { - Id int64 `json:"id"` - Fallback string `json:"fallback"` - Color string `json:"color"` - Pretext string `json:"pretext"` - AuthorName string `json:"author_name"` - AuthorLink string `json:"author_link"` - AuthorIcon string `json:"author_icon"` - Title string `json:"title"` - TitleLink string `json:"title_link"` - Text string `json:"text"` - Fields []*SlackAttachmentField `json:"fields"` - ImageURL string `json:"image_url"` - ThumbURL string `json:"thumb_url"` - Footer string `json:"footer"` - FooterIcon string `json:"footer_icon"` - Timestamp interface{} `json:"ts"` // This is either a string or an int64 - Actions []*PostAction `json:"actions,omitempty"` -} - -type SlackAttachmentField struct { - Title string `json:"title"` - Value interface{} `json:"value"` - Short bool `json:"short"` -} - -func StringifySlackFieldValue(a []*SlackAttachment) []*SlackAttachment { - var nonNilAttachments []*SlackAttachment - for _, attachment := range a { - if attachment == nil { - continue - } - nonNilAttachments = append(nonNilAttachments, attachment) - - var nonNilFields []*SlackAttachmentField - for _, field := range attachment.Fields { - if field == nil { - continue - } - nonNilFields = append(nonNilFields, field) - - if field.Value != nil { - // Ensure the value is set to a string if it is set - field.Value = fmt.Sprintf("%v", field.Value) - } - } - attachment.Fields = nonNilFields - } - return nonNilAttachments -} diff --git a/vendor/github.com/mattermost/platform/model/status.go b/vendor/github.com/mattermost/platform/model/status.go deleted file mode 100644 index 6da6161e..00000000 --- a/vendor/github.com/mattermost/platform/model/status.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - STATUS_OFFLINE = "offline" - STATUS_AWAY = "away" - STATUS_DND = "dnd" - STATUS_ONLINE = "online" - STATUS_CACHE_SIZE = SESSION_CACHE_SIZE - STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds - STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes -) - -type Status struct { - UserId string `json:"user_id"` - Status string `json:"status"` - Manual bool `json:"manual"` - LastActivityAt int64 `json:"last_activity_at"` - ActiveChannel string `json:"-" db:"-"` -} - -func (o *Status) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func StatusFromJson(data io.Reader) *Status { - decoder := json.NewDecoder(data) - var o Status - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func StatusListToJson(u []*Status) string { - b, err := json.Marshal(u) - if err != nil { - return "" - } else { - return string(b) - } -} - -func StatusListFromJson(data io.Reader) []*Status { - decoder := json.NewDecoder(data) - var statuses []*Status - err := decoder.Decode(&statuses) - if err == nil { - return statuses - } else { - return nil - } -} - -func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} { - interfaceMap := map[string]interface{}{} - for _, s := range statusMap { - // Omitted statues mean offline - if s.Status != STATUS_OFFLINE { - interfaceMap[s.UserId] = s.Status - } - } - return interfaceMap -} diff --git a/vendor/github.com/mattermost/platform/model/suggest_command.go b/vendor/github.com/mattermost/platform/model/suggest_command.go deleted file mode 100644 index 3d066499..00000000 --- a/vendor/github.com/mattermost/platform/model/suggest_command.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2015-present 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/switch_request.go b/vendor/github.com/mattermost/platform/model/switch_request.go deleted file mode 100644 index 10039294..00000000 --- a/vendor/github.com/mattermost/platform/model/switch_request.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type SwitchRequest struct { - CurrentService string `json:"current_service"` - NewService string `json:"new_service"` - Email string `json:"email"` - Password string `json:"password"` - NewPassword string `json:"new_password"` - MfaCode string `json:"mfa_code"` - LdapId string `json:"ldap_id"` -} - -func (o *SwitchRequest) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func SwitchRequestFromJson(data io.Reader) *SwitchRequest { - decoder := json.NewDecoder(data) - var o SwitchRequest - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func (o *SwitchRequest) EmailToOAuth() bool { - return o.CurrentService == USER_AUTH_SERVICE_EMAIL && - (o.NewService == USER_AUTH_SERVICE_SAML || - o.NewService == USER_AUTH_SERVICE_GITLAB || - o.NewService == SERVICE_GOOGLE || - o.NewService == SERVICE_OFFICE365) -} - -func (o *SwitchRequest) OAuthToEmail() bool { - return (o.CurrentService == USER_AUTH_SERVICE_SAML || - o.CurrentService == USER_AUTH_SERVICE_GITLAB || - o.CurrentService == SERVICE_GOOGLE || - o.CurrentService == SERVICE_OFFICE365) && o.NewService == USER_AUTH_SERVICE_EMAIL -} - -func (o *SwitchRequest) EmailToLdap() bool { - return o.CurrentService == USER_AUTH_SERVICE_EMAIL && o.NewService == USER_AUTH_SERVICE_LDAP -} - -func (o *SwitchRequest) LdapToEmail() bool { - return o.CurrentService == USER_AUTH_SERVICE_LDAP && o.NewService == USER_AUTH_SERVICE_EMAIL -} diff --git a/vendor/github.com/mattermost/platform/model/system.go b/vendor/github.com/mattermost/platform/model/system.go deleted file mode 100644 index e2f4283a..00000000 --- a/vendor/github.com/mattermost/platform/model/system.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2015-present 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 deleted file mode 100644 index 4fe03f2f..00000000 --- a/vendor/github.com/mattermost/platform/model/team.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "regexp" - "strings" - "unicode/utf8" -) - -const ( - TEAM_OPEN = "O" - TEAM_INVITE = "I" - TEAM_ALLOWED_DOMAINS_MAX_LENGTH = 500 - TEAM_COMPANY_NAME_MAX_LENGTH = 64 - TEAM_DESCRIPTION_MAX_LENGTH = 255 - TEAM_DISPLAY_NAME_MAX_RUNES = 64 - TEAM_EMAIL_MAX_LENGTH = 128 - TEAM_NAME_MAX_LENGTH = 64 - TEAM_NAME_MIN_LENGTH = 2 -) - -type Team struct { - 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"` - Description string `json:"description"` - 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"` -} - -type TeamPatch struct { - DisplayName *string `json:"display_name"` - Description *string `json:"description"` - CompanyName *string `json:"company_name"` - InviteId *string `json:"invite_id"` - AllowOpenInvite *bool `json:"allow_open_invite"` -} - -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) ToEmailList() []string { - emailList := make([]string, len(o.Invites)) - for _, invite := range o.Invites { - emailList = append(emailList, invite["email"]) - } - return emailList -} - -func (o *Invites) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - 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 TeamListToJson(t []*Team) string { - b, err := json.Marshal(t) - if err != nil { - return "" - } else { - return string(b) - } -} - -func TeamListFromJson(data io.Reader) []*Team { - decoder := json.NewDecoder(data) - var teams []*Team - err := decoder.Decode(&teams) - if err == nil { - return teams - } else { - return nil - } -} - -func (o *Team) Etag() string { - return Etag(o.Id, o.UpdateAt) -} - -func (o *Team) IsValid() *AppError { - - if len(o.Id) != 26 { - return NewAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if o.CreateAt == 0 { - return NewAppError("Team.IsValid", "model.team.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if o.UpdateAt == 0 { - return NewAppError("Team.IsValid", "model.team.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.Email) > TEAM_EMAIL_MAX_LENGTH { - return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.Email) > 0 && !IsValidEmail(o.Email) { - return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if utf8.RuneCountInString(o.DisplayName) == 0 || utf8.RuneCountInString(o.DisplayName) > TEAM_DISPLAY_NAME_MAX_RUNES { - return NewAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.Name) > TEAM_NAME_MAX_LENGTH { - return NewAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.Description) > TEAM_DESCRIPTION_MAX_LENGTH { - return NewAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if IsReservedTeamName(o.Name) { - return NewAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if !IsValidTeamName(o.Name) { - return NewAppError("Team.IsValid", "model.team.is_valid.characters.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) { - return NewAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.CompanyName) > TEAM_COMPANY_NAME_MAX_LENGTH { - return NewAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - if len(o.AllowedDomains) > TEAM_ALLOWED_DOMAINS_MAX_LENGTH { - return NewAppError("Team.IsValid", "model.team.is_valid.domains.app_error", nil, "id="+o.Id, http.StatusBadRequest) - } - - 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) { - return false - } - - if len(s) < TEAM_NAME_MIN_LENGTH { - 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) Sanitize() { - o.Email = "" - o.AllowedDomains = "" -} - -func (o *Team) SanitizeForNotLoggedIn() { - o.Email = "" - o.AllowedDomains = "" - o.CompanyName = "" - if !o.AllowOpenInvite { - o.InviteId = "" - } -} - -func (t *Team) Patch(patch *TeamPatch) { - if patch.DisplayName != nil { - t.DisplayName = *patch.DisplayName - } - - if patch.Description != nil { - t.Description = *patch.Description - } - - if patch.CompanyName != nil { - t.CompanyName = *patch.CompanyName - } - - if patch.InviteId != nil { - t.InviteId = *patch.InviteId - } - - if patch.AllowOpenInvite != nil { - t.AllowOpenInvite = *patch.AllowOpenInvite - } -} - -func (t *TeamPatch) ToJson() string { - b, err := json.Marshal(t) - if err != nil { - return "" - } - - return string(b) -} - -func TeamPatchFromJson(data io.Reader) *TeamPatch { - decoder := json.NewDecoder(data) - var team TeamPatch - err := decoder.Decode(&team) - if err != nil { - return nil - } - - return &team -} diff --git a/vendor/github.com/mattermost/platform/model/team_member.go b/vendor/github.com/mattermost/platform/model/team_member.go deleted file mode 100644 index 6c70b75e..00000000 --- a/vendor/github.com/mattermost/platform/model/team_member.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" - "strings" -) - -type TeamMember struct { - TeamId string `json:"team_id"` - UserId string `json:"user_id"` - Roles string `json:"roles"` - DeleteAt int64 `json:"delete_at"` -} - -type TeamUnread struct { - TeamId string `json:"team_id"` - MsgCount int64 `json:"msg_count"` - MentionCount int64 `json:"mention_count"` -} - -func (o *TeamMember) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (o *TeamUnread) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func TeamMemberFromJson(data io.Reader) *TeamMember { - decoder := json.NewDecoder(data) - var o TeamMember - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func TeamUnreadFromJson(data io.Reader) *TeamUnread { - decoder := json.NewDecoder(data) - var o TeamUnread - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func TeamMembersToJson(o []*TeamMember) string { - if b, err := json.Marshal(o); err != nil { - return "[]" - } else { - return string(b) - } -} - -func TeamMembersFromJson(data io.Reader) []*TeamMember { - decoder := json.NewDecoder(data) - var o []*TeamMember - err := decoder.Decode(&o) - if err == nil { - return o - } else { - return nil - } -} - -func TeamsUnreadToJson(o []*TeamUnread) string { - if b, err := json.Marshal(o); err != nil { - return "[]" - } else { - return string(b) - } -} - -func TeamsUnreadFromJson(data io.Reader) []*TeamUnread { - decoder := json.NewDecoder(data) - var o []*TeamUnread - err := decoder.Decode(&o) - if err == nil { - return o - } else { - return nil - } -} - -func (o *TeamMember) IsValid() *AppError { - - if len(o.TeamId) != 26 { - return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(o.UserId) != 26 { - return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (o *TeamMember) PreUpdate() { -} - -func (o *TeamMember) GetRoles() []string { - return strings.Fields(o.Roles) -} diff --git a/vendor/github.com/mattermost/platform/model/team_search.go b/vendor/github.com/mattermost/platform/model/team_search.go deleted file mode 100644 index e0676022..00000000 --- a/vendor/github.com/mattermost/platform/model/team_search.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type TeamSearch struct { - Term string `json:"term"` -} - -// ToJson convert a TeamSearch to json string -func (c *TeamSearch) ToJson() string { - b, err := json.Marshal(c) - if err != nil { - return "" - } - - return string(b) -} - -// TeamSearchFromJson decodes the input and returns a TeamSearch -func TeamSearchFromJson(data io.Reader) *TeamSearch { - decoder := json.NewDecoder(data) - var cs TeamSearch - err := decoder.Decode(&cs) - if err == nil { - return &cs - } - - return nil -} diff --git a/vendor/github.com/mattermost/platform/model/team_stats.go b/vendor/github.com/mattermost/platform/model/team_stats.go deleted file mode 100644 index e94ed37b..00000000 --- a/vendor/github.com/mattermost/platform/model/team_stats.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type TeamStats struct { - TeamId string `json:"team_id"` - TotalMemberCount int64 `json:"total_member_count"` - ActiveMemberCount int64 `json:"active_member_count"` -} - -func (o *TeamStats) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func TeamStatsFromJson(data io.Reader) *TeamStats { - decoder := json.NewDecoder(data) - var o TeamStats - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/token.go b/vendor/github.com/mattermost/platform/model/token.go deleted file mode 100644 index a4d10c7f..00000000 --- a/vendor/github.com/mattermost/platform/model/token.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import "net/http" - -const ( - TOKEN_SIZE = 64 - MAX_TOKEN_EXIPRY_TIME = 1000 * 60 * 60 * 24 // 24 hour - TOKEN_TYPE_OAUTH = "oauth" -) - -type Token struct { - Token string - CreateAt int64 - Type string - Extra string -} - -func NewToken(tokentype, extra string) *Token { - return &Token{ - Token: NewRandomString(TOKEN_SIZE), - CreateAt: GetMillis(), - Type: tokentype, - Extra: extra, - } -} - -func (t *Token) IsValid() *AppError { - if len(t.Token) != TOKEN_SIZE { - return NewAppError("Token.IsValid", "model.token.is_valid.size", nil, "", http.StatusInternalServerError) - } - - if t.CreateAt == 0 { - return NewAppError("Token.IsValid", "model.token.is_valid.expiry", nil, "", http.StatusInternalServerError) - } - - return nil -} diff --git a/vendor/github.com/mattermost/platform/model/user.go b/vendor/github.com/mattermost/platform/model/user.go deleted file mode 100644 index 7e767fd5..00000000 --- a/vendor/github.com/mattermost/platform/model/user.go +++ /dev/null @@ -1,653 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "regexp" - "strings" - "unicode/utf8" - - "golang.org/x/crypto/bcrypt" -) - -const ( - ME = "me" - USER_NOTIFY_ALL = "all" - USER_NOTIFY_MENTION = "mention" - USER_NOTIFY_NONE = "none" - DESKTOP_NOTIFY_PROP = "desktop" - DESKTOP_SOUND_NOTIFY_PROP = "desktop_sound" - DESKTOP_DURATION_NOTIFY_PROP = "desktop_duration" - MARK_UNREAD_NOTIFY_PROP = "mark_unread" - PUSH_NOTIFY_PROP = "push" - PUSH_STATUS_NOTIFY_PROP = "push_status" - EMAIL_NOTIFY_PROP = "email" - CHANNEL_MENTIONS_NOTIFY_PROP = "channel" - COMMENTS_NOTIFY_PROP = "comments" - MENTION_KEYS_NOTIFY_PROP = "mention_keys" - COMMENTS_NOTIFY_NEVER = "never" - COMMENTS_NOTIFY_ROOT = "root" - COMMENTS_NOTIFY_ANY = "any" - - DEFAULT_LOCALE = "en" - USER_AUTH_SERVICE_EMAIL = "email" - - USER_EMAIL_MAX_LENGTH = 128 - USER_NICKNAME_MAX_RUNES = 64 - USER_POSITION_MAX_RUNES = 64 - USER_FIRST_NAME_MAX_RUNES = 64 - USER_LAST_NAME_MAX_RUNES = 64 - USER_AUTH_DATA_MAX_LENGTH = 128 - USER_NAME_MAX_LENGTH = 64 - USER_NAME_MIN_LENGTH = 1 - USER_PASSWORD_MAX_LENGTH = 72 -) - -type User struct { - Id string `json:"id"` - CreateAt int64 `json:"create_at,omitempty"` - UpdateAt int64 `json:"update_at,omitempty"` - DeleteAt int64 `json:"delete_at"` - 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"` - Position string `json:"position"` - Roles string `json:"roles"` - AllowMarketing bool `json:"allow_marketing,omitempty"` - Props StringMap `json:"props,omitempty"` - NotifyProps StringMap `json:"notify_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"` - LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"` -} - -type UserPatch struct { - Username *string `json:"username"` - Nickname *string `json:"nickname"` - FirstName *string `json:"first_name"` - LastName *string `json:"last_name"` - Position *string `json:"position"` - Email *string `json:"email"` - Props StringMap `json:"props,omitempty"` - NotifyProps StringMap `json:"notify_props,omitempty"` - Locale *string `json:"locale"` -} - -type UserAuth struct { - Password string `json:"password,omitempty"` - AuthData *string `json:"auth_data,omitempty"` - AuthService string `json:"auth_service,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 InvalidUserError("id", "") - } - - if u.CreateAt == 0 { - return InvalidUserError("create_at", u.Id) - } - - if u.UpdateAt == 0 { - return InvalidUserError("update_at", u.Id) - } - - if !IsValidUsername(u.Username) { - return InvalidUserError("username", u.Id) - } - - if len(u.Email) > USER_EMAIL_MAX_LENGTH || len(u.Email) == 0 { - return InvalidUserError("email", u.Id) - } - - if utf8.RuneCountInString(u.Nickname) > USER_NICKNAME_MAX_RUNES { - return InvalidUserError("nickname", u.Id) - } - - if utf8.RuneCountInString(u.Position) > USER_POSITION_MAX_RUNES { - return InvalidUserError("position", u.Id) - } - - if utf8.RuneCountInString(u.FirstName) > USER_FIRST_NAME_MAX_RUNES { - return InvalidUserError("first_name", u.Id) - } - - if utf8.RuneCountInString(u.LastName) > USER_LAST_NAME_MAX_RUNES { - return InvalidUserError("last_name", u.Id) - } - - if u.AuthData != nil && len(*u.AuthData) > USER_AUTH_DATA_MAX_LENGTH { - return InvalidUserError("auth_data", u.Id) - } - - if u.AuthData != nil && len(*u.AuthData) > 0 && len(u.AuthService) == 0 { - return InvalidUserError("auth_data_type", u.Id) - } - - if len(u.Password) > 0 && u.AuthData != nil && len(*u.AuthData) > 0 { - return InvalidUserError("auth_data_pwd", u.Id) - } - - if len(u.Password) > USER_PASSWORD_MAX_LENGTH { - return InvalidUserError("password_limit", u.Id) - } - - return nil -} - -func InvalidUserError(fieldName string, userId string) *AppError { - id := fmt.Sprintf("model.user.is_valid.%s.app_error", fieldName) - details := "" - if userId != "" { - details = "user_id=" + userId - } - return NewAppError("User.IsValid", id, nil, details, http.StatusBadRequest) -} - -// 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() - } - - if u.AuthData != nil && *u.AuthData == "" { - u.AuthData = nil - } - - u.Username = strings.ToLower(u.Username) - u.Email = strings.ToLower(u.Email) - - 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.UpdateAt = GetMillis() - - if u.AuthData != nil && *u.AuthData == "" { - u.AuthData = nil - } - - 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["push"] = USER_NOTIFY_MENTION - u.NotifyProps["desktop"] = USER_NOTIFY_MENTION - u.NotifyProps["desktop_sound"] = "true" - u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username - u.NotifyProps["channel"] = "true" - u.NotifyProps["push_status"] = STATUS_AWAY - u.NotifyProps["comments"] = "never" - u.NotifyProps["first_name"] = "false" -} - -func (user *User) UpdateMentionKeysFromUsername(oldUsername string) { - nonUsernameKeys := []string{} - splitKeys := strings.Split(user.NotifyProps["mention_keys"], ",") - for _, key := range splitKeys { - if key != oldUsername && key != "@"+oldUsername { - nonUsernameKeys = append(nonUsernameKeys, key) - } - } - - user.NotifyProps["mention_keys"] = user.Username + ",@" + user.Username - if len(nonUsernameKeys) > 0 { - user.NotifyProps["mention_keys"] += "," + strings.Join(nonUsernameKeys, ",") - } -} - -func (u *User) Patch(patch *UserPatch) { - if patch.Username != nil { - u.Username = *patch.Username - } - - if patch.Nickname != nil { - u.Nickname = *patch.Nickname - } - - if patch.FirstName != nil { - u.FirstName = *patch.FirstName - } - - if patch.LastName != nil { - u.LastName = *patch.LastName - } - - if patch.Position != nil { - u.Position = *patch.Position - } - - if patch.Email != nil { - u.Email = *patch.Email - } - - if patch.Props != nil { - u.Props = patch.Props - } - - if patch.NotifyProps != nil { - u.NotifyProps = patch.NotifyProps - } - - if patch.Locale != nil { - u.Locale = *patch.Locale - } -} - -// ToJson convert a User to a json string -func (u *User) ToJson() string { - b, err := json.Marshal(u) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (u *UserPatch) ToJson() string { - b, err := json.Marshal(u) - if err != nil { - return "" - } else { - return string(b) - } -} - -func (u *UserAuth) ToJson() string { - b, err := json.Marshal(u) - if err != nil { - return "" - } else { - return string(b) - } -} - -// Generate a valid strong etag so the browser can cache the results -func (u *User) Etag(showFullName, showEmail bool) string { - return Etag(u.Id, u.UpdateAt, showFullName, showEmail) -} - -// Remove any private data from the user object -func (u *User) Sanitize(options map[string]bool) { - u.Password = "" - u.AuthData = NewString("") - u.MfaSecret = "" - - 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 - } - if len(options) != 0 && !options["authservice"] { - u.AuthService = "" - } -} - -func (u *User) ClearNonProfileFields() { - u.Password = "" - u.AuthData = NewString("") - u.MfaSecret = "" - u.EmailVerified = false - u.AllowMarketing = false - u.NotifyProps = StringMap{} - u.LastPasswordUpdate = 0 - u.FailedAttempts = 0 -} - -func (u *User) SanitizeProfile(options map[string]bool) { - u.ClearNonProfileFields() - - u.Sanitize(options) -} - -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(nameFormat string) string { - displayName := u.Username - - if nameFormat == SHOW_NICKNAME_FULLNAME { - if u.Nickname != "" { - displayName = u.Nickname - } else if fullName := u.GetFullName(); fullName != "" { - displayName = fullName - } - } else if nameFormat == SHOW_FULLNAME { - if fullName := u.GetFullName(); fullName != "" { - displayName = fullName - } - } - - return displayName -} - -func (u *User) GetRoles() []string { - return strings.Fields(u.Roles) -} - -func (u *User) GetRawRoles() string { - return u.Roles -} - -func IsValidUserRoles(userRoles string) bool { - - roles := strings.Fields(userRoles) - - for _, r := range roles { - if !isValidRole(r) { - return false - } - } - - // Exclude just the system_admin role explicitly to prevent mistakes - if len(roles) == 1 && roles[0] == "system_admin" { - return false - } - - return true -} - -func isValidRole(roleId string) bool { - _, ok := DefaultRoles[roleId] - return ok -} - -// Make sure you acually want to use this function. In context.go there are functions to check permissions -// 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 permissions -// 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 { - return u.AuthService != "" && u.AuthService != USER_AUTH_SERVICE_EMAIL -} - -func (u *User) IsOAuthUser() bool { - return u.AuthService == USER_AUTH_SERVICE_GITLAB -} - -func (u *User) IsLDAPUser() bool { - return u.AuthService == USER_AUTH_SERVICE_LDAP -} - -func (u *User) IsSAMLUser() bool { - return u.AuthService == USER_AUTH_SERVICE_SAML -} - -// 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 UserPatchFromJson(data io.Reader) *UserPatch { - decoder := json.NewDecoder(data) - var user UserPatch - err := decoder.Decode(&user) - if err == nil { - return &user - } else { - return nil - } -} - -func UserAuthFromJson(data io.Reader) *UserAuth { - decoder := json.NewDecoder(data) - var user UserAuth - 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 - } -} - -func UserListToJson(u []*User) string { - b, err := json.Marshal(u) - if err != nil { - return "" - } else { - return string(b) - } -} - -func UserListFromJson(data io.Reader) []*User { - decoder := json.NewDecoder(data) - var users []*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 || len(hash) == 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", - "matterbot", -} - -func IsValidUsername(s string) bool { - if len(s) < USER_NAME_MIN_LENGTH || len(s) > USER_NAME_MAX_LENGTH { - 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 -} - -func IsValidUserNotifyLevel(notifyLevel string) bool { - return notifyLevel == CHANNEL_NOTIFY_ALL || - notifyLevel == CHANNEL_NOTIFY_MENTION || - notifyLevel == CHANNEL_NOTIFY_NONE -} - -func IsValidPushStatusNotifyLevel(notifyLevel string) bool { - return notifyLevel == STATUS_ONLINE || - notifyLevel == STATUS_AWAY || - notifyLevel == STATUS_OFFLINE -} - -func IsValidCommentsNotifyLevel(notifyLevel string) bool { - return notifyLevel == COMMENTS_NOTIFY_ANY || - notifyLevel == COMMENTS_NOTIFY_ROOT || - notifyLevel == COMMENTS_NOTIFY_NEVER -} diff --git a/vendor/github.com/mattermost/platform/model/user_access_token.go b/vendor/github.com/mattermost/platform/model/user_access_token.go deleted file mode 100644 index e189ec23..00000000 --- a/vendor/github.com/mattermost/platform/model/user_access_token.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "net/http" -) - -type UserAccessToken struct { - Id string `json:"id"` - Token string `json:"token,omitempty"` - UserId string `json:"user_id"` - Description string `json:"description"` - IsActive bool `json:"is_active"` -} - -func (t *UserAccessToken) IsValid() *AppError { - if len(t.Id) != 26 { - return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.id.app_error", nil, "", http.StatusBadRequest) - } - - if len(t.Token) != 26 { - return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.token.app_error", nil, "", http.StatusBadRequest) - } - - if len(t.UserId) != 26 { - return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) - } - - if len(t.Description) > 255 { - return NewAppError("UserAccessToken.IsValid", "model.user_access_token.is_valid.description.app_error", nil, "", http.StatusBadRequest) - } - - return nil -} - -func (t *UserAccessToken) PreSave() { - t.Id = NewId() - t.IsActive = true -} - -func (t *UserAccessToken) ToJson() string { - b, err := json.Marshal(t) - if err != nil { - return "" - } else { - return string(b) - } -} - -func UserAccessTokenFromJson(data io.Reader) *UserAccessToken { - decoder := json.NewDecoder(data) - var t UserAccessToken - err := decoder.Decode(&t) - if err == nil { - return &t - } else { - return nil - } -} - -func UserAccessTokenListToJson(t []*UserAccessToken) string { - b, err := json.Marshal(t) - if err != nil { - return "" - } else { - return string(b) - } -} - -func UserAccessTokenListFromJson(data io.Reader) []*UserAccessToken { - decoder := json.NewDecoder(data) - var t []*UserAccessToken - err := decoder.Decode(&t) - if err == nil { - return t - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/user_autocomplete.go b/vendor/github.com/mattermost/platform/model/user_autocomplete.go deleted file mode 100644 index 43c030b8..00000000 --- a/vendor/github.com/mattermost/platform/model/user_autocomplete.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type UserAutocompleteInChannel struct { - InChannel []*User `json:"in_channel"` - OutOfChannel []*User `json:"out_of_channel"` -} - -type UserAutocompleteInTeam struct { - InTeam []*User `json:"in_team"` -} - -type UserAutocomplete struct { - Users []*User `json:"users"` - OutOfChannel []*User `json:"out_of_channel,omitempty"` -} - -func (o *UserAutocomplete) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func UserAutocompleteFromJson(data io.Reader) *UserAutocomplete { - decoder := json.NewDecoder(data) - autocomplete := new(UserAutocomplete) - err := decoder.Decode(&autocomplete) - if err == nil { - return autocomplete - } else { - return nil - } -} - -func (o *UserAutocompleteInChannel) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel { - decoder := json.NewDecoder(data) - var o UserAutocompleteInChannel - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func (o *UserAutocompleteInTeam) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam { - decoder := json.NewDecoder(data) - var o UserAutocompleteInTeam - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/user_search.go b/vendor/github.com/mattermost/platform/model/user_search.go deleted file mode 100644 index 6213b16e..00000000 --- a/vendor/github.com/mattermost/platform/model/user_search.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type UserSearch struct { - Term string `json:"term"` - TeamId string `json:"team_id"` - NotInTeamId string `json:"not_in_team_id"` - InChannelId string `json:"in_channel_id"` - NotInChannelId string `json:"not_in_channel_id"` - AllowInactive bool `json:"allow_inactive"` - WithoutTeam bool `json:"without_team"` -} - -// ToJson convert a User to a json string -func (u *UserSearch) ToJson() string { - b, err := json.Marshal(u) - if err != nil { - return "" - } else { - return string(b) - } -} - -// UserSearchFromJson will decode the input and return a User -func UserSearchFromJson(data io.Reader) *UserSearch { - decoder := json.NewDecoder(data) - var us UserSearch - err := decoder.Decode(&us) - if err == nil { - return &us - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/utils.go b/vendor/github.com/mattermost/platform/model/utils.go deleted file mode 100644 index e84d44f7..00000000 --- a/vendor/github.com/mattermost/platform/model/utils.go +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "bytes" - "crypto/rand" - "encoding/base32" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/mail" - "net/url" - "regexp" - "strconv" - "strings" - "time" - "unicode" - - goi18n "github.com/nicksnyder/go-i18n/i18n" - "github.com/pborman/uuid" -) - -const ( - LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz" - UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - NUMBERS = "0123456789" - SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~" -) - -type StringInterface map[string]interface{} -type StringMap map[string]string -type StringArray []string - -type AppError struct { - Id string `json:"id"` - Message string `json:"message"` // Message to be display to the end user without debugging information - DetailedError string `json:"detailed_error"` // Internal error string to help the developer - RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header - StatusCode int `json:"status_code,omitempty"` // The http status code - Where string `json:"-"` // The function where it happened in the form of Struct.Func - IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific - params map[string]interface{} -} - -func (er *AppError) Error() string { - return er.Where + ": " + er.Message + ", " + er.DetailedError -} - -func (er *AppError) Translate(T goi18n.TranslateFunc) { - if er.params == nil { - er.Message = T(er.Id) - } else { - er.Message = T(er.Id, er.params) - } -} - -func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string { - if er.params == nil { - return T(er.Id) - } else { - return 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 { - str := "" - bytes, rerr := ioutil.ReadAll(data) - if rerr != nil { - str = rerr.Error() - } else { - str = string(bytes) - } - - decoder := json.NewDecoder(strings.NewReader(str)) - var er AppError - err := decoder.Decode(&er) - if err == nil { - return &er - } else { - return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError) - } -} - -func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError { - ap := &AppError{} - ap.Id = id - ap.params = params - ap.Message = id - ap.Where = where - ap.DetailedError = details - ap.StatusCode = status - ap.IsOAuth = false - return ap -} - -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) - } -} - -// MapToJson converts a map to a json string -func MapBoolToJson(objmap map[string]bool) 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 - } -} - -// MapFromJson will decode the key/value pair map -func MapBoolFromJson(data io.Reader) map[string]bool { - decoder := json.NewDecoder(data) - - var objmap map[string]bool - if err := decoder.Decode(&objmap); err != nil { - return make(map[string]bool) - } 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 ArrayFromInterface(data interface{}) []string { - stringArray := []string{} - - dataArray, ok := data.([]interface{}) - if !ok { - return stringArray - } - - for _, v := range dataArray { - if str, ok := v.(string); ok { - stringArray = append(stringArray, str) - } - } - - return stringArray -} - -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 StringToJson(s string) string { - b, err := json.Marshal(s) - if err != nil { - return "" - } else { - return string(b) - } -} - -func StringFromJson(data io.Reader) string { - decoder := json.NewDecoder(data) - - var s string - if err := decoder.Decode(&s); err != nil { - return "" - } else { - return s - } -} - -func GetServerIpAddress() string { - if addrs, err := net.InterfaceAddrs(); err != nil { - return "" - } else { - for _, addr := range addrs { - - if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() { - if ip.IP.To4() != nil { - return ip.IP.String() - } - } - } - } - - return "" -} - -func IsLower(s string) bool { - return strings.ToLower(s) == s -} - -func IsValidEmail(email string) bool { - - if !IsLower(email) { - return false - } - - if _, err := mail.ParseAddress(email); err == nil { - return true - } - - return false -} - -var reservedName = []string{ - "signup", - "login", - "admin", - "channel", - "post", - "api", - "oauth", -} - -func IsValidChannelIdentifier(s string) bool { - - if !IsValidAlphaNumHyphenUnderscore(s, true) { - return false - } - - if len(s) < CHANNEL_NAME_MIN_LENGTH { - return false - } - - return true -} - -func IsValidAlphaNum(s string) bool { - validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) - - return validAlphaNum.MatchString(s) -} - -func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { - if withFormat { - validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) - return validAlphaNumHyphenUnderscore.MatchString(s) - } - - validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) - return validSimpleAlphaNumHyphenUnderscore.MatchString(s) -} - -func Etag(parts ...interface{}) string { - - etag := CurrentVersion - - for _, part := range parts { - etag += fmt.Sprintf(".%v", part) - } - - return etag -} - -var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) -var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) -var hashtagStart = regexp.MustCompile(`^#{2,}`) -var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) - -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, "", "", -1) - post = strings.Replace(post, "", "", -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+_~\-\.%=&]*)?)?(?:#[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,})?)`) - -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 -} - -func IsValidTurnOrStunServer(rawUri string) bool { - if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { - return false - } - - if _, err := url.ParseRequestURI(rawUri); err != nil { - return false - } - - return true -} - -func IsSafeLink(link *string) bool { - if link != nil { - if IsValidHttpUrl(*link) { - return true - } else if strings.HasPrefix(*link, "/") { - return true - } else { - return false - } - } - - return true -} - -func IsValidWebsocketUrl(rawUrl string) bool { - if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { - return false - } - - if _, err := url.ParseRequestURI(rawUrl); err != nil { - return false - } - - return true -} - -func IsValidTrueOrFalseString(value string) bool { - return value == "true" || value == "false" -} - -func IsValidNumberString(value string) bool { - if _, err := strconv.Atoi(value); err != nil { - return false - } - - return true -} - -func IsValidId(value string) bool { - if len(value) != 26 { - return false - } - - for _, r := range value { - if !unicode.IsLetter(r) && !unicode.IsNumber(r) { - return false - } - } - - return true -} diff --git a/vendor/github.com/mattermost/platform/model/version.go b/vendor/github.com/mattermost/platform/model/version.go deleted file mode 100644 index 430924ee..00000000 --- a/vendor/github.com/mattermost/platform/model/version.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2015-present 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{ - "4.6.0", - "4.5.0", - "4.4.0", - "4.3.0", - "4.2.0", - "4.1.0", - "4.0.0", - "3.10.0", - "3.9.0", - "3.8.0", - "3.7.0", - "3.6.0", - "3.5.0", - "3.4.0", - "3.3.0", - "3.2.0", - "3.1.0", - "3.0.0", - "2.2.0", - "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 BuildHashEnterprise 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/mattermost/platform/model/webrtc.go b/vendor/github.com/mattermost/platform/model/webrtc.go deleted file mode 100644 index fa15a4b7..00000000 --- a/vendor/github.com/mattermost/platform/model/webrtc.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -type WebrtcInfoResponse struct { - Token string `json:"token"` - GatewayUrl string `json:"gateway_url"` - StunUri string `json:"stun_uri,omitempty"` - TurnUri string `json:"turn_uri,omitempty"` - TurnPassword string `json:"turn_password,omitempty"` - TurnUsername string `json:"turn_username,omitempty"` -} - -type GatewayResponse struct { - Status string `json:"janus"` -} - -func GatewayResponseFromJson(data io.Reader) *GatewayResponse { - decoder := json.NewDecoder(data) - var o GatewayResponse - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -func (o *WebrtcInfoResponse) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func WebrtcInfoResponseFromJson(data io.Reader) *WebrtcInfoResponse { - decoder := json.NewDecoder(data) - var o WebrtcInfoResponse - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/websocket_client.go b/vendor/github.com/mattermost/platform/model/websocket_client.go deleted file mode 100644 index e5c44dde..00000000 --- a/vendor/github.com/mattermost/platform/model/websocket_client.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "net/http" - - "github.com/gorilla/websocket" -) - -const ( - SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB -) - -type WebSocketClient struct { - Url string // The location of the server like "ws://localhost:8065" - ApiUrl string // The api location of the server like "ws://localhost:8065/api/v3" - ConnectUrl string // The websocket URL to connect to like "ws://localhost:8065/api/v3/path/to/websocket" - Conn *websocket.Conn // The WebSocket connection - AuthToken string // The token used to open the WebSocket - Sequence int64 // The ever-incrementing sequence attached to each WebSocket action - EventChannel chan *WebSocketEvent - ResponseChannel chan *WebSocketResponse - ListenError *AppError -} - -// NewWebSocketClient constructs a new WebSocket client with convienence -// methods for talking to the server. -func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { - conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX_V3+"/users/websocket", nil) - if err != nil { - return nil, NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - client := &WebSocketClient{ - url, - url + API_URL_SUFFIX_V3, - url + API_URL_SUFFIX_V3 + "/users/websocket", - conn, - authToken, - 1, - make(chan *WebSocketEvent, 100), - make(chan *WebSocketResponse, 100), - nil, - } - - client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) - - return client, nil -} - -// NewWebSocketClient4 constructs a new WebSocket client with convienence -// methods for talking to the server. Uses the v4 endpoint. -func NewWebSocketClient4(url, authToken string) (*WebSocketClient, *AppError) { - conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/websocket", nil) - if err != nil { - return nil, NewAppError("NewWebSocketClient4", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - client := &WebSocketClient{ - url, - url + API_URL_SUFFIX, - url + API_URL_SUFFIX + "/websocket", - conn, - authToken, - 1, - make(chan *WebSocketEvent, 100), - make(chan *WebSocketResponse, 100), - nil, - } - - client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) - - return client, nil -} - -func (wsc *WebSocketClient) Connect() *AppError { - var err error - wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ConnectUrl, nil) - if err != nil { - return NewAppError("Connect", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - wsc.EventChannel = make(chan *WebSocketEvent, 100) - wsc.ResponseChannel = make(chan *WebSocketResponse, 100) - - wsc.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": wsc.AuthToken}) - - return nil -} - -func (wsc *WebSocketClient) Close() { - wsc.Conn.Close() -} - -func (wsc *WebSocketClient) Listen() { - go func() { - defer func() { - wsc.Conn.Close() - close(wsc.EventChannel) - close(wsc.ResponseChannel) - }() - - for { - var rawMsg json.RawMessage - var err error - if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { - if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { - wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - return - } - - var event WebSocketEvent - if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { - wsc.EventChannel <- &event - continue - } - - var response WebSocketResponse - if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { - wsc.ResponseChannel <- &response - continue - } - - } - }() -} - -func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { - req := &WebSocketRequest{} - req.Seq = wsc.Sequence - req.Action = action - req.Data = data - - wsc.Sequence++ - - wsc.Conn.WriteJSON(req) -} - -// UserTyping will push a user_typing event out to all connected users -// who are in the specified channel -func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { - data := map[string]interface{}{ - "channel_id": channelId, - "parent_id": parentId, - } - - wsc.SendMessage("user_typing", data) -} - -// GetStatuses will return a map of string statuses using user id as the key -func (wsc *WebSocketClient) GetStatuses() { - wsc.SendMessage("get_statuses", nil) -} - -// GetStatusesByIds will fetch certain user statuses based on ids and return -// a map of string statuses using user id as the key -func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) { - data := map[string]interface{}{ - "user_ids": userIds, - } - wsc.SendMessage("get_statuses_by_ids", data) -} diff --git a/vendor/github.com/mattermost/platform/model/websocket_message.go b/vendor/github.com/mattermost/platform/model/websocket_message.go deleted file mode 100644 index bf2535dc..00000000 --- a/vendor/github.com/mattermost/platform/model/websocket_message.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" -) - -const ( - WEBSOCKET_EVENT_TYPING = "typing" - WEBSOCKET_EVENT_POSTED = "posted" - WEBSOCKET_EVENT_POST_EDITED = "post_edited" - WEBSOCKET_EVENT_POST_DELETED = "post_deleted" - WEBSOCKET_EVENT_CHANNEL_DELETED = "channel_deleted" - WEBSOCKET_EVENT_CHANNEL_CREATED = "channel_created" - WEBSOCKET_EVENT_CHANNEL_UPDATED = "channel_updated" - WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added" - WEBSOCKET_EVENT_GROUP_ADDED = "group_added" - WEBSOCKET_EVENT_NEW_USER = "new_user" - WEBSOCKET_EVENT_ADDED_TO_TEAM = "added_to_team" - WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team" - WEBSOCKET_EVENT_UPDATE_TEAM = "update_team" - WEBSOCKET_EVENT_USER_ADDED = "user_added" - WEBSOCKET_EVENT_USER_UPDATED = "user_updated" - WEBSOCKET_EVENT_USER_ROLE_UPDATED = "user_role_updated" - WEBSOCKET_EVENT_MEMBERROLE_UPDATED = "memberrole_updated" - WEBSOCKET_EVENT_USER_REMOVED = "user_removed" - WEBSOCKET_EVENT_PREFERENCE_CHANGED = "preference_changed" - WEBSOCKET_EVENT_PREFERENCES_CHANGED = "preferences_changed" - WEBSOCKET_EVENT_PREFERENCES_DELETED = "preferences_deleted" - WEBSOCKET_EVENT_EPHEMERAL_MESSAGE = "ephemeral_message" - WEBSOCKET_EVENT_STATUS_CHANGE = "status_change" - WEBSOCKET_EVENT_HELLO = "hello" - WEBSOCKET_EVENT_WEBRTC = "webrtc" - WEBSOCKET_AUTHENTICATION_CHALLENGE = "authentication_challenge" - WEBSOCKET_EVENT_REACTION_ADDED = "reaction_added" - WEBSOCKET_EVENT_REACTION_REMOVED = "reaction_removed" - WEBSOCKET_EVENT_RESPONSE = "response" - WEBSOCKET_EVENT_EMOJI_ADDED = "emoji_added" - WEBSOCKET_EVENT_CHANNEL_VIEWED = "channel_viewed" - WEBSOCKET_EVENT_PLUGIN_ACTIVATED = "plugin_activated" // EXPERIMENTAL - SUBJECT TO CHANGE - WEBSOCKET_EVENT_PLUGIN_DEACTIVATED = "plugin_deactivated" // EXPERIMENTAL - SUBJECT TO CHANGE -) - -type WebSocketMessage interface { - ToJson() string - IsValid() bool - EventType() string -} - -type WebsocketBroadcast struct { - OmitUsers map[string]bool `json:"omit_users"` // broadcast is omitted for users listed here - UserId string `json:"user_id"` // broadcast only occurs for this user - ChannelId string `json:"channel_id"` // broadcast only occurs for users in this channel - TeamId string `json:"team_id"` // broadcast only occurs for users in this team -} - -type WebSocketEvent struct { - Event string `json:"event"` - Data map[string]interface{} `json:"data"` - Broadcast *WebsocketBroadcast `json:"broadcast"` - Sequence int64 `json:"seq"` -} - -func (m *WebSocketEvent) Add(key string, value interface{}) { - m.Data[key] = value -} - -func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { - return &WebSocketEvent{Event: event, Data: make(map[string]interface{}), - Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}} -} - -func (o *WebSocketEvent) IsValid() bool { - return o.Event != "" -} - -func (o *WebSocketEvent) EventType() string { - return o.Event -} - -func (o *WebSocketEvent) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { - decoder := json.NewDecoder(data) - var o WebSocketEvent - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} - -type WebSocketResponse struct { - Status string `json:"status"` - SeqReply int64 `json:"seq_reply,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` - Error *AppError `json:"error,omitempty"` -} - -func (m *WebSocketResponse) Add(key string, value interface{}) { - m.Data[key] = value -} - -func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse { - return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data} -} - -func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse { - return &WebSocketResponse{Status: STATUS_FAIL, SeqReply: seqReply, Error: err} -} - -func (o *WebSocketResponse) IsValid() bool { - return o.Status != "" -} - -func (o *WebSocketResponse) EventType() string { - return WEBSOCKET_EVENT_RESPONSE -} - -func (o *WebSocketResponse) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { - decoder := json.NewDecoder(data) - var o WebSocketResponse - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mattermost/platform/model/websocket_request.go b/vendor/github.com/mattermost/platform/model/websocket_request.go deleted file mode 100644 index 7dc0b433..00000000 --- a/vendor/github.com/mattermost/platform/model/websocket_request.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - - goi18n "github.com/nicksnyder/go-i18n/i18n" -) - -type WebSocketRequest struct { - // Client-provided fields - Seq int64 `json:"seq"` - Action string `json:"action"` - Data map[string]interface{} `json:"data"` - - // Server-provided fields - Session Session `json:"-"` - T goi18n.TranslateFunc `json:"-"` - Locale string `json:"-"` -} - -func (o *WebSocketRequest) ToJson() string { - b, err := json.Marshal(o) - if err != nil { - return "" - } else { - return string(b) - } -} - -func WebSocketRequestFromJson(data io.Reader) *WebSocketRequest { - decoder := json.NewDecoder(data) - var o WebSocketRequest - err := decoder.Decode(&o) - if err == nil { - return &o - } else { - return nil - } -} diff --git a/vendor/github.com/mitchellh/mapstructure/.travis.yml b/vendor/github.com/mitchellh/mapstructure/.travis.yml index d9deadb8..1689c7d7 100644 --- a/vendor/github.com/mitchellh/mapstructure/.travis.yml +++ b/vendor/github.com/mitchellh/mapstructure/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.9.x + - "1.11.x" - tip script: diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md new file mode 100644 index 00000000..3b3cb723 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md @@ -0,0 +1,21 @@ +## 1.1.2 + +* Fix error when decode hook decodes interface implementation into interface + type. [GH-140] + +## 1.1.1 + +* Fix panic that can happen in `decodePtr` + +## 1.1.0 + +* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133] +* Support struct to struct decoding [GH-137] +* If source map value is nil, then destination map value is nil (instead of empty) +* If source slice value is nil, then destination slice value is nil (instead of empty) +* If source pointer is nil, then destination pointer is set to nil (instead of + allocated zero value of type) + +## 1.0.0 + +* Initial tagged stable release. diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go index 2a727575..1f0abc65 100644 --- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go +++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go @@ -2,6 +2,8 @@ package mapstructure import ( "errors" + "fmt" + "net" "reflect" "strconv" "strings" @@ -115,6 +117,50 @@ func StringToTimeDurationHookFunc() DecodeHookFunc { } } +// StringToIPHookFunc returns a DecodeHookFunc that converts +// strings to net.IP +func StringToIPHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IP{}) { + return data, nil + } + + // Convert it by parsing + ip := net.ParseIP(data.(string)) + if ip == nil { + return net.IP{}, fmt.Errorf("failed parsing ip %v", data) + } + + return ip, nil + } +} + +// StringToIPNetHookFunc returns a DecodeHookFunc that converts +// strings to net.IPNet +func StringToIPNetHookFunc() DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if f.Kind() != reflect.String { + return data, nil + } + if t != reflect.TypeOf(net.IPNet{}) { + return data, nil + } + + // Convert it by parsing + _, net, err := net.ParseCIDR(data.(string)) + return net, err + } +} + // StringToTimeHookFunc returns a DecodeHookFunc that converts // strings to time.Time. func StringToTimeHookFunc(layout string) DecodeHookFunc { diff --git a/vendor/github.com/mitchellh/mapstructure/go.mod b/vendor/github.com/mitchellh/mapstructure/go.mod new file mode 100644 index 00000000..d2a71256 --- /dev/null +++ b/vendor/github.com/mitchellh/mapstructure/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/mapstructure diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index aaf12a29..256ee63f 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -224,6 +224,17 @@ func (d *Decoder) Decode(input interface{}) error { // Decodes an unknown data type into a specific reflection value. func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + if input == nil { // If the data is nil, then we don't set anything, unless ZeroFields is set // to true. @@ -237,7 +248,6 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e return nil } - inputVal := reflect.ValueOf(input) if !inputVal.IsValid() { // If the input value is invalid, then we just set the value // to be the zero value. @@ -260,8 +270,8 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e } var err error - inputKind := getKind(outVal) - switch inputKind { + outputKind := getKind(outVal) + switch outputKind { case reflect.Bool: err = d.decodeBool(name, input, outVal) case reflect.Interface: @@ -288,7 +298,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e err = d.decodeFunc(name, input, outVal) default: // If we reached this point then we weren't able to decode it - return fmt.Errorf("%s: unsupported type: %s", name, inputKind) + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) } // If we reached here, then we successfully decoded SOMETHING, so @@ -306,7 +316,16 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) if val.IsValid() && val.Elem().IsValid() { return d.decode(name, data, val.Elem()) } + dataVal := reflect.ValueOf(data) + + // If the input data is a pointer, and the assigned type is the dereference + // of that exact pointer, then indirect it so that we can assign it. + // Example: *string to string + if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() { + dataVal = reflect.Indirect(dataVal) + } + if !dataVal.IsValid() { dataVal = reflect.Zero(val.Type()) } @@ -323,7 +342,7 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) } func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) converted := true @@ -375,7 +394,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) } func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -417,7 +436,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er } func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { @@ -460,7 +479,7 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e } func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) switch { @@ -491,7 +510,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e } func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { - dataVal := reflect.ValueOf(data) + dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) dataType := dataVal.Type() @@ -595,6 +614,20 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle // Accumulate errors errors := make([]string, 0) + // If the input data is empty, then we just match what the input data is. + if dataVal.Len() == 0 { + if dataVal.IsNil() { + if !val.IsNil() { + val.Set(dataVal) + } + } else { + // Set to empty allocated value + val.Set(valMap) + } + + return nil + } + for _, k := range dataVal.MapKeys() { fieldName := fmt.Sprintf("%s[%s]", name, k) @@ -644,16 +677,28 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) } + tagValue := f.Tag.Get(d.config.TagName) + tagParts := strings.Split(tagValue, ",") + // Determine the name of the key in the map keyName := f.Name - tagValue := f.Tag.Get(d.config.TagName) - tagValue = strings.SplitN(tagValue, ",", 2)[0] - if tagValue != "" { - if tagValue == "-" { + if tagParts[0] != "" { + if tagParts[0] == "-" { continue } + keyName = tagParts[0] + } - keyName = tagValue + // If "squash" is specified in the tag, we squash the field down. + squash := false + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + if squash && v.Kind() != reflect.Struct { + return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } switch v.Kind() { @@ -673,7 +718,13 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re return err } - valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) + if squash { + for _, k := range vMap.MapKeys() { + valMap.SetMapIndex(k, vMap.MapIndex(k)) + } + } else { + valMap.SetMapIndex(reflect.ValueOf(keyName), vMap) + } default: valMap.SetMapIndex(reflect.ValueOf(keyName), v) @@ -688,11 +739,33 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re } func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return nil + } + // Create an element of the concrete (non pointer) type and decode // into that. Then set the value of the pointer to this type. valType := val.Type() valElemType := valType.Elem() - if val.CanSet() { realVal := val if realVal.IsNil() || d.config.ZeroFields { @@ -734,30 +807,44 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) valSlice := val if valSlice.IsNil() || d.config.ZeroFields { - // Check input type - if dataValKind != reflect.Array && dataValKind != reflect.Slice { - if d.config.WeaklyTypedInput { - switch { - // Empty maps turn into empty slices - case dataValKind == reflect.Map: - if dataVal.Len() == 0 { - val.Set(reflect.MakeSlice(sliceType, 0, 0)) - return nil - } - case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: - return d.decodeSlice(name, []byte(dataVal.String()), val) - // All other types we try to convert to the slice type - // and "lift" it into it. i.e. a string becomes a string slice. - default: - // Just re-try this function with data as a slice. - return d.decodeSlice(name, []interface{}{data}, val) + if d.config.WeaklyTypedInput { + switch { + // Slice and array we use the normal logic + case dataValKind == reflect.Slice, dataValKind == reflect.Array: + break + + // Empty maps turn into empty slices + case dataValKind == reflect.Map: + if dataVal.Len() == 0 { + val.Set(reflect.MakeSlice(sliceType, 0, 0)) + return nil } + // Create slice of maps of other sizes + return d.decodeSlice(name, []interface{}{data}, val) + + case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8: + return d.decodeSlice(name, []byte(dataVal.String()), val) + + // All other types we try to convert to the slice type + // and "lift" it into it. i.e. a string becomes a string slice. + default: + // Just re-try this function with data as a slice. + return d.decodeSlice(name, []interface{}{data}, val) } + } + + // Check input type + if dataValKind != reflect.Array && dataValKind != reflect.Slice { return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) } + // If the input value is empty, then don't allocate since non-nil != nil + if dataVal.Len() == 0 { + return nil + } + // Make a new slice to hold our result, same size as the original data. valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) } @@ -867,10 +954,29 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) } dataValKind := dataVal.Kind() - if dataValKind != reflect.Map { - return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind) + switch dataValKind { + case reflect.Map: + return d.decodeStructFromMap(name, dataVal, val) + + case reflect.Struct: + // Not the most efficient way to do this but we can optimize later if + // we want to. To convert from struct to struct we go to map first + // as an intermediary. + m := make(map[string]interface{}) + mval := reflect.Indirect(reflect.ValueOf(&m)) + if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil { + return err + } + + result := d.decodeStructFromMap(name, mval, val) + return result + + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) } +} +func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { return fmt.Errorf( diff --git a/vendor/github.com/pelletier/go-toml/.travis.yml b/vendor/github.com/pelletier/go-toml/.travis.yml index ab2775d7..c9fbf304 100644 --- a/vendor/github.com/pelletier/go-toml/.travis.yml +++ b/vendor/github.com/pelletier/go-toml/.travis.yml @@ -1,8 +1,9 @@ sudo: false language: go go: - - 1.8.5 - - 1.9.2 + - 1.8.x + - 1.9.x + - 1.10.x - tip matrix: allow_failures: diff --git a/vendor/github.com/pelletier/go-toml/marshal.go b/vendor/github.com/pelletier/go-toml/marshal.go index f584ba4e..671da556 100644 --- a/vendor/github.com/pelletier/go-toml/marshal.go +++ b/vendor/github.com/pelletier/go-toml/marshal.go @@ -11,10 +11,13 @@ import ( "time" ) +const tagKeyMultiline = "multiline" + type tomlOpts struct { name string comment string commented bool + multiline bool include bool omitempty bool } @@ -230,7 +233,12 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er if err != nil { return nil, err } - tval.SetWithComment(opts.name, opts.comment, opts.commented, val) + + tval.SetWithOptions(opts.name, SetOptions{ + Comment: opts.comment, + Commented: opts.commented, + Multiline: opts.multiline, + }, val) } } case reflect.Map: @@ -559,7 +567,8 @@ func tomlOptions(vf reflect.StructField) tomlOpts { comment = c } commented, _ := strconv.ParseBool(vf.Tag.Get("commented")) - result := tomlOpts{name: vf.Name, comment: comment, commented: commented, include: true, omitempty: false} + multiline, _ := strconv.ParseBool(vf.Tag.Get(tagKeyMultiline)) + result := tomlOpts{name: vf.Name, comment: comment, commented: commented, multiline: multiline, include: true, omitempty: false} if parse[0] != "" { if parse[0] == "-" && len(parse) == 1 { result.include = false diff --git a/vendor/github.com/pelletier/go-toml/test.sh b/vendor/github.com/pelletier/go-toml/test.sh index a70a8b02..ba6adf3f 100644 --- a/vendor/github.com/pelletier/go-toml/test.sh +++ b/vendor/github.com/pelletier/go-toml/test.sh @@ -23,9 +23,6 @@ function git_clone() { # Remove potential previous runs rm -rf src test_program_bin toml-test -# Run go vet -go vet ./... - go get github.com/pelletier/go-buffruneio go get github.com/davecgh/go-spew/spew go get gopkg.in/yaml.v2 diff --git a/vendor/github.com/pelletier/go-toml/toml.go b/vendor/github.com/pelletier/go-toml/toml.go index 05493a44..98c185ad 100644 --- a/vendor/github.com/pelletier/go-toml/toml.go +++ b/vendor/github.com/pelletier/go-toml/toml.go @@ -14,6 +14,7 @@ type tomlValue struct { value interface{} // string, int64, uint64, float64, bool, time.Time, [] of any of this list comment string commented bool + multiline bool position Position } @@ -175,6 +176,63 @@ func (t *Tree) GetDefault(key string, def interface{}) interface{} { return val } +// SetOptions arguments are supplied to the SetWithOptions and SetPathWithOptions functions to modify marshalling behaviour. +// The default values within the struct are valid default options. +type SetOptions struct { + Comment string + Commented bool + Multiline bool +} + +// SetWithOptions is the same as Set, but allows you to provide formatting +// instructions to the key, that will be used by Marshal(). +func (t *Tree) SetWithOptions(key string, opts SetOptions, value interface{}) { + t.SetPathWithOptions(strings.Split(key, "."), opts, value) +} + +// SetPathWithOptions is the same as SetPath, but allows you to provide +// formatting instructions to the key, that will be reused by Marshal(). +func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interface{}) { + subtree := t + for _, intermediateKey := range keys[:len(keys)-1] { + nextTree, exists := subtree.values[intermediateKey] + if !exists { + nextTree = newTree() + subtree.values[intermediateKey] = nextTree // add new element here + } + switch node := nextTree.(type) { + case *Tree: + subtree = node + case []*Tree: + // go to most recent element + if len(node) == 0 { + // create element if it does not exist + subtree.values[intermediateKey] = append(node, newTree()) + } + subtree = node[len(node)-1] + } + } + + var toInsert interface{} + + switch value.(type) { + case *Tree: + tt := value.(*Tree) + tt.comment = opts.Comment + toInsert = value + case []*Tree: + toInsert = value + case *tomlValue: + tt := value.(*tomlValue) + tt.comment = opts.Comment + toInsert = tt + default: + toInsert = &tomlValue{value: value, comment: opts.Comment, commented: opts.Commented, multiline: opts.Multiline} + } + + subtree.values[keys[len(keys)-1]] = toInsert +} + // Set an element in the tree. // Key is a dot-separated path (e.g. a.b.c). // Creates all necessary intermediate trees, if needed. diff --git a/vendor/github.com/pelletier/go-toml/tomltree_write.go b/vendor/github.com/pelletier/go-toml/tomltree_write.go index b5600a58..e4049e29 100644 --- a/vendor/github.com/pelletier/go-toml/tomltree_write.go +++ b/vendor/github.com/pelletier/go-toml/tomltree_write.go @@ -12,7 +12,41 @@ import ( "time" ) -// encodes a string to a TOML-compliant string value +// Encodes a string to a TOML-compliant multi-line string value +// This function is a clone of the existing encodeTomlString function, except that whitespace characters +// are preserved. Quotation marks and backslashes are also not escaped. +func encodeMultilineTomlString(value string) string { + var b bytes.Buffer + + for _, rr := range value { + switch rr { + case '\b': + b.WriteString(`\b`) + case '\t': + b.WriteString("\t") + case '\n': + b.WriteString("\n") + case '\f': + b.WriteString(`\f`) + case '\r': + b.WriteString("\r") + case '"': + b.WriteString(`"`) + case '\\': + b.WriteString(`\`) + default: + intRr := uint16(rr) + if intRr < 0x001F { + b.WriteString(fmt.Sprintf("\\u%0.4X", intRr)) + } else { + b.WriteRune(rr) + } + } + } + return b.String() +} + +// Encodes a string to a TOML-compliant string value func encodeTomlString(value string) string { var b bytes.Buffer @@ -45,6 +79,15 @@ func encodeTomlString(value string) string { } func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElementPerLine bool) (string, error) { + // this interface check is added to dereference the change made in the writeTo function. + // That change was made to allow this function to see formatting options. + tv, ok := v.(*tomlValue) + if ok { + v = tv.value + } else { + tv = &tomlValue{} + } + switch value := v.(type) { case uint64: return strconv.FormatUint(value, 10), nil @@ -58,6 +101,9 @@ func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElemen } return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil case string: + if tv.multiline { + return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil + } return "\"" + encodeTomlString(value) + "\"", nil case []byte: b, _ := v.([]byte) @@ -130,7 +176,7 @@ func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, a return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) } - repr, err := tomlValueStringRepresentation(v.value, indent, arraysOneElementPerLine) + repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine) if err != nil { return bytesCount, err } diff --git a/vendor/github.com/peterhellberg/emojilib/.travis.yml b/vendor/github.com/peterhellberg/emojilib/.travis.yml index 15cfa8b6..99c59c46 100644 --- a/vendor/github.com/peterhellberg/emojilib/.travis.yml +++ b/vendor/github.com/peterhellberg/emojilib/.travis.yml @@ -1,8 +1,11 @@ language: go go: - - 1.8 - - 1.7.5 - - 1.6.4 + - "1.10.3" + - "1.9.7" sudo: false + +script: + - go vet ./... + - go test ./... diff --git a/vendor/github.com/peterhellberg/emojilib/LICENSE b/vendor/github.com/peterhellberg/emojilib/LICENSE index 782fe09a..e1df4843 100644 --- a/vendor/github.com/peterhellberg/emojilib/LICENSE +++ b/vendor/github.com/peterhellberg/emojilib/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2017 Peter Hellberg https://c7.se/ +Copyright (c) 2015-2018 Peter Hellberg https://c7.se/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/vendor/github.com/peterhellberg/emojilib/README.md b/vendor/github.com/peterhellberg/emojilib/README.md index 3175a43d..2a8ab690 100644 --- a/vendor/github.com/peterhellberg/emojilib/README.md +++ b/vendor/github.com/peterhellberg/emojilib/README.md @@ -39,7 +39,7 @@ _You’ll need to have the [golang.org/x/tools/imports](https://golang.org/x/too ## License (MIT) -Copyright (c) 2015-2017 [Peter Hellberg](http://c7.se/) +Copyright (c) 2015-2018 [Peter Hellberg](http://c7.se/) > Permission is hereby granted, free of charge, to any person obtaining > a copy of this software and associated documentation files (the diff --git a/vendor/github.com/peterhellberg/emojilib/generated.go b/vendor/github.com/peterhellberg/emojilib/generated.go index a5976471..9bc3ea3b 100644 --- a/vendor/github.com/peterhellberg/emojilib/generated.go +++ b/vendor/github.com/peterhellberg/emojilib/generated.go @@ -2,7 +2,7 @@ // // Update it by running: go generate // -// Generated at: 2017-06-16 16:34:02 +0000 +// Generated at: 2018-08-20 09:01:38 +0000 package emojilib @@ -74,6 +74,11 @@ var emojis = Emojis{ Char: "🉑", Category: "symbols", }, + "adult": Emoji{ + Keywords: []string{"gender-neutral", "person"}, + Char: "🧑", + Category: "people", + }, "aerial_tramway": Emoji{ Keywords: []string{"transportation", "vehicle", "ski"}, Char: "🚡", @@ -105,7 +110,7 @@ var emojis = Emojis{ Category: "flags", }, "alembic": Emoji{ - Keywords: []string{"distilling", "science", "experiment"}, + Keywords: []string{"distilling", "science", "experiment", "chemistry"}, Char: "⚗", Category: "objects", }, @@ -355,7 +360,7 @@ var emojis = Emojis{ Category: "symbols", }, "atom_symbol": Emoji{ - Keywords: []string{"science"}, + Keywords: []string{"science", "physics", "chemistry"}, Char: "⚛", Category: "symbols", }, @@ -549,6 +554,11 @@ var emojis = Emojis{ Char: "🐻", Category: "animals_and_nature", }, + "bearded_person": Emoji{ + Keywords: []string{"person", "bewhiskered"}, + Char: "🧔", + Category: "people", + }, "bed": Emoji{ Keywords: []string{"sleep", "rest"}, Char: "🛏", @@ -639,6 +649,11 @@ var emojis = Emojis{ Char: "👙", Category: "people", }, + "billed_hat": Emoji{ + Keywords: []string{"cap", "baseball"}, + Char: "🧢", + Category: "people", + }, "biohazard": Emoji{ Keywords: []string{"danger"}, Char: "☣", @@ -814,16 +829,16 @@ var emojis = Emojis{ Char: "🙇‍♀️", Category: "people", }, + "bowl_with_spoon": Emoji{ + Keywords: []string{"food", "breakfast", "cereal", "oatmeal", "porridge"}, + Char: "🥣", + Category: "food_and_drink", + }, "bowling": Emoji{ Keywords: []string{"sports", "fun", "play"}, Char: "🎳", Category: "activity", }, - "bowtie": Emoji{ - Keywords: []string{"face", "formal", "fashion", "suit", "classy", "magic", "circus"}, - Char: "", - Category: "_custom", - }, "boxing_glove": Emoji{ Keywords: []string{"sports", "fighting"}, Char: "🥊", @@ -834,6 +849,11 @@ var emojis = Emojis{ Char: "👦", Category: "people", }, + "brain": Emoji{ + Keywords: []string{"smart", "intelligent"}, + Char: "🧠", + Category: "people", + }, "brazil": Emoji{ Keywords: []string{"br", "flag", "nation", "country", "banner"}, Char: "🇧🇷", @@ -844,6 +864,11 @@ var emojis = Emojis{ Char: "🍞", Category: "food_and_drink", }, + "breastfeeding": Emoji{ + Keywords: []string{"nursing", "baby"}, + Char: "🤱", + Category: "people", + }, "bride_with_veil": Emoji{ Keywords: []string{"couple", "marriage", "wedding", "woman", "bride"}, Char: "👰", @@ -855,7 +880,7 @@ var emojis = Emojis{ Category: "travel_and_places", }, "briefcase": Emoji{ - Keywords: []string{"business", "documents", "work", "law", "legal"}, + Keywords: []string{"business", "documents", "work", "law", "legal", "job", "career"}, Char: "💼", Category: "people", }, @@ -869,6 +894,11 @@ var emojis = Emojis{ Char: "🇻🇬", Category: "flags", }, + "broccoli": Emoji{ + Keywords: []string{"fruit", "food", "vegetable"}, + Char: "🥦", + Category: "food_and_drink", + }, "broken_heart": Emoji{ Keywords: []string{"sad", "sorry", "break", "heart", "heartbreak"}, Char: "💔", @@ -1034,6 +1064,11 @@ var emojis = Emojis{ Char: "🍬", Category: "food_and_drink", }, + "canned_food": Emoji{ + Keywords: []string{"food", "soup"}, + Char: "🥫", + Category: "food_and_drink", + }, "canoe": Emoji{ Keywords: []string{"boat", "paddle", "water", "ship"}, Char: "🛶", @@ -1169,6 +1204,11 @@ var emojis = Emojis{ Char: "🐔", Category: "animals_and_nature", }, + "child": Emoji{ + Keywords: []string{"gender-neutral", "young"}, + Char: "🧒", + Category: "people", + }, "children_crossing": Emoji{ Keywords: []string{"school", "warning", "danger", "sign", "driving", "yellow-diamond"}, Char: "🚸", @@ -1189,6 +1229,11 @@ var emojis = Emojis{ Char: "🍫", Category: "food_and_drink", }, + "chopsticks": Emoji{ + Keywords: []string{"food"}, + Char: "🥢", + Category: "food_and_drink", + }, "christmas_island": Emoji{ Keywords: []string{"christmas", "island", "flag", "nation", "country", "banner"}, Char: "🇨🇽", @@ -1254,8 +1299,18 @@ var emojis = Emojis{ Char: "🏛", Category: "travel_and_places", }, + "climbing_man": Emoji{ + Keywords: []string{"sports", "hobby", "man", "male", "rock"}, + Char: "🧗‍♂️", + Category: "activity", + }, + "climbing_woman": Emoji{ + Keywords: []string{"sports", "hobby", "woman", "female", "rock"}, + Char: "🧗‍♀️", + Category: "activity", + }, "clinking_glasses": Emoji{ - Keywords: []string{"beverage", "drink", "party", "alcohol", "celebrate", "cheers"}, + Keywords: []string{"beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast"}, Char: "🥂", Category: "food_and_drink", }, @@ -1439,11 +1494,21 @@ var emojis = Emojis{ Char: "🇨🇳", Category: "flags", }, + "coat": Emoji{ + Keywords: []string{"jacket"}, + Char: "🧥", + Category: "people", + }, "cocktail": Emoji{ Keywords: []string{"drink", "drunk", "alcohol", "beverage", "booze", "mojito"}, Char: "🍸", Category: "food_and_drink", }, + "coconut": Emoji{ + Keywords: []string{"fruit", "nature", "food", "palm"}, + Char: "🥥", + Category: "food_and_drink", + }, "cocos_islands": Emoji{ Keywords: []string{"cocos", "keeling", "islands", "flag", "nation", "country", "banner"}, Char: "🇨🇨", @@ -1455,7 +1520,7 @@ var emojis = Emojis{ Category: "food_and_drink", }, "coffin": Emoji{ - Keywords: []string{"vampire", "dead", "die", "death", "rip", "graveyard", "cemetery"}, + Keywords: []string{"vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box"}, Char: "⚰", Category: "objects", }, @@ -1719,6 +1784,11 @@ var emojis = Emojis{ Char: "🥒", Category: "food_and_drink", }, + "cup_with_straw": Emoji{ + Keywords: []string{"drink", "soda"}, + Char: "🥤", + Category: "food_and_drink", + }, "cupid": Emoji{ Keywords: []string{"love", "like", "heart", "affection", "valentines"}, Char: "💘", @@ -1729,6 +1799,11 @@ var emojis = Emojis{ Char: "🇨🇼", Category: "flags", }, + "curling_stone": Emoji{ + Keywords: []string{"sports"}, + Char: "🥌", + Category: "activity", + }, "curly_loop": Emoji{ Keywords: []string{"scribble", "draw", "shape", "squiggle"}, Char: "➰", @@ -1755,7 +1830,7 @@ var emojis = Emojis{ Category: "symbols", }, "cyclone": Emoji{ - Keywords: []string{"weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin"}, + Keywords: []string{"weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon"}, Char: "🌀", Category: "symbols", }, @@ -1800,7 +1875,7 @@ var emojis = Emojis{ Category: "people", }, "dart": Emoji{ - Keywords: []string{"game", "play", "bar"}, + Keywords: []string{"game", "play", "bar", "target", "bullseye"}, Char: "🎯", Category: "activity", }, @@ -1980,7 +2055,7 @@ var emojis = Emojis{ Category: "animals_and_nature", }, "drum": Emoji{ - Keywords: []string{"music", "instrument", "drumsticks"}, + Keywords: []string{"music", "instrument", "drumsticks", "snare"}, Char: "🥁", Category: "activity", }, @@ -1989,6 +2064,11 @@ var emojis = Emojis{ Char: "🦆", Category: "animals_and_nature", }, + "dumpling": Emoji{ + Keywords: []string{"food", "empanada", "pierogi", "potsticker"}, + Char: "🥟", + Category: "food_and_drink", + }, "dvd": Emoji{ Keywords: []string{"cd", "disk", "disc"}, Char: "📀", @@ -2064,6 +2144,11 @@ var emojis = Emojis{ Char: "✳️", Category: "symbols", }, + "eject_button": Emoji{ + Keywords: []string{"blue-square"}, + Char: "⏏️", + Category: "symbols", + }, "el_salvador": Emoji{ Keywords: []string{"el", "salvador", "flag", "nation", "country", "banner"}, Char: "🇸🇻", @@ -2089,6 +2174,11 @@ var emojis = Emojis{ Char: "🔚", Category: "symbols", }, + "england": Emoji{ + Keywords: []string{"flag", "english"}, + Char: "🏴󠁧󠁢󠁥󠁮󠁧󠁿", + Category: "flags", + }, "envelope_with_arrow": Emoji{ Keywords: []string{"email", "communication"}, Char: "📩", @@ -2149,6 +2239,11 @@ var emojis = Emojis{ Char: "❗", Category: "symbols", }, + "exploding_head": Emoji{ + Keywords: []string{"face", "shocked", "mind", "blown"}, + Char: "🤯", + Category: "people", + }, "expressionless": Emoji{ Keywords: []string{"face", "indifferent", "-_-", "meh", "deadpan"}, Char: "😑", @@ -2344,11 +2439,6 @@ var emojis = Emojis{ Char: "😨", Category: "people", }, - "feelsgood": Emoji{ - Keywords: []string{"doom", "oldschool"}, - Char: "", - Category: "_custom", - }, "female_detective": Emoji{ Keywords: []string{"human", "spy", "detective", "female", "woman"}, Char: "🕵️‍♀️", @@ -2399,11 +2489,6 @@ var emojis = Emojis{ Char: "🇫🇮", Category: "flags", }, - "finnadie": Emoji{ - Keywords: []string{"doom", "oldschool"}, - Char: "", - Category: "_custom", - }, "fire": Emoji{ Keywords: []string{"hot", "cook", "flame"}, Char: "🔥", @@ -2435,7 +2520,7 @@ var emojis = Emojis{ Category: "animals_and_nature", }, "fish_cake": Emoji{ - Keywords: []string{"food", "japan", "sea", "beach"}, + Keywords: []string{"food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen"}, Char: "🍥", Category: "food_and_drink", }, @@ -2504,6 +2589,11 @@ var emojis = Emojis{ Char: "😳", Category: "people", }, + "flying_saucer": Emoji{ + Keywords: []string{"transportation", "vehicle", "ufo"}, + Char: "🛸", + Category: "travel_and_places", + }, "fog": Emoji{ Keywords: []string{"weather"}, Char: "🌫", @@ -2529,6 +2619,11 @@ var emojis = Emojis{ Char: "🍴", Category: "food_and_drink", }, + "fortune_cookie": Emoji{ + Keywords: []string{"food", "prophecy"}, + Char: "🥠", + Category: "food_and_drink", + }, "fountain": Emoji{ Keywords: []string{"photo", "summer", "water", "fresh"}, Char: "⛲", @@ -2709,6 +2804,11 @@ var emojis = Emojis{ Char: "💝", Category: "symbols", }, + "giraffe": Emoji{ + Keywords: []string{"animal", "nature", "spots", "safari"}, + Char: "🦒", + Category: "animals_and_nature", + }, "girl": Emoji{ Keywords: []string{"female", "woman", "teenager"}, Char: "👧", @@ -2719,6 +2819,11 @@ var emojis = Emojis{ Char: "🌐", Category: "symbols", }, + "gloves": Emoji{ + Keywords: []string{"hands", "winter", "clothes"}, + Char: "🧤", + Category: "people", + }, "goal_net": Emoji{ Keywords: []string{"sports"}, Char: "🥅", @@ -2729,16 +2834,6 @@ var emojis = Emojis{ Char: "🐐", Category: "animals_and_nature", }, - "goberserk": Emoji{ - Keywords: []string{"doom", "rage", "bloody", "hurt"}, - Char: "", - Category: "_custom", - }, - "godmode": Emoji{ - Keywords: []string{"doom", "oldschool"}, - Char: "", - Category: "_custom", - }, "golf": Emoji{ Keywords: []string{"sports", "business", "flag", "hole", "summer"}, Char: "⛳", @@ -2764,6 +2859,11 @@ var emojis = Emojis{ Char: "🍇", Category: "food_and_drink", }, + "grasshopper": Emoji{ + Keywords: []string{"animal", "cricket", "chirp"}, + Char: "🦗", + Category: "animals_and_nature", + }, "greece": Emoji{ Keywords: []string{"gr", "flag", "nation", "country", "banner"}, Char: "🇬🇷", @@ -2919,6 +3019,11 @@ var emojis = Emojis{ Char: "🐹", Category: "animals_and_nature", }, + "hand_over_mouth": Emoji{ + Keywords: []string{"face", "whoops", "shock", "surprise"}, + Char: "🤭", + Category: "people", + }, "handbag": Emoji{ Keywords: []string{"fashion", "accessory", "accessories", "shopping"}, Char: "👜", @@ -3024,6 +3129,11 @@ var emojis = Emojis{ Char: "➕", Category: "symbols", }, + "hedgehog": Emoji{ + Keywords: []string{"animal", "nature", "spiny"}, + Char: "🦔", + Category: "animals_and_nature", + }, "helicopter": Emoji{ Keywords: []string{"transportation", "vehicle", "fly"}, Char: "🚁", @@ -3149,11 +3259,6 @@ var emojis = Emojis{ Char: "🇭🇺", Category: "flags", }, - "hurtrealbad": Emoji{ - Keywords: []string{"mad", "injured", "doom", "oldschool", "custom_"}, - Char: "", - Category: "_custom", - }, "hushed": Emoji{ Keywords: []string{"face", "woo", "shh"}, Char: "😯", @@ -3654,6 +3759,11 @@ var emojis = Emojis{ Char: "💌", Category: "objects", }, + "love_you": Emoji{ + Keywords: []string{"hand", "fingers", "gesture"}, + Char: "🤟", + Category: "people", + }, "low_brightness": Emoji{ Keywords: []string{"sun", "afternoon", "warm", "summer"}, Char: "🔅", @@ -3784,6 +3894,11 @@ var emojis = Emojis{ Char: "🕺", Category: "people", }, + "man_elf": Emoji{ + Keywords: []string{"man", "male"}, + Char: "🧝‍♂️", + Category: "people", + }, "man_facepalming": Emoji{ Keywords: []string{"man", "male", "boy", "disbelief"}, Char: "🤦", @@ -3794,6 +3909,11 @@ var emojis = Emojis{ Char: "👨‍🏭", Category: "people", }, + "man_fairy": Emoji{ + Keywords: []string{"man", "male"}, + Char: "🧚‍♂️", + Category: "people", + }, "man_farmer": Emoji{ Keywords: []string{"rancher", "gardener", "man", "human"}, Char: "👨‍🌾", @@ -3804,11 +3924,26 @@ var emojis = Emojis{ Char: "👨‍🚒", Category: "people", }, + "man_genie": Emoji{ + Keywords: []string{"man", "male"}, + Char: "🧞‍♂️", + Category: "people", + }, "man_health_worker": Emoji{ Keywords: []string{"doctor", "nurse", "therapist", "healthcare", "man", "human"}, Char: "👨‍⚕️", Category: "people", }, + "man_in_lotus_position": Emoji{ + Keywords: []string{"man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness"}, + Char: "🧘‍♂️", + Category: "activity", + }, + "man_in_steamy_room": Emoji{ + Keywords: []string{"male", "man", "spa", "steamroom", "sauna"}, + Char: "🧖‍♂️", + Category: "people", + }, "man_in_tuxedo": Emoji{ Keywords: []string{"couple", "marriage", "wedding", "groom"}, Char: "🤵", @@ -3825,7 +3960,7 @@ var emojis = Emojis{ Category: "activity", }, "man_mechanic": Emoji{ - Keywords: []string{"plumber", "man", "human"}, + Keywords: []string{"plumber", "man", "human", "wrench"}, Char: "👨‍🔧", Category: "people", }, @@ -3875,10 +4010,15 @@ var emojis = Emojis{ Category: "people", }, "man_technologist": Emoji{ - Keywords: []string{"coder", "developer", "engineer", "programmer", "software", "man", "human"}, + Keywords: []string{"coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer"}, Char: "👨‍💻", Category: "people", }, + "man_vampire": Emoji{ + Keywords: []string{"man", "male", "dracula"}, + Char: "🧛‍♂️", + Category: "people", + }, "man_with_gua_pi_mao": Emoji{ Keywords: []string{"male", "boy", "chinese"}, Char: "👲", @@ -3889,6 +4029,11 @@ var emojis = Emojis{ Char: "👳", Category: "people", }, + "man_zombie": Emoji{ + Keywords: []string{"man", "male", "dracula", "undead", "walking dead"}, + Char: "🧟‍♂️", + Category: "people", + }, "mans_shoe": Emoji{ Keywords: []string{"fashion", "male"}, Char: "👞", @@ -3994,6 +4139,16 @@ var emojis = Emojis{ Char: "🚹", Category: "symbols", }, + "mermaid": Emoji{ + Keywords: []string{"woman", "female", "merwoman", "ariel"}, + Char: "🧜‍♀️", + Category: "people", + }, + "merman": Emoji{ + Keywords: []string{"man", "male", "triton"}, + Char: "🧜‍♂️", + Category: "people", + }, "metal": Emoji{ Keywords: []string{"hand", "fingers", "evil_eye", "sign_of_horns", "rock_on"}, Char: "🤘", @@ -4089,6 +4244,11 @@ var emojis = Emojis{ Char: "🐵", Category: "animals_and_nature", }, + "monocle": Emoji{ + Keywords: []string{"face", "stuffy", "wealthy"}, + Char: "🧐", + Category: "people", + }, "monorail": Emoji{ Keywords: []string{"transportation", "vehicle"}, Char: "🚝", @@ -4269,11 +4429,6 @@ var emojis = Emojis{ Char: "🤢", Category: "people", }, - "neckbeard": Emoji{ - Keywords: []string{"nerdy", "face", "custom_"}, - Char: "", - Category: "_custom", - }, "necktie": Emoji{ Keywords: []string{"shirt", "suitup", "formal", "fashion", "cloth", "business"}, Char: "👔", @@ -4494,11 +4649,6 @@ var emojis = Emojis{ Char: "🌊", Category: "animals_and_nature", }, - "octocat": Emoji{ - Keywords: []string{"animal", "octopus", "github", "custom_"}, - Char: "", - Category: "_custom", - }, "octopus": Emoji{ Keywords: []string{"animal", "creature", "ocean", "sea", "nature", "beach"}, Char: "🐙", @@ -4525,7 +4675,7 @@ var emojis = Emojis{ Category: "symbols", }, "ok_hand": Emoji{ - Keywords: []string{"fingers", "limbs", "perfect", "ok"}, + Keywords: []string{"fingers", "limbs", "perfect", "ok", "okay"}, Char: "👌", Category: "people", }, @@ -4544,6 +4694,11 @@ var emojis = Emojis{ Char: "🗝", Category: "objects", }, + "older_adult": Emoji{ + Keywords: []string{"human", "elder", "senior", "gender-neutral"}, + Char: "🧓", + Category: "people", + }, "older_man": Emoji{ Keywords: []string{"human", "male", "men", "old", "elder", "senior"}, Char: "👴", @@ -4629,6 +4784,11 @@ var emojis = Emojis{ Char: "📙", Category: "objects", }, + "orange_heart": Emoji{ + Keywords: []string{"love", "like", "affection", "valentines"}, + Char: "🧡", + Category: "symbols", + }, "orthodox_cross": Emoji{ Keywords: []string{"suppedaneum", "religion"}, Char: "☦", @@ -4694,6 +4854,11 @@ var emojis = Emojis{ Char: "🌴", Category: "animals_and_nature", }, + "palms_up": Emoji{ + Keywords: []string{"hands", "gesture", "cupped", "prayer"}, + Char: "🤲", + Category: "people", + }, "panama": Emoji{ Keywords: []string{"pa", "flag", "nation", "country", "banner"}, Char: "🇵🇦", @@ -4805,7 +4970,7 @@ var emojis = Emojis{ Category: "animals_and_nature", }, "pensive": Emoji{ - Keywords: []string{"face", "sad", "depressed", "okay", "upset"}, + Keywords: []string{"face", "sad", "depressed", "upset"}, Char: "😔", Category: "people", }, @@ -4844,6 +5009,11 @@ var emojis = Emojis{ Char: "⛏", Category: "objects", }, + "pie": Emoji{ + Keywords: []string{"food", "dessert", "pastry"}, + Char: "🥧", + Category: "food_and_drink", + }, "pig": Emoji{ Keywords: []string{"animal", "oink", "nature"}, Char: "🐷", @@ -5039,6 +5209,11 @@ var emojis = Emojis{ Char: "🤰", Category: "people", }, + "pretzel": Emoji{ + Keywords: []string{"food", "bread", "twisted"}, + Char: "🥨", + Category: "food_and_drink", + }, "previous_track_button": Emoji{ Keywords: []string{"backward"}, Char: "⏮", @@ -5134,26 +5309,6 @@ var emojis = Emojis{ Char: "😡", Category: "people", }, - "rage1": Emoji{ - Keywords: []string{"angry", "mad", "hate", "despise"}, - Char: "", - Category: "_custom", - }, - "rage2": Emoji{ - Keywords: []string{"angry", "mad", "hate", "despise"}, - Char: "", - Category: "_custom", - }, - "rage3": Emoji{ - Keywords: []string{"angry", "mad", "hate", "despise"}, - Char: "", - Category: "_custom", - }, - "rage4": Emoji{ - Keywords: []string{"angry", "mad", "hate", "despise"}, - Char: "", - Category: "_custom", - }, "railway_car": Emoji{ Keywords: []string{"transportation", "vehicle"}, Char: "🚃", @@ -5179,6 +5334,11 @@ var emojis = Emojis{ Char: "🤚", Category: "people", }, + "raised_eyebrow": Emoji{ + Keywords: []string{"face", "distrust", "scepticism", "disapproval", "disbelief", "surprise"}, + Char: "🤨", + Category: "people", + }, "raised_hand": Emoji{ Keywords: []string{"fingers", "stop", "highfive", "palm", "ban"}, Char: "✋", @@ -5464,6 +5624,11 @@ var emojis = Emojis{ Char: "👡", Category: "people", }, + "sandwich": Emoji{ + Keywords: []string{"food", "lunch", "bread"}, + Char: "🥪", + Category: "food_and_drink", + }, "santa": Emoji{ Keywords: []string{"festival", "man", "male", "xmas", "father christmas"}, Char: "🎅", @@ -5484,11 +5649,21 @@ var emojis = Emojis{ Char: "🇸🇦", Category: "flags", }, + "sauropod": Emoji{ + Keywords: []string{"animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct"}, + Char: "🦕", + Category: "animals_and_nature", + }, "saxophone": Emoji{ Keywords: []string{"music", "instrument", "jazz", "blues"}, Char: "🎷", Category: "activity", }, + "scarf": Emoji{ + Keywords: []string{"neck", "winter", "clothes"}, + Char: "🧣", + Category: "people", + }, "school": Emoji{ Keywords: []string{"building", "student", "education", "learn", "teach"}, Char: "🏫", @@ -5514,6 +5689,11 @@ var emojis = Emojis{ Char: "♏", Category: "symbols", }, + "scotland": Emoji{ + Keywords: []string{"flag", "scottish"}, + Char: "🏴󠁧󠁢󠁳󠁣󠁴󠁿", + Category: "flags", + }, "scream": Emoji{ Keywords: []string{"face", "munch", "scared", "omg"}, Char: "😱", @@ -5619,11 +5799,6 @@ var emojis = Emojis{ Char: "🚢", Category: "travel_and_places", }, - "shipit": Emoji{ - Keywords: []string{"squirrel", "detective", "animal", "sherlock", "inspector", "custom_"}, - Char: "", - Category: "_custom", - }, "shopping": Emoji{ Keywords: []string{"mall", "buy", "purchase"}, Char: "🛍", @@ -5644,6 +5819,11 @@ var emojis = Emojis{ Char: "🦐", Category: "animals_and_nature", }, + "shushing": Emoji{ + Keywords: []string{"face", "quiet", "shhh"}, + Char: "🤫", + Category: "people", + }, "sierra_leone": Emoji{ Keywords: []string{"sierra", "leone", "flag", "nation", "country", "banner"}, Char: "🇸🇱", @@ -5685,15 +5865,20 @@ var emojis = Emojis{ Category: "activity", }, "skull": Emoji{ - Keywords: []string{"dead", "skeleton", "creepy"}, + Keywords: []string{"dead", "skeleton", "creepy", "death"}, Char: "💀", Category: "people", }, "skull_and_crossbones": Emoji{ - Keywords: []string{"poison", "danger", "deadly", "scary"}, + Keywords: []string{"poison", "danger", "deadly", "scary", "death", "pirate", "evil"}, Char: "☠", Category: "objects", }, + "sled": Emoji{ + Keywords: []string{"sleigh", "luge", "toboggan"}, + Char: "🛷", + Category: "activity", + }, "sleeping": Emoji{ Keywords: []string{"face", "tired", "sleepy", "night", "zzz"}, Char: "😴", @@ -5844,6 +6029,11 @@ var emojis = Emojis{ Char: "⚽", Category: "activity", }, + "socks": Emoji{ + Keywords: []string{"stockings", "clothes"}, + Char: "🧦", + Category: "people", + }, "solomon_islands": Emoji{ Keywords: []string{"solomon", "islands", "flag", "nation", "country", "banner"}, Char: "🇸🇧", @@ -5859,6 +6049,11 @@ var emojis = Emojis{ Char: "🔜", Category: "symbols", }, + "sorceress": Emoji{ + Keywords: []string{"woman", "female", "mage", "witch"}, + Char: "🧙‍♀️", + Category: "people", + }, "sos": Emoji{ Keywords: []string{"help", "red-square", "words", "emergency", "911"}, Char: "🆘", @@ -6034,6 +6229,11 @@ var emojis = Emojis{ Char: "✡", Category: "symbols", }, + "star_struck": Emoji{ + Keywords: []string{"face", "smile", "starry", "eyes", "grinning"}, + Char: "🤩", + Category: "people", + }, "stars": Emoji{ Keywords: []string{"night", "photo"}, Char: "🌠", @@ -6049,6 +6249,11 @@ var emojis = Emojis{ Char: "🗽", Category: "travel_and_places", }, + "steak": Emoji{ + Keywords: []string{"food", "cow", "meat", "cut", "chop", "lambchop", "porkchop"}, + Char: "🥩", + Category: "food_and_drink", + }, "steam_locomotive": Emoji{ Keywords: []string{"transportation", "vehicle", "train"}, Char: "🚂", @@ -6179,11 +6384,6 @@ var emojis = Emojis{ Char: "🍣", Category: "food_and_drink", }, - "suspect": Emoji{ - Keywords: []string{"mad", "custom_"}, - Char: "", - Category: "_custom", - }, "suspension_railway": Emoji{ Keywords: []string{"vehicle", "transportation"}, Char: "🚟", @@ -6239,6 +6439,11 @@ var emojis = Emojis{ Char: "🔣", Category: "symbols", }, + "symbols_over_mouth": Emoji{ + Keywords: []string{"face", "swearing", "cursing", "cussing", "profanity", "expletive"}, + Char: "🤬", + Category: "people", + }, "synagogue": Emoji{ Keywords: []string{"judaism", "worship", "temple", "jewish"}, Char: "🕍", @@ -6254,6 +6459,11 @@ var emojis = Emojis{ Char: "💉", Category: "objects", }, + "t-rex": Emoji{ + Keywords: []string{"animal", "nature", "dinosaur", "tyrannosaurus", "extinct"}, + Char: "🦖", + Category: "animals_and_nature", + }, "taco": Emoji{ Keywords: []string{"food", "mexican"}, Char: "🌮", @@ -6274,6 +6484,11 @@ var emojis = Emojis{ Char: "🇹🇯", Category: "flags", }, + "takeout_box": Emoji{ + Keywords: []string{"food", "leftovers"}, + Char: "🥡", + Category: "food_and_drink", + }, "tanabata_tree": Emoji{ Keywords: []string{"plant", "nature", "branch", "summer"}, Char: "🎋", @@ -6310,7 +6525,7 @@ var emojis = Emojis{ Category: "objects", }, "telescope": Emoji{ - Keywords: []string{"stars", "space", "zoom"}, + Keywords: []string{"stars", "space", "zoom", "science", "astronomy"}, Char: "🔭", Category: "objects", }, @@ -6514,11 +6729,6 @@ var emojis = Emojis{ Char: "🚎", Category: "travel_and_places", }, - "trollface": Emoji{ - Keywords: []string{"internet", "meme", "custom_"}, - Char: "", - Category: "_custom", - }, "trophy": Emoji{ Keywords: []string{"win", "award", "contest", "place", "ftw", "ceremony"}, Char: "🏆", @@ -6695,7 +6905,7 @@ var emojis = Emojis{ Category: "animals_and_nature", }, "unamused": Emoji{ - Keywords: []string{"indifference", "bored", "straight face", "serious", "sarcasm"}, + Keywords: []string{"indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye"}, Char: "😒", Category: "people", }, @@ -6819,6 +7029,11 @@ var emojis = Emojis{ Char: "🏐", Category: "activity", }, + "vomiting": Emoji{ + Keywords: []string{"face", "sick"}, + Char: "🤮", + Category: "people", + }, "vs": Emoji{ Keywords: []string{"words", "orange-square"}, Char: "🆚", @@ -6829,6 +7044,11 @@ var emojis = Emojis{ Char: "🖖", Category: "people", }, + "wales": Emoji{ + Keywords: []string{"flag", "welsh"}, + Char: "🏴󠁧󠁢󠁷󠁬󠁳󠁿", + Category: "flags", + }, "walking_man": Emoji{ Keywords: []string{"human", "feet", "steps"}, Char: "🚶", @@ -7019,6 +7239,11 @@ var emojis = Emojis{ Char: "😉", Category: "people", }, + "wizard": Emoji{ + Keywords: []string{"man", "male", "mage", "sorcerer"}, + Char: "🧙‍♂️", + Category: "people", + }, "wolf": Emoji{ Keywords: []string{"animal", "nature", "wild"}, Char: "🐺", @@ -7049,6 +7274,11 @@ var emojis = Emojis{ Char: "👩‍🍳", Category: "people", }, + "woman_elf": Emoji{ + Keywords: []string{"woman", "female"}, + Char: "🧝‍♀️", + Category: "people", + }, "woman_facepalming": Emoji{ Keywords: []string{"woman", "female", "girl", "disbelief"}, Char: "🤦‍♀️", @@ -7059,6 +7289,11 @@ var emojis = Emojis{ Char: "👩‍🏭", Category: "people", }, + "woman_fairy": Emoji{ + Keywords: []string{"woman", "female"}, + Char: "🧚‍♀️", + Category: "people", + }, "woman_farmer": Emoji{ Keywords: []string{"rancher", "gardener", "woman", "human"}, Char: "👩‍🌾", @@ -7069,11 +7304,26 @@ var emojis = Emojis{ Char: "👩‍🚒", Category: "people", }, + "woman_genie": Emoji{ + Keywords: []string{"woman", "female"}, + Char: "🧞‍♀️", + Category: "people", + }, "woman_health_worker": Emoji{ Keywords: []string{"doctor", "nurse", "therapist", "healthcare", "woman", "human"}, Char: "👩‍⚕️", Category: "people", }, + "woman_in_lotus_position": Emoji{ + Keywords: []string{"woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness"}, + Char: "🧘‍♀️", + Category: "activity", + }, + "woman_in_steamy_room": Emoji{ + Keywords: []string{"female", "woman", "spa", "steamroom", "sauna"}, + Char: "🧖‍♀️", + Category: "people", + }, "woman_judge": Emoji{ Keywords: []string{"justice", "court", "woman", "human"}, Char: "👩‍⚖️", @@ -7085,7 +7335,7 @@ var emojis = Emojis{ Category: "activity", }, "woman_mechanic": Emoji{ - Keywords: []string{"plumber", "woman", "human"}, + Keywords: []string{"plumber", "woman", "human", "wrench"}, Char: "👩‍🔧", Category: "people", }, @@ -7135,15 +7385,30 @@ var emojis = Emojis{ Category: "people", }, "woman_technologist": Emoji{ - Keywords: []string{"coder", "developer", "engineer", "programmer", "software", "woman", "human"}, + Keywords: []string{"coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer"}, Char: "👩‍💻", Category: "people", }, + "woman_vampire": Emoji{ + Keywords: []string{"woman", "female"}, + Char: "🧛‍♀️", + Category: "people", + }, + "woman_with_headscarf": Emoji{ + Keywords: []string{"female", "hijab", "mantilla", "tichel"}, + Char: "🧕", + Category: "people", + }, "woman_with_turban": Emoji{ Keywords: []string{"female", "indian", "hinduism", "arabs", "woman"}, Char: "👳‍♀️", Category: "people", }, + "woman_zombie": Emoji{ + Keywords: []string{"woman", "female", "undead", "walking dead"}, + Char: "🧟‍♀️", + Category: "people", + }, "womans_clothes": Emoji{ Keywords: []string{"fashion", "shopping_bags", "female"}, Char: "👚", @@ -7219,11 +7484,21 @@ var emojis = Emojis{ Char: "🇿🇲", Category: "flags", }, + "zany": Emoji{ + Keywords: []string{"face", "goofy", "crazy"}, + Char: "🤪", + Category: "people", + }, "zap": Emoji{ Keywords: []string{"thunder", "weather", "lightning bolt", "fast"}, Char: "⚡", Category: "animals_and_nature", }, + "zebra": Emoji{ + Keywords: []string{"animal", "nature", "stripes", "safari"}, + Char: "🦓", + Category: "animals_and_nature", + }, "zero": Emoji{ Keywords: []string{"0", "numbers", "blue-square", "null"}, Char: "0️⃣", @@ -7431,6 +7706,7 @@ var keywordLookup = map[string][]string{ "kissing_smiling_eyes", "love_hotel", "love_letter", + "orange_heart", "purple_heart", "revolving_hearts", "sparkling_heart", @@ -7542,10 +7818,6 @@ var keywordLookup = map[string][]string{ "facepunch", "imp", "rage", - "rage1", - "rage2", - "rage3", - "rage4", }, "animal": []string{ "ant", @@ -7583,13 +7855,16 @@ var keywordLookup = map[string][]string{ "fox_face", "fried_shrimp", "frog", + "giraffe", "goat", "gorilla", + "grasshopper", "hamster", "hatched_chick", "hatching_chick", "hear_no_evil", "heart_eyes_cat", + "hedgehog", "honeybee", "horse", "horse_racing", @@ -7603,7 +7878,6 @@ var keywordLookup = map[string][]string{ "monkey_face", "mouse", "mouse2", - "octocat", "octopus", "owl", "ox", @@ -7622,12 +7896,12 @@ var keywordLookup = map[string][]string{ "rat", "rhinoceros", "rooster", + "sauropod", "scorpion", "scream_cat", "see_no_evil", "shark", "sheep", - "shipit", "shrimp", "smile_cat", "smiley_cat", @@ -7638,6 +7912,7 @@ var keywordLookup = map[string][]string{ "spider", "spider_web", "squid", + "t-rex", "tiger", "tiger2", "tropical_fish", @@ -7648,6 +7923,7 @@ var keywordLookup = map[string][]string{ "whale", "whale2", "wolf", + "zebra", }, "annoyed": []string{ "angry", @@ -7697,6 +7973,9 @@ var keywordLookup = map[string][]string{ "straight_ruler", "triangular_ruler", }, + "ariel": []string{ + "mermaid", + }, "arm": []string{ "muscle", }, @@ -7754,6 +8033,9 @@ var keywordLookup = map[string][]string{ "taurus", "virgo", }, + "astronomy": []string{ + "telescope", + }, "at": []string{ "austria", }, @@ -7799,6 +8081,7 @@ var keywordLookup = map[string][]string{ "azerbaijan", }, "baby": []string{ + "breastfeeding", "hatched_chick", "hatching_chick", "pregnant_woman", @@ -8123,6 +8406,9 @@ var keywordLookup = map[string][]string{ "barthélemy": []string{ "st_barthelemy", }, + "baseball": []string{ + "billed_hat", + }, "bath": []string{ "hotsprings", }, @@ -8197,6 +8483,9 @@ var keywordLookup = map[string][]string{ "tumbler_glass", "wine_glass", }, + "bewhiskered": []string{ + "bearded_person", + }, "bg": []string{ "bulgaria", }, @@ -8280,11 +8569,9 @@ var keywordLookup = map[string][]string{ "blood": []string{ "syringe", }, - "bloody": []string{ - "goberserk", - }, "blown": []string{ "boom", + "exploding_head", }, "blue": []string{ "cyclone", @@ -8325,6 +8612,7 @@ var keywordLookup = map[string][]string{ "cool", "customs", "eight", + "eject_button", "fast_forward", "five", "four", @@ -8458,6 +8746,7 @@ var keywordLookup = map[string][]string{ }, "box": []string{ "bento", + "coffin", "package", }, "boy": []string{ @@ -8484,6 +8773,9 @@ var keywordLookup = map[string][]string{ "br": []string{ "brazil", }, + "brachiosaurus": []string{ + "sauropod", + }, "branch": []string{ "tanabata_tree", }, @@ -8496,12 +8788,15 @@ var keywordLookup = map[string][]string{ "bread": []string{ "baguette_bread", "croissant", + "pretzel", + "sandwich", }, "break": []string{ "broken_heart", }, "breakfast": []string{ "bacon", + "bowl_with_spoon", "bread", "egg", "fried_egg", @@ -8538,6 +8833,9 @@ var keywordLookup = map[string][]string{ "bromance": []string{ "two_men_holding_hands", }, + "brontosaurus": []string{ + "sauropod", + }, "brown": []string{ "horse", }, @@ -8591,6 +8889,9 @@ var keywordLookup = map[string][]string{ "city_sunset", "houses", }, + "bullseye": []string{ + "dart", + }, "bunny": []string{ "dancing_men", "dancing_women", @@ -8704,6 +9005,7 @@ var keywordLookup = map[string][]string{ "lollipop", }, "cap": []string{ + "billed_hat", "mortar_board", }, "caption": []string{ @@ -8725,6 +9027,9 @@ var keywordLookup = map[string][]string{ "hearts", "spades", }, + "career": []string{ + "briefcase", + }, "carnival": []string{ "carousel_horse", "circus_tent", @@ -8754,6 +9059,9 @@ var keywordLookup = map[string][]string{ "casino": []string{ "slot_machine", }, + "casket": []string{ + "coffin", + }, "casserole": []string{ "shallow_pan_of_food", }, @@ -8813,6 +9121,9 @@ var keywordLookup = map[string][]string{ "century": []string{ "100", }, + "cereal": []string{ + "bowl_with_spoon", + }, "ceremony": []string{ "trophy", }, @@ -8822,6 +9133,9 @@ var keywordLookup = map[string][]string{ "chadder": []string{ "cheese", }, + "champagne": []string{ + "clinking_glasses", + }, "characters": []string{ "symbols", }, @@ -8852,6 +9166,10 @@ var keywordLookup = map[string][]string{ "man_scientist", "woman_scientist", }, + "chemistry": []string{ + "alembic", + "atom_symbol", + }, "chicken": []string{ "baby_chick", "egg", @@ -8935,9 +9253,15 @@ var keywordLookup = map[string][]string{ "chips": []string{ "fries", }, + "chirp": []string{ + "grasshopper", + }, "chocolate": []string{ "cookie", }, + "chop": []string{ + "steak", + }, "chopsticks": []string{ "ramen", }, @@ -8979,7 +9303,6 @@ var keywordLookup = map[string][]string{ }, "circus": []string{ "balloon", - "bowtie", "confetti_ball", "crystal_ball", "elephant", @@ -8997,7 +9320,6 @@ var keywordLookup = map[string][]string{ "notebook_with_decorative_cover", }, "classy": []string{ - "bowtie", "man", "tophat", }, @@ -9024,6 +9346,9 @@ var keywordLookup = map[string][]string{ }, "clothes": []string{ "dress", + "gloves", + "scarf", + "socks", }, "cloud": []string{ "cyclone", @@ -9110,7 +9435,9 @@ var keywordLookup = map[string][]string{ }, "computer": []string{ "keyboard", + "man_technologist", "robot", + "woman_technologist", }, "computing": []string{ "desktop_computer", @@ -9458,11 +9785,15 @@ var keywordLookup = map[string][]string{ "cow": []string{ "milk_glass", "ox", + "steak", "water_buffalo", }, "cowgirl": []string{ "cowboy_hat_face", }, + "crazy": []string{ + "zany", + }, "create": []string{ "hammer", "hammer_and_pick", @@ -9481,6 +9812,9 @@ var keywordLookup = map[string][]string{ "japanese_ogre", "skull", }, + "cricket": []string{ + "grasshopper", + }, "croak": []string{ "frog", }, @@ -9521,6 +9855,9 @@ var keywordLookup = map[string][]string{ "cupertino": []string{ "motorway", }, + "cupped": []string{ + "palms_up", + }, "curaçao": []string{ "curacao", }, @@ -9531,22 +9868,21 @@ var keywordLookup = map[string][]string{ "pound", "yen", }, + "cursing": []string{ + "symbols_over_mouth", + }, "curtain": []string{ "cinema", }, + "cussing": []string{ + "symbols_over_mouth", + }, "custom": []string{ "passport_control", }, - "custom_": []string{ - "hurtrealbad", - "neckbeard", - "octocat", - "shipit", - "suspect", - "trollface", - }, "cut": []string{ "scissors", + "steak", "u5272", }, "cutlery": []string{ @@ -9631,6 +9967,8 @@ var keywordLookup = map[string][]string{ "death": []string{ "coffin", "funeral_urn", + "skull", + "skull_and_crossbones", }, "december": []string{ "christmas_tree", @@ -9686,10 +10024,6 @@ var keywordLookup = map[string][]string{ }, "despise": []string{ "rage", - "rage1", - "rage2", - "rage3", - "rage4", }, "dessert": []string{ "birthday", @@ -9702,6 +10036,7 @@ var keywordLookup = map[string][]string{ "doughnut", "ice_cream", "icecream", + "pie", "shaved_ice", }, "destination": []string{ @@ -9712,7 +10047,6 @@ var keywordLookup = map[string][]string{ "mag", "mag_right", "male_detective", - "shipit", }, "developer": []string{ "man_technologist", @@ -9758,6 +10092,13 @@ var keywordLookup = map[string][]string{ "dinner": []string{ "plate_with_cutlery", }, + "dinosaur": []string{ + "sauropod", + "t-rex", + }, + "diplodocus": []string{ + "sauropod", + }, "direction": []string{ "arrow_backward", "arrow_double_down", @@ -9794,12 +10135,16 @@ var keywordLookup = map[string][]string{ "disappointed": []string{ "slightly_frowning_face", }, + "disapproval": []string{ + "raised_eyebrow", + }, "disaster": []string{ "ocean", "volcano", }, "disbelief": []string{ "man_facepalming", + "raised_eyebrow", "woman_facepalming", }, "disc": []string{ @@ -9830,6 +10175,9 @@ var keywordLookup = map[string][]string{ "distilling": []string{ "alembic", }, + "distrust": []string{ + "raised_eyebrow", + }, "divide": []string{ "heavy_division_sign", "u5272", @@ -9891,13 +10239,6 @@ var keywordLookup = map[string][]string{ "donut": []string{ "doughnut", }, - "doom": []string{ - "feelsgood", - "finnadie", - "goberserk", - "godmode", - "hurtrealbad", - }, "door": []string{ "key", "old_key", @@ -9920,6 +10261,10 @@ var keywordLookup = map[string][]string{ "downtown": []string{ "night_with_stars", }, + "dracula": []string{ + "man_vampire", + "man_zombie", + }, "drama": []string{ "performing_arts", }, @@ -9946,6 +10291,7 @@ var keywordLookup = map[string][]string{ "champagne", "clinking_glasses", "cocktail", + "cup_with_straw", "milk_glass", "non-potable_water", "sake", @@ -9986,6 +10332,9 @@ var keywordLookup = map[string][]string{ "tumbler_glass", "wine_glass", }, + "dubious": []string{ + "unamused", + }, "dusk": []string{ "clock6", }, @@ -10063,6 +10412,7 @@ var keywordLookup = map[string][]string{ "el_salvador", }, "elder": []string{ + "older_adult", "older_man", "older_woman", }, @@ -10100,6 +10450,9 @@ var keywordLookup = map[string][]string{ "emirates": []string{ "united_arab_emirates", }, + "empanada": []string{ + "dumpling", + }, "empty": []string{ "u7a7a", }, @@ -10126,6 +10479,7 @@ var keywordLookup = map[string][]string{ "uk", }, "english": []string{ + "england", "uk", }, "enter": []string{ @@ -10201,6 +10555,7 @@ var keywordLookup = map[string][]string{ "evil": []string{ "black_heart", "japanese_goblin", + "skull_and_crossbones", "snake", }, "evil_eye": []string{ @@ -10234,6 +10589,9 @@ var keywordLookup = map[string][]string{ "alembic", "microscope", }, + "expletive": []string{ + "symbols_over_mouth", + }, "explode": []string{ "bomb", "boom", @@ -10245,12 +10603,19 @@ var keywordLookup = map[string][]string{ "express": []string{ "articulated_lorry", }, + "extinct": []string{ + "sauropod", + "t-rex", + }, "eye": []string{ "wink", }, "eyeroll": []string{ "roll_eyes", }, + "eyes": []string{ + "star_struck", + }, "eyesight": []string{ "eyeglasses", }, @@ -10262,7 +10627,6 @@ var keywordLookup = map[string][]string{ "anguished", "astonished", "blush", - "bowtie", "clown_face", "cold_sweat", "confounded", @@ -10274,6 +10638,7 @@ var keywordLookup = map[string][]string{ "disappointed_relieved", "drooling_face", "ear", + "exploding_head", "expressionless", "eye", "fearful", @@ -10284,6 +10649,7 @@ var keywordLookup = map[string][]string{ "grimacing", "grin", "grinning", + "hand_over_mouth", "heart_eyes", "hugs", "hushed", @@ -10298,18 +10664,20 @@ var keywordLookup = map[string][]string{ "lying_face", "mask", "money_mouth_face", + "monocle", "nauseated_face", - "neckbeard", "nerd_face", "no_mouth", "open_mouth", "pensive", "persevere", + "raised_eyebrow", "relaxed", "relieved", "rofl", "roll_eyes", "scream", + "shushing", "sleeping", "sleepy", "slightly_frowning_face", @@ -10319,19 +10687,23 @@ var keywordLookup = map[string][]string{ "smirk", "sneezing_face", "sob", + "star_struck", "stuck_out_tongue", "stuck_out_tongue_closed_eyes", "stuck_out_tongue_winking_eye", "sunglasses", "sweat", "sweat_smile", + "symbols_over_mouth", "thinking", "triumph", "upside_down_face", + "vomiting", "weary", "wink", "worried", "yum", + "zany", "zipper_mouth_face", }, "fail": []string{ @@ -10375,7 +10747,6 @@ var keywordLookup = map[string][]string{ "fashion": []string{ "bikini", "boot", - "bowtie", "dress", "eyeglasses", "handbag", @@ -10441,6 +10812,7 @@ var keywordLookup = map[string][]string{ "bikini", "blonde_woman", "bowing_woman", + "climbing_woman", "construction_worker_woman", "dancer", "dancing_women", @@ -10454,6 +10826,7 @@ var keywordLookup = map[string][]string{ "kimono", "lipstick", "massage_woman", + "mermaid", "mountain_biking_woman", "mrs_claus", "no_good_woman", @@ -10465,6 +10838,7 @@ var keywordLookup = map[string][]string{ "raising_hand_woman", "rowing_woman", "running_woman", + "sorceress", "surfing_woman", "swimming_woman", "tipping_hand_woman", @@ -10472,9 +10846,17 @@ var keywordLookup = map[string][]string{ "walking_woman", "weight_lifting_woman", "woman", + "woman_elf", "woman_facepalming", + "woman_fairy", + "woman_genie", + "woman_in_lotus_position", + "woman_in_steamy_room", "woman_shrugging", + "woman_vampire", + "woman_with_headscarf", "woman_with_turban", + "woman_zombie", "womans_clothes", "womans_hat", "womens", @@ -10524,6 +10906,7 @@ var keywordLookup = map[string][]string{ "fingers": []string{ "fist", "fu", + "love_you", "metal", "ok_hand", "open_hands", @@ -10649,6 +11032,7 @@ var keywordLookup = map[string][]string{ "ecuador", "egypt", "el_salvador", + "england", "equatorial_guinea", "eritrea", "es", @@ -10772,6 +11156,7 @@ var keywordLookup = map[string][]string{ "san_marino", "sao_tome_principe", "saudi_arabia", + "scotland", "senegal", "serbia", "seychelles", @@ -10824,6 +11209,7 @@ var keywordLookup = map[string][]string{ "vatican_city", "venezuela", "vietnam", + "wales", "wallis_futuna", "western_sahara", "yemen", @@ -10899,14 +11285,19 @@ var keywordLookup = map[string][]string{ "bento", "birthday", "blowfish", + "bowl_with_spoon", "bread", + "broccoli", "burrito", "cake", + "canned_food", "carrot", "cheese", "cherries", "chestnut", "chocolate_bar", + "chopsticks", + "coconut", "cookie", "corn", "croissant", @@ -10915,11 +11306,13 @@ var keywordLookup = map[string][]string{ "custard", "dango", "doughnut", + "dumpling", "egg", "eggplant", "fish", "fish_cake", "fishing_pole_and_fish", + "fortune_cookie", "fried_egg", "fried_shrimp", "grapes", @@ -10937,24 +11330,29 @@ var keywordLookup = map[string][]string{ "peach", "peanuts", "pear", + "pie", "pineapple", "pizza", "plate_with_cutlery", "popcorn", "potato", "poultry_leg", + "pretzel", "ramen", "rice", "rice_ball", "rice_cracker", + "sandwich", "shallow_pan_of_food", "spaghetti", + "steak", "stew", "strawberry", "stuffed_flatbread", "sushi", "sweet_potato", "taco", + "takeout_box", "tangerine", "tomato", "watermelon", @@ -10973,7 +11371,6 @@ var keywordLookup = map[string][]string{ "u7981", }, "formal": []string{ - "bowtie", "necktie", }, "formula": []string{ @@ -11028,7 +11425,9 @@ var keywordLookup = map[string][]string{ "apple", "avocado", "banana", + "broccoli", "cherries", + "coconut", "cucumber", "grapes", "green_apple", @@ -11064,6 +11463,9 @@ var keywordLookup = map[string][]string{ "man_dancing", "roller_coaster", }, + "funeral": []string{ + "coffin", + }, "funny": []string{ "smile", "smiley", @@ -11144,6 +11546,11 @@ var keywordLookup = map[string][]string{ "restroom", "womens", }, + "gender-neutral": []string{ + "adult", + "child", + "older_adult", + }, "gentleman": []string{ "tophat", }, @@ -11155,6 +11562,8 @@ var keywordLookup = map[string][]string{ }, "gesture": []string{ "call_me_hand", + "love_you", + "palms_up", "raised_hands", "wave", }, @@ -11200,9 +11609,6 @@ var keywordLookup = map[string][]string{ "dancing_women", "woman", }, - "github": []string{ - "octocat", - }, "give up": []string{ "white_flag", }, @@ -11256,6 +11662,9 @@ var keywordLookup = map[string][]string{ "goodbye": []string{ "wave", }, + "goofy": []string{ + "zany", + }, "gp": []string{ "guadeloupe", }, @@ -11322,6 +11731,9 @@ var keywordLookup = map[string][]string{ "grin": []string{ "grinning", }, + "grinning": []string{ + "star_struck", + }, "groceries": []string{ "convenience_store", }, @@ -11400,6 +11812,7 @@ var keywordLookup = map[string][]string{ "fist_left", "fist_right", "fu", + "love_you", "metal", "muscle", "point_down", @@ -11414,7 +11827,9 @@ var keywordLookup = map[string][]string{ "hands": []string{ "call_me_hand", "clap", + "gloves", "open_hands", + "palms_up", "raised_hands", "wave", }, @@ -11455,10 +11870,6 @@ var keywordLookup = map[string][]string{ }, "hate": []string{ "rage", - "rage1", - "rage2", - "rage3", - "rage4", }, "have": []string{ "u6709", @@ -11541,6 +11952,9 @@ var keywordLookup = map[string][]string{ "highway": []string{ "motorway", }, + "hijab": []string{ + "woman_with_headscarf", + }, "hinduism": []string{ "man_with_turban", "om", @@ -11575,6 +11989,8 @@ var keywordLookup = map[string][]string{ }, "hobby": []string{ "8ball", + "climbing_man", + "climbing_woman", "fishing_pole_and_fish", "rowing_man", "rowing_woman", @@ -11740,6 +12156,7 @@ var keywordLookup = map[string][]string{ "mountain_biking_woman", "ok_man", "ok_woman", + "older_adult", "older_man", "older_woman", "put_litter_in_its_place", @@ -11776,9 +12193,11 @@ var keywordLookup = map[string][]string{ "hundred": []string{ "100", }, + "hurricane": []string{ + "cyclone", + }, "hurt": []string{ "face_with_head_bandage", - "goberserk", }, "i18n": []string{ "globe_with_meridians", @@ -11873,7 +12292,6 @@ var keywordLookup = map[string][]string{ }, "injured": []string{ "face_with_head_bandage", - "hurtrealbad", }, "ink": []string{ "printer", @@ -11892,9 +12310,6 @@ var keywordLookup = map[string][]string{ "honeybee", "spider_web", }, - "inspector": []string{ - "shipit", - }, "instructor": []string{ "man_teacher", "woman_teacher", @@ -11907,6 +12322,9 @@ var keywordLookup = map[string][]string{ "saxophone", "violin", }, + "intelligent": []string{ + "brain", + }, "international": []string{ "earth_africa", "earth_americas", @@ -11916,7 +12334,6 @@ var keywordLookup = map[string][]string{ "internet": []string{ "globe_with_meridians", "signal_strength", - "trollface", }, "interstate": []string{ "motorway", @@ -11990,6 +12407,9 @@ var keywordLookup = map[string][]string{ "ivory": []string{ "cote_divoire", }, + "jacket": []string{ + "coat", + }, "jainism": []string{ "om", "wheel_of_dharma", @@ -12066,6 +12486,9 @@ var keywordLookup = map[string][]string{ "jo": []string{ "jordan", }, + "job": []string{ + "briefcase", + }, "join": []string{ "u5408", }, @@ -12099,6 +12522,9 @@ var keywordLookup = map[string][]string{ "man_judge", "woman_judge", }, + "kamaboko": []string{ + "fish_cake", + }, "kanji": []string{ "accept", "congratulations", @@ -12234,6 +12660,9 @@ var keywordLookup = map[string][]string{ "ladybug": []string{ "beetle", }, + "lambchop": []string{ + "steak", + }, "landing": []string{ "flight_departure", }, @@ -12245,6 +12674,8 @@ var keywordLookup = map[string][]string{ }, "laptop": []string{ "computer", + "man_technologist", + "woman_technologist", }, "late": []string{ "clock1", @@ -12322,6 +12753,9 @@ var keywordLookup = map[string][]string{ "arrow_backward", "point_left", }, + "leftovers": []string{ + "takeout_box", + }, "legal": []string{ "briefcase", "copyright", @@ -12421,6 +12855,7 @@ var keywordLookup = map[string][]string{ "kissing_heart", "love_hotel", "love_letter", + "orange_heart", "purple_heart", "revolving_hearts", "smile", @@ -12530,6 +12965,7 @@ var keywordLookup = map[string][]string{ "kissing", "kissing_closed_eyes", "kissing_heart", + "orange_heart", "purple_heart", "revolving_hearts", "rose", @@ -12569,8 +13005,12 @@ var keywordLookup = map[string][]string{ "crossed_fingers", "four_leaf_clover", }, + "luge": []string{ + "sled", + }, "lunch": []string{ "plate_with_cutlery", + "sandwich", }, "lv": []string{ "latvia", @@ -12599,19 +13039,16 @@ var keywordLookup = map[string][]string{ "mad": []string{ "anger", "angry", - "hurtrealbad", "rage", - "rage1", - "rage2", - "rage3", - "rage4", "right_anger_bubble", - "suspect", + }, + "mage": []string{ + "sorceress", + "wizard", }, "magic": []string{ "8ball", "black_joker", - "bowtie", "clubs", "crescent_moon", "crystal_ball", @@ -12636,19 +13073,28 @@ var keywordLookup = map[string][]string{ "blonde_man", "bowing_man", "boy", + "climbing_man", "construction_worker_man", "dancing_men", "frowning_man", "guardsman", "haircut_man", "man_dancing", + "man_elf", "man_facepalming", + "man_fairy", + "man_genie", + "man_in_lotus_position", + "man_in_steamy_room", "man_shrugging", + "man_vampire", "man_with_gua_pi_mao", "man_with_turban", + "man_zombie", "mans_shoe", "massage_man", "mens", + "merman", "no_good_man", "ok_man", "older_man", @@ -12657,6 +13103,7 @@ var keywordLookup = map[string][]string{ "raising_hand_man", "santa", "tipping_hand_man", + "wizard", }, "mall": []string{ "department_store", @@ -12672,17 +13119,23 @@ var keywordLookup = map[string][]string{ "blonde_man", "bowing_man", "boy", + "climbing_man", "frowning_man", "haircut_man", "isle_of_man", "man_artist", "man_astronaut", "man_cook", + "man_elf", "man_facepalming", "man_factory_worker", + "man_fairy", "man_farmer", "man_firefighter", + "man_genie", "man_health_worker", + "man_in_lotus_position", + "man_in_steamy_room", "man_judge", "man_mechanic", "man_office_worker", @@ -12693,7 +13146,10 @@ var keywordLookup = map[string][]string{ "man_student", "man_teacher", "man_technologist", + "man_vampire", + "man_zombie", "massage_man", + "merman", "no_good_man", "ok_man", "policeman", @@ -12703,6 +13159,7 @@ var keywordLookup = map[string][]string{ "running_man", "santa", "tipping_hand_man", + "wizard", }, "manager": []string{ "man_office_worker", @@ -12711,6 +13168,9 @@ var keywordLookup = map[string][]string{ "manicure": []string{ "nail_care", }, + "mantilla": []string{ + "woman_with_headscarf", + }, "map": []string{ "round_pushpin", }, @@ -12776,6 +13236,7 @@ var keywordLookup = map[string][]string{ "dango", "hamburger", "poultry_leg", + "steak", "stew", }, "mecca": []string{ @@ -12786,13 +13247,14 @@ var keywordLookup = map[string][]string{ "pill", "syringe", }, + "meditation": []string{ + "man_in_lotus_position", + "woman_in_lotus_position", + }, "meh": []string{ "expressionless", "neutral_face", }, - "meme": []string{ - "trollface", - }, "memo": []string{ "spiral_notepad", }, @@ -12805,6 +13267,9 @@ var keywordLookup = map[string][]string{ "cat", "cat2", }, + "merwoman": []string{ + "mermaid", + }, "message": []string{ "left_speech_bubble", "speech_balloon", @@ -12842,6 +13307,13 @@ var keywordLookup = map[string][]string{ "minaret": []string{ "mosque", }, + "mind": []string{ + "exploding_head", + }, + "mindfulness": []string{ + "man_in_lotus_position", + "woman_in_lotus_position", + }, "minor": []string{ "underage", }, @@ -13048,6 +13520,9 @@ var keywordLookup = map[string][]string{ "nap": []string{ "sleepy", }, + "narutomaki": []string{ + "fish_cake", + }, "nation": []string{ "afghanistan", "aland_islands", @@ -13317,6 +13792,7 @@ var keywordLookup = map[string][]string{ "cherry_blossom", "chicken", "chipmunk", + "coconut", "cow", "cow2", "crocodile", @@ -13342,11 +13818,13 @@ var keywordLookup = map[string][]string{ "frog", "full_moon", "full_moon_with_face", + "giraffe", "goat", "gorilla", "green_apple", "hamster", "hear_no_evil", + "hedgehog", "honeybee", "horse", "house_with_garden", @@ -13389,6 +13867,7 @@ var keywordLookup = map[string][]string{ "ram", "rhinoceros", "rooster", + "sauropod", "see_no_evil", "seedling", "shamrock", @@ -13404,6 +13883,7 @@ var keywordLookup = map[string][]string{ "sunflower", "sunny", "sweet_potato", + "t-rex", "tanabata_tree", "tangerine", "tiger", @@ -13423,10 +13903,14 @@ var keywordLookup = map[string][]string{ "wilted_flower", "wind_chime", "wolf", + "zebra", }, "ne": []string{ "niger", }, + "neck": []string{ + "scarf", + }, "needle": []string{ "syringe", }, @@ -13435,7 +13919,6 @@ var keywordLookup = map[string][]string{ }, "nerdy": []string{ "eyeglasses", - "neckbeard", "nerd_face", }, "nervous": []string{ @@ -13592,9 +14075,15 @@ var keywordLookup = map[string][]string{ "syringe", "woman_health_worker", }, + "nursing": []string{ + "breastfeeding", + }, "nut": []string{ "peanuts", }, + "oatmeal": []string{ + "bowl_with_spoon", + }, "obtain": []string{ "ideograph_advantage", }, @@ -13612,9 +14101,6 @@ var keywordLookup = map[string][]string{ "whale", "whale2", }, - "octopus": []string{ - "octocat", - }, "office": []string{ "file_folder", "page_facing_up", @@ -13638,7 +14124,7 @@ var keywordLookup = map[string][]string{ "white_check_mark", }, "okay": []string{ - "pensive", + "ok_hand", }, "old": []string{ "older_man", @@ -13646,13 +14132,9 @@ var keywordLookup = map[string][]string{ "radio_button", }, "oldschool": []string{ - "feelsgood", - "finnadie", "floppy_disk", - "godmode", "hourglass", "hourglass_flowing_sand", - "hurtrealbad", "pager", "tv", "vhs", @@ -13767,6 +14249,7 @@ var keywordLookup = map[string][]string{ "palestinian_territories", }, "palm": []string{ + "coconut", "raised_hand", "raised_hand_with_fingers_splayed", "wave", @@ -13842,6 +14325,9 @@ var keywordLookup = map[string][]string{ "lock", "old_key", }, + "pastry": []string{ + "pie", + }, "paul": []string{ "alien", }, @@ -13909,6 +14395,8 @@ var keywordLookup = map[string][]string{ "ok_hand", }, "person": []string{ + "adult", + "bearded_person", "blonde_man", "blonde_woman", "bust_in_silhouette", @@ -13984,6 +14472,9 @@ var keywordLookup = map[string][]string{ "man_scientist", "woman_scientist", }, + "physics": []string{ + "atom_symbol", + }, "piano": []string{ "musical_keyboard", }, @@ -13993,6 +14484,9 @@ var keywordLookup = map[string][]string{ "picnic": []string{ "watermelon", }, + "pierogi": []string{ + "dumpling", + }, "pierre": []string{ "st_pierre_miquelon", }, @@ -14009,6 +14503,7 @@ var keywordLookup = map[string][]string{ "ping_pong", }, "pink": []string{ + "fish_cake", "heartbeat", "heartpulse", "ok_woman", @@ -14022,6 +14517,7 @@ var keywordLookup = map[string][]string{ }, "pirate": []string{ "black_flag", + "skull_and_crossbones", }, "pistol": []string{ "gun", @@ -14161,9 +14657,18 @@ var keywordLookup = map[string][]string{ "pork": []string{ "bacon", }, + "porkchop": []string{ + "steak", + }, + "porridge": []string{ + "bowl_with_spoon", + }, "postal": []string{ "email", }, + "potsticker": []string{ + "dumpling", + }, "potty": []string{ "toilet", }, @@ -14181,6 +14686,7 @@ var keywordLookup = map[string][]string{ "stuck_out_tongue_winking_eye", }, "prayer": []string{ + "palms_up", "place_of_worship", }, "prc": []string{ @@ -14225,6 +14731,9 @@ var keywordLookup = map[string][]string{ "problem": []string{ "warning", }, + "profanity": []string{ + "symbols_over_mouth", + }, "professor": []string{ "man_teacher", "woman_teacher", @@ -14244,6 +14753,9 @@ var keywordLookup = map[string][]string{ "prohibited": []string{ "no_bicycles", }, + "prophecy": []string{ + "fortune_cookie", + }, "propose": []string{ "ring", }, @@ -14330,6 +14842,7 @@ var keywordLookup = map[string][]string{ "mobile_phone_off", "mute", "no_bell", + "shushing", }, "quiz": []string{ "100", @@ -14348,9 +14861,6 @@ var keywordLookup = map[string][]string{ "radio": []string{ "satellite", }, - "rage": []string{ - "goberserk", - }, "rain": []string{ "closed_umbrella", }, @@ -14363,6 +14873,9 @@ var keywordLookup = map[string][]string{ "raised": []string{ "raised_back_of_hand", }, + "ramen": []string{ + "fish_cake", + }, "rancher": []string{ "man_farmer", "woman_farmer", @@ -14531,6 +15044,8 @@ var keywordLookup = map[string][]string{ "tiger2", }, "rock": []string{ + "climbing_man", + "climbing_woman", "moyai", }, "rock_on": []string{ @@ -14614,6 +15129,10 @@ var keywordLookup = map[string][]string{ "sweat", "weary", }, + "safari": []string{ + "giraffe", + "zebra", + }, "sahara": []string{ "western_sahara", }, @@ -14681,6 +15200,10 @@ var keywordLookup = map[string][]string{ "satisfied": []string{ "laughing", }, + "sauna": []string{ + "man_in_steamy_room", + "woman_in_steamy_room", + }, "save": []string{ "bookmark", "bookmark_tabs", @@ -14709,6 +15232,9 @@ var keywordLookup = map[string][]string{ "japanese_ogre", "skull_and_crossbones", }, + "scepticism": []string{ + "raised_eyebrow", + }, "schedule": []string{ "calendar", "clock1", @@ -14749,6 +15275,7 @@ var keywordLookup = map[string][]string{ "alembic", "atom_symbol", "microscope", + "telescope", }, "score": []string{ "100", @@ -14762,6 +15289,9 @@ var keywordLookup = map[string][]string{ "scotch": []string{ "tumbler_glass", }, + "scottish": []string{ + "scotland", + }, "scout": []string{ "fleur_de_lis", }, @@ -14834,9 +15364,14 @@ var keywordLookup = map[string][]string{ "eyes", }, "senior": []string{ + "older_adult", "older_man", "older_woman", }, + "serenity": []string{ + "man_in_lotus_position", + "woman_in_lotus_position", + }, "serious": []string{ "unamused", }, @@ -14881,12 +15416,12 @@ var keywordLookup = map[string][]string{ "shell": []string{ "snail", }, - "sherlock": []string{ - "shipit", - }, "shh": []string{ "hushed", }, + "shhh": []string{ + "shushing", + }, "shield": []string{ "beginner", }, @@ -14921,6 +15456,12 @@ var keywordLookup = map[string][]string{ "shitface": []string{ "poop", }, + "shock": []string{ + "hand_over_mouth", + }, + "shocked": []string{ + "exploding_head", + }, "shoes": []string{ "athletic_shoe", "boot", @@ -14974,6 +15515,10 @@ var keywordLookup = map[string][]string{ "persevere", "sneezing_face", "tired_face", + "vomiting", + }, + "side_eye": []string{ + "unamused", }, "sideways": []string{ "left_right_arrow", @@ -15046,6 +15591,9 @@ var keywordLookup = map[string][]string{ "skeleton": []string{ "skull", }, + "skeptical": []string{ + "unamused", + }, "sketch": []string{ "straight_ruler", "triangular_ruler", @@ -15088,10 +15636,16 @@ var keywordLookup = map[string][]string{ "weary", "zzz", }, + "sleigh": []string{ + "sled", + }, "slow": []string{ "snail", "turtle", }, + "smart": []string{ + "brain", + }, "smell": []string{ "no_smoking", "nose", @@ -15106,6 +15660,7 @@ var keywordLookup = map[string][]string{ "smiley", "smiley_cat", "smirk", + "star_struck", "stuck_out_tongue", "stuck_out_tongue_closed_eyes", "stuck_out_tongue_winking_eye", @@ -15139,6 +15694,9 @@ var keywordLookup = map[string][]string{ "lollipop", "popcorn", }, + "snare": []string{ + "drum", + }, "sneakers": []string{ "athletic_shoe", }, @@ -15155,6 +15713,9 @@ var keywordLookup = map[string][]string{ "so": []string{ "somalia", }, + "soda": []string{ + "cup_with_straw", + }, "software": []string{ "man_technologist", "woman_technologist", @@ -15165,6 +15726,9 @@ var keywordLookup = map[string][]string{ "solong": []string{ "wave", }, + "sorcerer": []string{ + "wizard", + }, "sorry": []string{ "broken_heart", }, @@ -15180,6 +15744,7 @@ var keywordLookup = map[string][]string{ "speaker", }, "soup": []string{ + "canned_food", "stew", }, "south": []string{ @@ -15197,6 +15762,10 @@ var keywordLookup = map[string][]string{ "southwest": []string{ "arrow_lower_left", }, + "spa": []string{ + "man_in_steamy_room", + "woman_in_steamy_room", + }, "space": []string{ "comet", "first_quarter_moon", @@ -15254,6 +15823,9 @@ var keywordLookup = map[string][]string{ "spin": []string{ "cyclone", }, + "spiny": []string{ + "hedgehog", + }, "spiral": []string{ "cyclone", }, @@ -15277,7 +15849,10 @@ var keywordLookup = map[string][]string{ "bow_and_arrow", "bowling", "boxing_glove", + "climbing_man", + "climbing_woman", "cricket", + "curling_stone", "field_hockey", "football", "goal_net", @@ -15317,6 +15892,9 @@ var keywordLookup = map[string][]string{ "woman_playing_water_polo", "women_wrestling", }, + "spots": []string{ + "giraffe", + }, "spring": []string{ "bird", "bouquet", @@ -15349,7 +15927,6 @@ var keywordLookup = map[string][]string{ "squirrel": []string{ "chestnut", "chipmunk", - "shipit", }, "sr": []string{ "suriname", @@ -15383,6 +15960,9 @@ var keywordLookup = map[string][]string{ "stare": []string{ "eye", }, + "starry": []string{ + "star_struck", + }, "stars": []string{ "milky_way", "sparkle", @@ -15425,6 +16005,10 @@ var keywordLookup = map[string][]string{ "chart_with_upwards_trend", "part_alternation_mark", }, + "steamroom": []string{ + "man_in_steamy_room", + "woman_in_steamy_room", + }, "steps": []string{ "walking_man", "walking_woman", @@ -15435,6 +16019,9 @@ var keywordLookup = map[string][]string{ "stiletto": []string{ "high_heel", }, + "stockings": []string{ + "socks", + }, "stone": []string{ "white_large_square", "white_medium_small_square", @@ -15449,6 +16036,9 @@ var keywordLookup = map[string][]string{ "straight face": []string{ "unamused", }, + "stripes": []string{ + "zebra", + }, "strong": []string{ "muscle", }, @@ -15471,6 +16061,9 @@ var keywordLookup = map[string][]string{ "stuffed": []string{ "stuffed_flatbread", }, + "stuffy": []string{ + "monocle", + }, "stunned": []string{ "anguished", }, @@ -15484,7 +16077,6 @@ var keywordLookup = map[string][]string{ "chart_with_upwards_trend", }, "suit": []string{ - "bowtie", "business_suit_levitating", }, "suits": []string{ @@ -15546,12 +16138,17 @@ var keywordLookup = map[string][]string{ "surgery": []string{ "hospital", }, + "surimi": []string{ + "fish_cake", + }, "surprise": []string{ "bangbang", "exclamation", "grey_exclamation", + "hand_over_mouth", "interrobang", "open_mouth", + "raised_eyebrow", }, "surprised": []string{ "astonished", @@ -15562,6 +16159,9 @@ var keywordLookup = map[string][]string{ "sustain": []string{ "battery", }, + "swearing": []string{ + "symbols_over_mouth", + }, "sweat": []string{ "cold_sweat", "disappointed_relieved", @@ -15584,6 +16184,7 @@ var keywordLookup = map[string][]string{ }, "swirl": []string{ "cyclone", + "fish_cake", }, "sword": []string{ "person_fencing", @@ -15635,6 +16236,9 @@ var keywordLookup = map[string][]string{ "film_projector", "loop", }, + "target": []string{ + "dart", + }, "td": []string{ "chad", }, @@ -15760,6 +16364,9 @@ var keywordLookup = map[string][]string{ "cloud_with_lightning", "zap", }, + "tichel": []string{ + "woman_with_headscarf", + }, "tick": []string{ "ballot_box_with_check", "heavy_check_mark", @@ -15830,6 +16437,7 @@ var keywordLookup = map[string][]string{ }, "toast": []string{ "bread", + "clinking_glasses", }, "tobacco": []string{ "smoking", @@ -15837,6 +16445,9 @@ var keywordLookup = map[string][]string{ "tobago": []string{ "trinidad_tobago", }, + "toboggan": []string{ + "sled", + }, "toddler": []string{ "baby", }, @@ -15876,6 +16487,9 @@ var keywordLookup = map[string][]string{ "arrow_up_small", "small_red_triangle", }, + "tornado": []string{ + "cyclone", + }, "tortoise": []string{ "turtle", }, @@ -15920,6 +16534,7 @@ var keywordLookup = map[string][]string{ "bus", "busstop", "fire_engine", + "flying_saucer", "helicopter", "light_rail", "metro", @@ -15977,6 +16592,9 @@ var keywordLookup = map[string][]string{ "tristan": []string{ "st_helena", }, + "triton": []string{ + "merman", + }, "trolley": []string{ "shopping_cart", }, @@ -16026,6 +16644,9 @@ var keywordLookup = map[string][]string{ "waxing_crescent_moon", "waxing_gibbous_moon", }, + "twisted": []string{ + "pretzel", + }, "twister": []string{ "tornado", }, @@ -16038,6 +16659,12 @@ var keywordLookup = map[string][]string{ "type": []string{ "keyboard", }, + "typhoon": []string{ + "cyclone", + }, + "tyrannosaurus": []string{ + "t-rex", + }, "ua": []string{ "ukraine", }, @@ -16045,6 +16672,9 @@ var keywordLookup = map[string][]string{ "oncoming_taxi", "taxi", }, + "ufo": []string{ + "flying_saucer", + }, "ug": []string{ "uganda", }, @@ -16056,6 +16686,10 @@ var keywordLookup = map[string][]string{ "unconscious": []string{ "dizzy_face", }, + "undead": []string{ + "man_zombie", + "woman_zombie", + }, "underground": []string{ "metro", }, @@ -16069,6 +16703,9 @@ var keywordLookup = map[string][]string{ "unicorn_face": []string{ "rainbow", }, + "unimpressed": []string{ + "unamused", + }, "union": []string{ "eu", }, @@ -16152,6 +16789,7 @@ var keywordLookup = map[string][]string{ "kissing_heart", "kissing_smiling_eyes", "love_letter", + "orange_heart", "purple_heart", "revolving_hearts", "ring", @@ -16181,6 +16819,7 @@ var keywordLookup = map[string][]string{ }, "vegetable": []string{ "bamboo", + "broccoli", "cactus", "carrot", "corn", @@ -16205,6 +16844,7 @@ var keywordLookup = map[string][]string{ "bullettrain_side", "bus", "fire_engine", + "flying_saucer", "helicopter", "kick_scooter", "light_rail", @@ -16307,6 +16947,10 @@ var keywordLookup = map[string][]string{ "running_man", "running_woman", }, + "walking dead": []string{ + "man_zombie", + "woman_zombie", + }, "wallis": []string{ "wallis_futuna", }, @@ -16361,6 +17005,9 @@ var keywordLookup = map[string][]string{ "restroom", "toilet", }, + "wealthy": []string{ + "monocle", + }, "weapon": []string{ "crossed_swords", "dagger", @@ -16412,6 +17059,9 @@ var keywordLookup = map[string][]string{ "alien", "confused", }, + "welsh": []string{ + "wales", + }, "western": []string{ "western_sahara", }, @@ -16433,6 +17083,9 @@ var keywordLookup = map[string][]string{ "whoa": []string{ "open_mouth", }, + "whoops": []string{ + "hand_over_mouth", + }, "wifi": []string{ "signal_strength", }, @@ -16449,6 +17102,7 @@ var keywordLookup = map[string][]string{ }, "wine": []string{ "champagne", + "clinking_glasses", "grapes", "sake", }, @@ -16464,7 +17118,9 @@ var keywordLookup = map[string][]string{ "medal_sports", }, "winter": []string{ + "gloves", "mountain_snow", + "scarf", "ski", "skier", "snowboarder", @@ -16482,6 +17138,9 @@ var keywordLookup = map[string][]string{ "wish": []string{ "pray", }, + "witch": []string{ + "sorceress", + }, "without_snow": []string{ "snowman", }, @@ -16492,6 +17151,7 @@ var keywordLookup = map[string][]string{ "blonde_woman", "bowing_woman", "bride_with_veil", + "climbing_woman", "construction_worker_woman", "dancer", "female_detective", @@ -16502,6 +17162,7 @@ var keywordLookup = map[string][]string{ "haircut_woman", "lipstick", "massage_woman", + "mermaid", "mountain_biking_woman", "mrs_claus", "no_good_woman", @@ -16512,6 +17173,7 @@ var keywordLookup = map[string][]string{ "raising_hand_woman", "rowing_woman", "running_woman", + "sorceress", "surfing_woman", "swimming_woman", "tipping_hand_woman", @@ -16520,11 +17182,16 @@ var keywordLookup = map[string][]string{ "woman_artist", "woman_astronaut", "woman_cook", + "woman_elf", "woman_facepalming", "woman_factory_worker", + "woman_fairy", "woman_farmer", "woman_firefighter", + "woman_genie", "woman_health_worker", + "woman_in_lotus_position", + "woman_in_steamy_room", "woman_judge", "woman_mechanic", "woman_office_worker", @@ -16535,7 +17202,9 @@ var keywordLookup = map[string][]string{ "woman_student", "woman_teacher", "woman_technologist", + "woman_vampire", "woman_with_turban", + "woman_zombie", "womens", }, "women": []string{ @@ -16600,6 +17269,10 @@ var keywordLookup = map[string][]string{ "grey_exclamation", "open_mouth", }, + "wrench": []string{ + "man_mechanic", + "woman_mechanic", + }, "wrestlers": []string{ "men_wrestling", "women_wrestling", @@ -16671,6 +17344,13 @@ var keywordLookup = map[string][]string{ "heavy_check_mark", "ok", }, + "yoga": []string{ + "man_in_lotus_position", + "woman_in_lotus_position", + }, + "young": []string{ + "child", + }, "yt": []string{ "mayotte", }, @@ -16680,6 +17360,10 @@ var keywordLookup = map[string][]string{ "zealand": []string{ "new_zealand", }, + "zen": []string{ + "man_in_lotus_position", + "woman_in_lotus_position", + }, "zipper": []string{ "zipper_mouth_face", }, @@ -16733,6 +17417,7 @@ var emojiReplacer = strings.NewReplacer( ":abc:", "🔤", ":abcd:", "🔡", ":accept:", "🉑", + ":adult:", "🧑", ":aerial_tramway:", "🚡", ":afghanistan:", "🇦🇫", ":airplane:", "✈️", @@ -16828,6 +17513,7 @@ var emojiReplacer = strings.NewReplacer( ":battery:", "🔋", ":beach_umbrella:", "🏖", ":bear:", "🐻", + ":bearded_person:", "🧔", ":bed:", "🛏", ":beer:", "🍺", ":beers:", "🍻", @@ -16846,6 +17532,7 @@ var emojiReplacer = strings.NewReplacer( ":biking_man:", "🚴", ":biking_woman:", "🚴‍♀️", ":bikini:", "👙", + ":billed_hat:", "🧢", ":biohazard:", "☣", ":bird:", "🐦", ":birthday:", "🎂", @@ -16881,17 +17568,20 @@ var emojiReplacer = strings.NewReplacer( ":bow_and_arrow:", "🏹", ":bowing_man:", "🙇", ":bowing_woman:", "🙇‍♀️", + ":bowl_with_spoon:", "🥣", ":bowling:", "🎳", - ":bowtie:", "", ":boxing_glove:", "🥊", ":boy:", "👦", + ":brain:", "🧠", ":brazil:", "🇧🇷", ":bread:", "🍞", + ":breastfeeding:", "🤱", ":bride_with_veil:", "👰", ":bridge_at_night:", "🌉", ":briefcase:", "💼", ":british_indian_ocean_territory:", "🇮🇴", ":british_virgin_islands:", "🇻🇬", + ":broccoli:", "🥦", ":broken_heart:", "💔", ":brunei:", "🇧🇳", ":bug:", "🐛", @@ -16925,6 +17615,7 @@ var emojiReplacer = strings.NewReplacer( ":cancer:", "♋", ":candle:", "🕯", ":candy:", "🍬", + ":canned_food:", "🥫", ":canoe:", "🛶", ":cape_verde:", "🇨🇻", ":capital_abcd:", "🔠", @@ -16952,10 +17643,12 @@ var emojiReplacer = strings.NewReplacer( ":cherry_blossom:", "🌸", ":chestnut:", "🌰", ":chicken:", "🐔", + ":child:", "🧒", ":children_crossing:", "🚸", ":chile:", "🇨🇱", ":chipmunk:", "🐿", ":chocolate_bar:", "🍫", + ":chopsticks:", "🥢", ":christmas_island:", "🇨🇽", ":christmas_tree:", "🎄", ":church:", "⛪", @@ -16969,6 +17662,8 @@ var emojiReplacer = strings.NewReplacer( ":clap:", "👏", ":clapper:", "🎬", ":classical_building:", "🏛", + ":climbing_man:", "🧗‍♂️", + ":climbing_woman:", "🧗‍♀️", ":clinking_glasses:", "🥂", ":clipboard:", "📋", ":clock1:", "🕐", @@ -17006,7 +17701,9 @@ var emojiReplacer = strings.NewReplacer( ":clown_face:", "🤡", ":clubs:", "♣️", ":cn:", "🇨🇳", + ":coat:", "🧥", ":cocktail:", "🍸", + ":coconut:", "🥥", ":cocos_islands:", "🇨🇨", ":coffee:", "☕", ":coffin:", "⚰", @@ -17062,8 +17759,10 @@ var emojiReplacer = strings.NewReplacer( ":crystal_ball:", "🔮", ":cuba:", "🇨🇺", ":cucumber:", "🥒", + ":cup_with_straw:", "🥤", ":cupid:", "💘", ":curacao:", "🇨🇼", + ":curling_stone:", "🥌", ":curly_loop:", "➰", ":currency_exchange:", "💱", ":curry:", "🍛", @@ -17116,6 +17815,7 @@ var emojiReplacer = strings.NewReplacer( ":droplet:", "💧", ":drum:", "🥁", ":duck:", "🦆", + ":dumpling:", "🥟", ":dvd:", "📀", ":e-mail:", "📧", ":eagle:", "🦅", @@ -17131,11 +17831,13 @@ var emojiReplacer = strings.NewReplacer( ":eight:", "8️⃣", ":eight_pointed_black_star:", "✴️", ":eight_spoked_asterisk:", "✳️", + ":eject_button:", "⏏️", ":el_salvador:", "🇸🇻", ":electric_plug:", "🔌", ":elephant:", "🐘", ":email:", "✉️", ":end:", "🔚", + ":england:", "🏴󠁧󠁢󠁥󠁮󠁧󠁿", ":envelope_with_arrow:", "📩", ":equatorial_guinea:", "🇬🇶", ":eritrea:", "🇪🇷", @@ -17148,6 +17850,7 @@ var emojiReplacer = strings.NewReplacer( ":european_post_office:", "🏤", ":evergreen_tree:", "🌲", ":exclamation:", "❗", + ":exploding_head:", "🤯", ":expressionless:", "😑", ":eye:", "👁", ":eyeglasses:", "👓", @@ -17187,7 +17890,6 @@ var emojiReplacer = strings.NewReplacer( ":fast_forward:", "⏩", ":fax:", "📠", ":fearful:", "😨", - ":feelsgood:", "", ":female_detective:", "🕵️‍♀️", ":ferris_wheel:", "🎡", ":ferry:", "⛴", @@ -17198,7 +17900,6 @@ var emojiReplacer = strings.NewReplacer( ":film_projector:", "📽", ":film_strip:", "🎞", ":finland:", "🇫🇮", - ":finnadie:", "", ":fire:", "🔥", ":fire_engine:", "🚒", ":fireworks:", "🎆", @@ -17219,11 +17920,13 @@ var emojiReplacer = strings.NewReplacer( ":floppy_disk:", "💾", ":flower_playing_cards:", "🎴", ":flushed:", "😳", + ":flying_saucer:", "🛸", ":fog:", "🌫", ":foggy:", "🌁", ":football:", "🏈", ":footprints:", "👣", ":fork_and_knife:", "🍴", + ":fortune_cookie:", "🥠", ":fountain:", "⛲", ":fountain_pen:", "🖋", ":four:", "4️⃣", @@ -17260,17 +17963,18 @@ var emojiReplacer = strings.NewReplacer( ":gibraltar:", "🇬🇮", ":gift:", "🎁", ":gift_heart:", "💝", + ":giraffe:", "🦒", ":girl:", "👧", ":globe_with_meridians:", "🌐", + ":gloves:", "🧤", ":goal_net:", "🥅", ":goat:", "🐐", - ":goberserk:", "", - ":godmode:", "", ":golf:", "⛳", ":golfing_man:", "🏌", ":golfing_woman:", "🏌️‍♀️", ":gorilla:", "🦍", ":grapes:", "🍇", + ":grasshopper:", "🦗", ":greece:", "🇬🇷", ":green_apple:", "🍏", ":green_book:", "📗", @@ -17302,6 +18006,7 @@ var emojiReplacer = strings.NewReplacer( ":hammer_and_pick:", "⚒", ":hammer_and_wrench:", "🛠", ":hamster:", "🐹", + ":hand_over_mouth:", "🤭", ":handbag:", "👜", ":handshake:", "🤝", ":hash:", "#️⃣", @@ -17323,6 +18028,7 @@ var emojiReplacer = strings.NewReplacer( ":heavy_minus_sign:", "➖", ":heavy_multiplication_x:", "✖️", ":heavy_plus_sign:", "➕", + ":hedgehog:", "🦔", ":helicopter:", "🚁", ":herb:", "🌿", ":hibiscus:", "🌺", @@ -17348,7 +18054,6 @@ var emojiReplacer = strings.NewReplacer( ":houses:", "🏘", ":hugs:", "🤗", ":hungary:", "🇭🇺", - ":hurtrealbad:", "", ":hushed:", "😯", ":ice_cream:", "🍨", ":ice_hockey:", "🏒", @@ -17449,6 +18154,7 @@ var emojiReplacer = strings.NewReplacer( ":loudspeaker:", "📢", ":love_hotel:", "🏩", ":love_letter:", "💌", + ":love_you:", "🤟", ":low_brightness:", "🔅", ":luxembourg:", "🇱🇺", ":lying_face:", "🤥", @@ -17475,11 +18181,16 @@ var emojiReplacer = strings.NewReplacer( ":man_cartwheeling:", "🤸‍♂️", ":man_cook:", "👨‍🍳", ":man_dancing:", "🕺", + ":man_elf:", "🧝‍♂️", ":man_facepalming:", "🤦", ":man_factory_worker:", "👨‍🏭", + ":man_fairy:", "🧚‍♂️", ":man_farmer:", "👨‍🌾", ":man_firefighter:", "👨‍🚒", + ":man_genie:", "🧞‍♂️", ":man_health_worker:", "👨‍⚕️", + ":man_in_lotus_position:", "🧘‍♂️", + ":man_in_steamy_room:", "🧖‍♂️", ":man_in_tuxedo:", "🤵", ":man_judge:", "👨‍⚖️", ":man_juggling:", "🤹‍♂️", @@ -17494,8 +18205,10 @@ var emojiReplacer = strings.NewReplacer( ":man_student:", "👨‍🎓", ":man_teacher:", "👨‍🏫", ":man_technologist:", "👨‍💻", + ":man_vampire:", "🧛‍♂️", ":man_with_gua_pi_mao:", "👲", ":man_with_turban:", "👳", + ":man_zombie:", "🧟‍♂️", ":mans_shoe:", "👞", ":mantelpiece_clock:", "🕰", ":maple_leaf:", "🍁", @@ -17517,6 +18230,8 @@ var emojiReplacer = strings.NewReplacer( ":men_wrestling:", "🤼‍♂️", ":menorah:", "🕎", ":mens:", "🚹", + ":mermaid:", "🧜‍♀️", + ":merman:", "🧜‍♂️", ":metal:", "🤘", ":metro:", "🚇", ":mexico:", "🇲🇽", @@ -17536,6 +18251,7 @@ var emojiReplacer = strings.NewReplacer( ":mongolia:", "🇲🇳", ":monkey:", "🐒", ":monkey_face:", "🐵", + ":monocle:", "🧐", ":monorail:", "🚝", ":montenegro:", "🇲🇪", ":montserrat:", "🇲🇸", @@ -17572,7 +18288,6 @@ var emojiReplacer = strings.NewReplacer( ":national_park:", "🏞", ":nauru:", "🇳🇷", ":nauseated_face:", "🤢", - ":neckbeard:", "", ":necktie:", "👔", ":negative_squared_cross_mark:", "❎", ":nepal:", "🇳🇵", @@ -17617,7 +18332,6 @@ var emojiReplacer = strings.NewReplacer( ":o:", "⭕", ":o2:", "🅾️", ":ocean:", "🌊", - ":octocat:", "", ":octopus:", "🐙", ":oden:", "🍢", ":office:", "🏢", @@ -17627,6 +18341,7 @@ var emojiReplacer = strings.NewReplacer( ":ok_man:", "🙆‍♂️", ":ok_woman:", "🙆", ":old_key:", "🗝", + ":older_adult:", "🧓", ":older_man:", "👴", ":older_woman:", "👵", ":om:", "🕉", @@ -17644,6 +18359,7 @@ var emojiReplacer = strings.NewReplacer( ":open_umbrella:", "☂", ":ophiuchus:", "⛎", ":orange_book:", "📙", + ":orange_heart:", "🧡", ":orthodox_cross:", "☦", ":outbox_tray:", "📤", ":owl:", "🦉", @@ -17657,6 +18373,7 @@ var emojiReplacer = strings.NewReplacer( ":palau:", "🇵🇼", ":palestinian_territories:", "🇵🇸", ":palm_tree:", "🌴", + ":palms_up:", "🤲", ":panama:", "🇵🇦", ":pancakes:", "🥞", ":panda_face:", "🐼", @@ -17687,6 +18404,7 @@ var emojiReplacer = strings.NewReplacer( ":philippines:", "🇵🇭", ":phone:", "☎️", ":pick:", "⛏", + ":pie:", "🥧", ":pig:", "🐷", ":pig2:", "🐖", ":pig_nose:", "🐽", @@ -17726,6 +18444,7 @@ var emojiReplacer = strings.NewReplacer( ":pray:", "🙏", ":prayer_beads:", "📿", ":pregnant_woman:", "🤰", + ":pretzel:", "🥨", ":previous_track_button:", "⏮", ":prince:", "🤴", ":princess:", "👸", @@ -17745,15 +18464,12 @@ var emojiReplacer = strings.NewReplacer( ":radio_button:", "🔘", ":radioactive:", "☢", ":rage:", "😡", - ":rage1:", "", - ":rage2:", "", - ":rage3:", "", - ":rage4:", "", ":railway_car:", "🚃", ":railway_track:", "🛤", ":rainbow:", "🌈", ":rainbow_flag:", "🏳️‍🌈", ":raised_back_of_hand:", "🤚", + ":raised_eyebrow:", "🤨", ":raised_hand:", "✋", ":raised_hand_with_fingers_splayed:", "🖐", ":raised_hands:", "🙌", @@ -17811,16 +18527,20 @@ var emojiReplacer = strings.NewReplacer( ":samoa:", "🇼🇸", ":san_marino:", "🇸🇲", ":sandal:", "👡", + ":sandwich:", "🥪", ":santa:", "🎅", ":sao_tome_principe:", "🇸🇹", ":satellite:", "📡", ":saudi_arabia:", "🇸🇦", + ":sauropod:", "🦕", ":saxophone:", "🎷", + ":scarf:", "🧣", ":school:", "🏫", ":school_satchel:", "🎒", ":scissors:", "✂️", ":scorpion:", "🦂", ":scorpius:", "♏", + ":scotland:", "🏴󠁧󠁢󠁳󠁣󠁴󠁿", ":scream:", "😱", ":scream_cat:", "🙀", ":scroll:", "📜", @@ -17842,11 +18562,11 @@ var emojiReplacer = strings.NewReplacer( ":shield:", "🛡", ":shinto_shrine:", "⛩", ":ship:", "🚢", - ":shipit:", "", ":shopping:", "🛍", ":shopping_cart:", "🛒", ":shower:", "🚿", ":shrimp:", "🦐", + ":shushing:", "🤫", ":sierra_leone:", "🇸🇱", ":signal_strength:", "📶", ":singapore:", "🇸🇬", @@ -17857,6 +18577,7 @@ var emojiReplacer = strings.NewReplacer( ":skier:", "⛷", ":skull:", "💀", ":skull_and_crossbones:", "☠", + ":sled:", "🛷", ":sleeping:", "😴", ":sleeping_bed:", "🛌", ":sleepy:", "😪", @@ -17887,9 +18608,11 @@ var emojiReplacer = strings.NewReplacer( ":snowman_with_snow:", "☃", ":sob:", "😭", ":soccer:", "⚽", + ":socks:", "🧦", ":solomon_islands:", "🇸🇧", ":somalia:", "🇸🇴", ":soon:", "🔜", + ":sorceress:", "🧙‍♀️", ":sos:", "🆘", ":sound:", "🔉", ":south_africa:", "🇿🇦", @@ -17925,9 +18648,11 @@ var emojiReplacer = strings.NewReplacer( ":star2:", "🌟", ":star_and_crescent:", "☪", ":star_of_david:", "✡", + ":star_struck:", "🤩", ":stars:", "🌠", ":station:", "🚉", ":statue_of_liberty:", "🗽", + ":steak:", "🥩", ":steam_locomotive:", "🚂", ":stew:", "🍲", ":stop_button:", "⏹", @@ -17954,7 +18679,6 @@ var emojiReplacer = strings.NewReplacer( ":surfing_woman:", "🏄‍♀️", ":suriname:", "🇸🇷", ":sushi:", "🍣", - ":suspect:", "", ":suspension_railway:", "🚟", ":swaziland:", "🇸🇿", ":sweat:", "😓", @@ -17966,13 +18690,16 @@ var emojiReplacer = strings.NewReplacer( ":swimming_woman:", "🏊‍♀️", ":switzerland:", "🇨🇭", ":symbols:", "🔣", + ":symbols_over_mouth:", "🤬", ":synagogue:", "🕍", ":syria:", "🇸🇾", ":syringe:", "💉", + ":t-rex:", "🦖", ":taco:", "🌮", ":tada:", "🎉", ":taiwan:", "🇹🇼", ":tajikistan:", "🇹🇯", + ":takeout_box:", "🥡", ":tanabata_tree:", "🎋", ":tangerine:", "🍊", ":tanzania:", "🇹🇿", @@ -18021,7 +18748,6 @@ var emojiReplacer = strings.NewReplacer( ":trinidad_tobago:", "🇹🇹", ":triumph:", "😤", ":trolleybus:", "🚎", - ":trollface:", "", ":trophy:", "🏆", ":tropical_drink:", "🍹", ":tropical_fish:", "🐠", @@ -18082,8 +18808,10 @@ var emojiReplacer = strings.NewReplacer( ":virgo:", "♍", ":volcano:", "🌋", ":volleyball:", "🏐", + ":vomiting:", "🤮", ":vs:", "🆚", ":vulcan_salute:", "🖖", + ":wales:", "🏴󠁧󠁢󠁷󠁬󠁳󠁿", ":walking_man:", "🚶", ":walking_woman:", "🚶‍♀️", ":wallis_futuna:", "🇼🇫", @@ -18122,17 +18850,23 @@ var emojiReplacer = strings.NewReplacer( ":wind_face:", "🌬", ":wine_glass:", "🍷", ":wink:", "😉", + ":wizard:", "🧙‍♂️", ":wolf:", "🐺", ":woman:", "👩", ":woman_artist:", "👩‍🎨", ":woman_astronaut:", "👩‍🚀", ":woman_cartwheeling:", "🤸‍♀️", ":woman_cook:", "👩‍🍳", + ":woman_elf:", "🧝‍♀️", ":woman_facepalming:", "🤦‍♀️", ":woman_factory_worker:", "👩‍🏭", + ":woman_fairy:", "🧚‍♀️", ":woman_farmer:", "👩‍🌾", ":woman_firefighter:", "👩‍🚒", + ":woman_genie:", "🧞‍♀️", ":woman_health_worker:", "👩‍⚕️", + ":woman_in_lotus_position:", "🧘‍♀️", + ":woman_in_steamy_room:", "🧖‍♀️", ":woman_judge:", "👩‍⚖️", ":woman_juggling:", "🤹‍♀️", ":woman_mechanic:", "👩‍🔧", @@ -18146,7 +18880,10 @@ var emojiReplacer = strings.NewReplacer( ":woman_student:", "👩‍🎓", ":woman_teacher:", "👩‍🏫", ":woman_technologist:", "👩‍💻", + ":woman_vampire:", "🧛‍♀️", + ":woman_with_headscarf:", "🧕", ":woman_with_turban:", "👳‍♀️", + ":woman_zombie:", "🧟‍♀️", ":womans_clothes:", "👚", ":womans_hat:", "👒", ":women_wrestling:", "🤼‍♀️", @@ -18162,7 +18899,9 @@ var emojiReplacer = strings.NewReplacer( ":yin_yang:", "☯", ":yum:", "😋", ":zambia:", "🇿🇲", + ":zany:", "🤪", ":zap:", "⚡", + ":zebra:", "🦓", ":zero:", "0️⃣", ":zimbabwe:", "🇿🇼", ":zipper_mouth_face:", "🤐", @@ -18183,6 +18922,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":abc:", "🔤 ", ":abcd:", "🔡 ", ":accept:", "🉑 ", + ":adult:", "🧑 ", ":aerial_tramway:", "🚡 ", ":afghanistan:", "🇦🇫 ", ":airplane:", "✈️ ", @@ -18278,6 +19018,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":battery:", "🔋 ", ":beach_umbrella:", "🏖 ", ":bear:", "🐻 ", + ":bearded_person:", "🧔 ", ":bed:", "🛏 ", ":beer:", "🍺 ", ":beers:", "🍻 ", @@ -18296,6 +19037,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":biking_man:", "🚴 ", ":biking_woman:", "🚴‍♀️ ", ":bikini:", "👙 ", + ":billed_hat:", "🧢 ", ":biohazard:", "☣ ", ":bird:", "🐦 ", ":birthday:", "🎂 ", @@ -18331,17 +19073,20 @@ var emojiPaddedReplacer = strings.NewReplacer( ":bow_and_arrow:", "🏹 ", ":bowing_man:", "🙇 ", ":bowing_woman:", "🙇‍♀️ ", + ":bowl_with_spoon:", "🥣 ", ":bowling:", "🎳 ", - ":bowtie:", " ", ":boxing_glove:", "🥊 ", ":boy:", "👦 ", + ":brain:", "🧠 ", ":brazil:", "🇧🇷 ", ":bread:", "🍞 ", + ":breastfeeding:", "🤱 ", ":bride_with_veil:", "👰 ", ":bridge_at_night:", "🌉 ", ":briefcase:", "💼 ", ":british_indian_ocean_territory:", "🇮🇴 ", ":british_virgin_islands:", "🇻🇬 ", + ":broccoli:", "🥦 ", ":broken_heart:", "💔 ", ":brunei:", "🇧🇳 ", ":bug:", "🐛 ", @@ -18375,6 +19120,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":cancer:", "♋ ", ":candle:", "🕯 ", ":candy:", "🍬 ", + ":canned_food:", "🥫 ", ":canoe:", "🛶 ", ":cape_verde:", "🇨🇻 ", ":capital_abcd:", "🔠 ", @@ -18402,10 +19148,12 @@ var emojiPaddedReplacer = strings.NewReplacer( ":cherry_blossom:", "🌸 ", ":chestnut:", "🌰 ", ":chicken:", "🐔 ", + ":child:", "🧒 ", ":children_crossing:", "🚸 ", ":chile:", "🇨🇱 ", ":chipmunk:", "🐿 ", ":chocolate_bar:", "🍫 ", + ":chopsticks:", "🥢 ", ":christmas_island:", "🇨🇽 ", ":christmas_tree:", "🎄 ", ":church:", "⛪ ", @@ -18419,6 +19167,8 @@ var emojiPaddedReplacer = strings.NewReplacer( ":clap:", "👏 ", ":clapper:", "🎬 ", ":classical_building:", "🏛 ", + ":climbing_man:", "🧗‍♂️ ", + ":climbing_woman:", "🧗‍♀️ ", ":clinking_glasses:", "🥂 ", ":clipboard:", "📋 ", ":clock1:", "🕐 ", @@ -18456,7 +19206,9 @@ var emojiPaddedReplacer = strings.NewReplacer( ":clown_face:", "🤡 ", ":clubs:", "♣️ ", ":cn:", "🇨🇳 ", + ":coat:", "🧥 ", ":cocktail:", "🍸 ", + ":coconut:", "🥥 ", ":cocos_islands:", "🇨🇨 ", ":coffee:", "☕ ", ":coffin:", "⚰ ", @@ -18512,8 +19264,10 @@ var emojiPaddedReplacer = strings.NewReplacer( ":crystal_ball:", "🔮 ", ":cuba:", "🇨🇺 ", ":cucumber:", "🥒 ", + ":cup_with_straw:", "🥤 ", ":cupid:", "💘 ", ":curacao:", "🇨🇼 ", + ":curling_stone:", "🥌 ", ":curly_loop:", "➰ ", ":currency_exchange:", "💱 ", ":curry:", "🍛 ", @@ -18566,6 +19320,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":droplet:", "💧 ", ":drum:", "🥁 ", ":duck:", "🦆 ", + ":dumpling:", "🥟 ", ":dvd:", "📀 ", ":e-mail:", "📧 ", ":eagle:", "🦅 ", @@ -18581,11 +19336,13 @@ var emojiPaddedReplacer = strings.NewReplacer( ":eight:", "8️⃣ ", ":eight_pointed_black_star:", "✴️ ", ":eight_spoked_asterisk:", "✳️ ", + ":eject_button:", "⏏️ ", ":el_salvador:", "🇸🇻 ", ":electric_plug:", "🔌 ", ":elephant:", "🐘 ", ":email:", "✉️ ", ":end:", "🔚 ", + ":england:", "🏴󠁧󠁢󠁥󠁮󠁧󠁿 ", ":envelope_with_arrow:", "📩 ", ":equatorial_guinea:", "🇬🇶 ", ":eritrea:", "🇪🇷 ", @@ -18598,6 +19355,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":european_post_office:", "🏤 ", ":evergreen_tree:", "🌲 ", ":exclamation:", "❗ ", + ":exploding_head:", "🤯 ", ":expressionless:", "😑 ", ":eye:", "👁 ", ":eyeglasses:", "👓 ", @@ -18637,7 +19395,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":fast_forward:", "⏩ ", ":fax:", "📠 ", ":fearful:", "😨 ", - ":feelsgood:", " ", ":female_detective:", "🕵️‍♀️ ", ":ferris_wheel:", "🎡 ", ":ferry:", "⛴ ", @@ -18648,7 +19405,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":film_projector:", "📽 ", ":film_strip:", "🎞 ", ":finland:", "🇫🇮 ", - ":finnadie:", " ", ":fire:", "🔥 ", ":fire_engine:", "🚒 ", ":fireworks:", "🎆 ", @@ -18669,11 +19425,13 @@ var emojiPaddedReplacer = strings.NewReplacer( ":floppy_disk:", "💾 ", ":flower_playing_cards:", "🎴 ", ":flushed:", "😳 ", + ":flying_saucer:", "🛸 ", ":fog:", "🌫 ", ":foggy:", "🌁 ", ":football:", "🏈 ", ":footprints:", "👣 ", ":fork_and_knife:", "🍴 ", + ":fortune_cookie:", "🥠 ", ":fountain:", "⛲ ", ":fountain_pen:", "🖋 ", ":four:", "4️⃣ ", @@ -18710,17 +19468,18 @@ var emojiPaddedReplacer = strings.NewReplacer( ":gibraltar:", "🇬🇮 ", ":gift:", "🎁 ", ":gift_heart:", "💝 ", + ":giraffe:", "🦒 ", ":girl:", "👧 ", ":globe_with_meridians:", "🌐 ", + ":gloves:", "🧤 ", ":goal_net:", "🥅 ", ":goat:", "🐐 ", - ":goberserk:", " ", - ":godmode:", " ", ":golf:", "⛳ ", ":golfing_man:", "🏌 ", ":golfing_woman:", "🏌️‍♀️ ", ":gorilla:", "🦍 ", ":grapes:", "🍇 ", + ":grasshopper:", "🦗 ", ":greece:", "🇬🇷 ", ":green_apple:", "🍏 ", ":green_book:", "📗 ", @@ -18752,6 +19511,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":hammer_and_pick:", "⚒ ", ":hammer_and_wrench:", "🛠 ", ":hamster:", "🐹 ", + ":hand_over_mouth:", "🤭 ", ":handbag:", "👜 ", ":handshake:", "🤝 ", ":hash:", "#️⃣ ", @@ -18773,6 +19533,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":heavy_minus_sign:", "➖ ", ":heavy_multiplication_x:", "✖️ ", ":heavy_plus_sign:", "➕ ", + ":hedgehog:", "🦔 ", ":helicopter:", "🚁 ", ":herb:", "🌿 ", ":hibiscus:", "🌺 ", @@ -18798,7 +19559,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":houses:", "🏘 ", ":hugs:", "🤗 ", ":hungary:", "🇭🇺 ", - ":hurtrealbad:", " ", ":hushed:", "😯 ", ":ice_cream:", "🍨 ", ":ice_hockey:", "🏒 ", @@ -18899,6 +19659,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":loudspeaker:", "📢 ", ":love_hotel:", "🏩 ", ":love_letter:", "💌 ", + ":love_you:", "🤟 ", ":low_brightness:", "🔅 ", ":luxembourg:", "🇱🇺 ", ":lying_face:", "🤥 ", @@ -18925,11 +19686,16 @@ var emojiPaddedReplacer = strings.NewReplacer( ":man_cartwheeling:", "🤸‍♂️ ", ":man_cook:", "👨‍🍳 ", ":man_dancing:", "🕺 ", + ":man_elf:", "🧝‍♂️ ", ":man_facepalming:", "🤦 ", ":man_factory_worker:", "👨‍🏭 ", + ":man_fairy:", "🧚‍♂️ ", ":man_farmer:", "👨‍🌾 ", ":man_firefighter:", "👨‍🚒 ", + ":man_genie:", "🧞‍♂️ ", ":man_health_worker:", "👨‍⚕️ ", + ":man_in_lotus_position:", "🧘‍♂️ ", + ":man_in_steamy_room:", "🧖‍♂️ ", ":man_in_tuxedo:", "🤵 ", ":man_judge:", "👨‍⚖️ ", ":man_juggling:", "🤹‍♂️ ", @@ -18944,8 +19710,10 @@ var emojiPaddedReplacer = strings.NewReplacer( ":man_student:", "👨‍🎓 ", ":man_teacher:", "👨‍🏫 ", ":man_technologist:", "👨‍💻 ", + ":man_vampire:", "🧛‍♂️ ", ":man_with_gua_pi_mao:", "👲 ", ":man_with_turban:", "👳 ", + ":man_zombie:", "🧟‍♂️ ", ":mans_shoe:", "👞 ", ":mantelpiece_clock:", "🕰 ", ":maple_leaf:", "🍁 ", @@ -18967,6 +19735,8 @@ var emojiPaddedReplacer = strings.NewReplacer( ":men_wrestling:", "🤼‍♂️ ", ":menorah:", "🕎 ", ":mens:", "🚹 ", + ":mermaid:", "🧜‍♀️ ", + ":merman:", "🧜‍♂️ ", ":metal:", "🤘 ", ":metro:", "🚇 ", ":mexico:", "🇲🇽 ", @@ -18986,6 +19756,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":mongolia:", "🇲🇳 ", ":monkey:", "🐒 ", ":monkey_face:", "🐵 ", + ":monocle:", "🧐 ", ":monorail:", "🚝 ", ":montenegro:", "🇲🇪 ", ":montserrat:", "🇲🇸 ", @@ -19022,7 +19793,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":national_park:", "🏞 ", ":nauru:", "🇳🇷 ", ":nauseated_face:", "🤢 ", - ":neckbeard:", " ", ":necktie:", "👔 ", ":negative_squared_cross_mark:", "❎ ", ":nepal:", "🇳🇵 ", @@ -19067,7 +19837,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":o:", "⭕ ", ":o2:", "🅾️ ", ":ocean:", "🌊 ", - ":octocat:", " ", ":octopus:", "🐙 ", ":oden:", "🍢 ", ":office:", "🏢 ", @@ -19077,6 +19846,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":ok_man:", "🙆‍♂️ ", ":ok_woman:", "🙆 ", ":old_key:", "🗝 ", + ":older_adult:", "🧓 ", ":older_man:", "👴 ", ":older_woman:", "👵 ", ":om:", "🕉 ", @@ -19094,6 +19864,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":open_umbrella:", "☂ ", ":ophiuchus:", "⛎ ", ":orange_book:", "📙 ", + ":orange_heart:", "🧡 ", ":orthodox_cross:", "☦ ", ":outbox_tray:", "📤 ", ":owl:", "🦉 ", @@ -19107,6 +19878,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":palau:", "🇵🇼 ", ":palestinian_territories:", "🇵🇸 ", ":palm_tree:", "🌴 ", + ":palms_up:", "🤲 ", ":panama:", "🇵🇦 ", ":pancakes:", "🥞 ", ":panda_face:", "🐼 ", @@ -19137,6 +19909,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":philippines:", "🇵🇭 ", ":phone:", "☎️ ", ":pick:", "⛏ ", + ":pie:", "🥧 ", ":pig:", "🐷 ", ":pig2:", "🐖 ", ":pig_nose:", "🐽 ", @@ -19176,6 +19949,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":pray:", "🙏 ", ":prayer_beads:", "📿 ", ":pregnant_woman:", "🤰 ", + ":pretzel:", "🥨 ", ":previous_track_button:", "⏮ ", ":prince:", "🤴 ", ":princess:", "👸 ", @@ -19195,15 +19969,12 @@ var emojiPaddedReplacer = strings.NewReplacer( ":radio_button:", "🔘 ", ":radioactive:", "☢ ", ":rage:", "😡 ", - ":rage1:", " ", - ":rage2:", " ", - ":rage3:", " ", - ":rage4:", " ", ":railway_car:", "🚃 ", ":railway_track:", "🛤 ", ":rainbow:", "🌈 ", ":rainbow_flag:", "🏳️‍🌈 ", ":raised_back_of_hand:", "🤚 ", + ":raised_eyebrow:", "🤨 ", ":raised_hand:", "✋ ", ":raised_hand_with_fingers_splayed:", "🖐 ", ":raised_hands:", "🙌 ", @@ -19261,16 +20032,20 @@ var emojiPaddedReplacer = strings.NewReplacer( ":samoa:", "🇼🇸 ", ":san_marino:", "🇸🇲 ", ":sandal:", "👡 ", + ":sandwich:", "🥪 ", ":santa:", "🎅 ", ":sao_tome_principe:", "🇸🇹 ", ":satellite:", "📡 ", ":saudi_arabia:", "🇸🇦 ", + ":sauropod:", "🦕 ", ":saxophone:", "🎷 ", + ":scarf:", "🧣 ", ":school:", "🏫 ", ":school_satchel:", "🎒 ", ":scissors:", "✂️ ", ":scorpion:", "🦂 ", ":scorpius:", "♏ ", + ":scotland:", "🏴󠁧󠁢󠁳󠁣󠁴󠁿 ", ":scream:", "😱 ", ":scream_cat:", "🙀 ", ":scroll:", "📜 ", @@ -19292,11 +20067,11 @@ var emojiPaddedReplacer = strings.NewReplacer( ":shield:", "🛡 ", ":shinto_shrine:", "⛩ ", ":ship:", "🚢 ", - ":shipit:", " ", ":shopping:", "🛍 ", ":shopping_cart:", "🛒 ", ":shower:", "🚿 ", ":shrimp:", "🦐 ", + ":shushing:", "🤫 ", ":sierra_leone:", "🇸🇱 ", ":signal_strength:", "📶 ", ":singapore:", "🇸🇬 ", @@ -19307,6 +20082,7 @@ var emojiPaddedReplacer = strings.NewReplacer( ":skier:", "⛷ ", ":skull:", "💀 ", ":skull_and_crossbones:", "☠ ", + ":sled:", "🛷 ", ":sleeping:", "😴 ", ":sleeping_bed:", "🛌 ", ":sleepy:", "😪 ", @@ -19337,9 +20113,11 @@ var emojiPaddedReplacer = strings.NewReplacer( ":snowman_with_snow:", "☃ ", ":sob:", "😭 ", ":soccer:", "⚽ ", + ":socks:", "🧦 ", ":solomon_islands:", "🇸🇧 ", ":somalia:", "🇸🇴 ", ":soon:", "🔜 ", + ":sorceress:", "🧙‍♀️ ", ":sos:", "🆘 ", ":sound:", "🔉 ", ":south_africa:", "🇿🇦 ", @@ -19375,9 +20153,11 @@ var emojiPaddedReplacer = strings.NewReplacer( ":star2:", "🌟 ", ":star_and_crescent:", "☪ ", ":star_of_david:", "✡ ", + ":star_struck:", "🤩 ", ":stars:", "🌠 ", ":station:", "🚉 ", ":statue_of_liberty:", "🗽 ", + ":steak:", "🥩 ", ":steam_locomotive:", "🚂 ", ":stew:", "🍲 ", ":stop_button:", "⏹ ", @@ -19404,7 +20184,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":surfing_woman:", "🏄‍♀️ ", ":suriname:", "🇸🇷 ", ":sushi:", "🍣 ", - ":suspect:", " ", ":suspension_railway:", "🚟 ", ":swaziland:", "🇸🇿 ", ":sweat:", "😓 ", @@ -19416,13 +20195,16 @@ var emojiPaddedReplacer = strings.NewReplacer( ":swimming_woman:", "🏊‍♀️ ", ":switzerland:", "🇨🇭 ", ":symbols:", "🔣 ", + ":symbols_over_mouth:", "🤬 ", ":synagogue:", "🕍 ", ":syria:", "🇸🇾 ", ":syringe:", "💉 ", + ":t-rex:", "🦖 ", ":taco:", "🌮 ", ":tada:", "🎉 ", ":taiwan:", "🇹🇼 ", ":tajikistan:", "🇹🇯 ", + ":takeout_box:", "🥡 ", ":tanabata_tree:", "🎋 ", ":tangerine:", "🍊 ", ":tanzania:", "🇹🇿 ", @@ -19471,7 +20253,6 @@ var emojiPaddedReplacer = strings.NewReplacer( ":trinidad_tobago:", "🇹🇹 ", ":triumph:", "😤 ", ":trolleybus:", "🚎 ", - ":trollface:", " ", ":trophy:", "🏆 ", ":tropical_drink:", "🍹 ", ":tropical_fish:", "🐠 ", @@ -19532,8 +20313,10 @@ var emojiPaddedReplacer = strings.NewReplacer( ":virgo:", "♍ ", ":volcano:", "🌋 ", ":volleyball:", "🏐 ", + ":vomiting:", "🤮 ", ":vs:", "🆚 ", ":vulcan_salute:", "🖖 ", + ":wales:", "🏴󠁧󠁢󠁷󠁬󠁳󠁿 ", ":walking_man:", "🚶 ", ":walking_woman:", "🚶‍♀️ ", ":wallis_futuna:", "🇼🇫 ", @@ -19572,17 +20355,23 @@ var emojiPaddedReplacer = strings.NewReplacer( ":wind_face:", "🌬 ", ":wine_glass:", "🍷 ", ":wink:", "😉 ", + ":wizard:", "🧙‍♂️ ", ":wolf:", "🐺 ", ":woman:", "👩 ", ":woman_artist:", "👩‍🎨 ", ":woman_astronaut:", "👩‍🚀 ", ":woman_cartwheeling:", "🤸‍♀️ ", ":woman_cook:", "👩‍🍳 ", + ":woman_elf:", "🧝‍♀️ ", ":woman_facepalming:", "🤦‍♀️ ", ":woman_factory_worker:", "👩‍🏭 ", + ":woman_fairy:", "🧚‍♀️ ", ":woman_farmer:", "👩‍🌾 ", ":woman_firefighter:", "👩‍🚒 ", + ":woman_genie:", "🧞‍♀️ ", ":woman_health_worker:", "👩‍⚕️ ", + ":woman_in_lotus_position:", "🧘‍♀️ ", + ":woman_in_steamy_room:", "🧖‍♀️ ", ":woman_judge:", "👩‍⚖️ ", ":woman_juggling:", "🤹‍♀️ ", ":woman_mechanic:", "👩‍🔧 ", @@ -19596,7 +20385,10 @@ var emojiPaddedReplacer = strings.NewReplacer( ":woman_student:", "👩‍🎓 ", ":woman_teacher:", "👩‍🏫 ", ":woman_technologist:", "👩‍💻 ", + ":woman_vampire:", "🧛‍♀️ ", + ":woman_with_headscarf:", "🧕 ", ":woman_with_turban:", "👳‍♀️ ", + ":woman_zombie:", "🧟‍♀️ ", ":womans_clothes:", "👚 ", ":womans_hat:", "👒 ", ":women_wrestling:", "🤼‍♀️ ", @@ -19612,7 +20404,9 @@ var emojiPaddedReplacer = strings.NewReplacer( ":yin_yang:", "☯ ", ":yum:", "😋 ", ":zambia:", "🇿🇲 ", + ":zany:", "🤪 ", ":zap:", "⚡ ", + ":zebra:", "🦓 ", ":zero:", "0️⃣ ", ":zimbabwe:", "🇿🇼 ", ":zipper_mouth_face:", "🤐 ", diff --git a/vendor/github.com/rs/xid/hostid_darwin.go b/vendor/github.com/rs/xid/hostid_darwin.go index abd06840..08351ff7 100644 --- a/vendor/github.com/rs/xid/hostid_darwin.go +++ b/vendor/github.com/rs/xid/hostid_darwin.go @@ -2,8 +2,8 @@ package xid -import "golang.org/x/sys/unix" +import "syscall" func readPlatformMachineID() (string, error) { - return unix.Sysctl("kern.uuid") + return syscall.Sysctl("kern.uuid") } diff --git a/vendor/github.com/rs/xid/hostid_freebsd.go b/vendor/github.com/rs/xid/hostid_freebsd.go index df2bd13c..be25a039 100644 --- a/vendor/github.com/rs/xid/hostid_freebsd.go +++ b/vendor/github.com/rs/xid/hostid_freebsd.go @@ -2,8 +2,8 @@ package xid -import "golang.org/x/sys/unix" +import "syscall" func readPlatformMachineID() (string, error) { - return unix.Sysctl("kern.hostuuid") + return syscall.Sysctl("kern.hostuuid") } diff --git a/vendor/github.com/rs/xid/hostid_windows.go b/vendor/github.com/rs/xid/hostid_windows.go index b8e1c2cb..ec2593ee 100644 --- a/vendor/github.com/rs/xid/hostid_windows.go +++ b/vendor/github.com/rs/xid/hostid_windows.go @@ -4,32 +4,31 @@ package xid import ( "fmt" + "syscall" "unsafe" - - "golang.org/x/sys/windows" ) func readPlatformMachineID() (string, error) { - // source: https://github.com/shirou/gopsutil/blob/master/host/host_windows.go - var h windows.Handle - err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h) + // source: https://github.com/shirou/gopsutil/blob/master/host/host_syscall.go + var h syscall.Handle + err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, syscall.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h) if err != nil { return "", err } - defer windows.RegCloseKey(h) + defer syscall.RegCloseKey(h) - const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 + const syscallRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 const uuidLen = 36 - var regBuf [windowsRegBufLen]uint16 - bufLen := uint32(windowsRegBufLen) + var regBuf [syscallRegBufLen]uint16 + bufLen := uint32(syscallRegBufLen) var valType uint32 - err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) + err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) if err != nil { return "", err } - hostID := windows.UTF16ToString(regBuf[:]) + hostID := syscall.UTF16ToString(regBuf[:]) hostIDLen := len(hostID) if hostIDLen != uuidLen { return "", fmt.Errorf("HostID incorrect: %q\n", hostID) diff --git a/vendor/github.com/rs/xid/id.go b/vendor/github.com/rs/xid/id.go index 51b8d7fa..466faf26 100644 --- a/vendor/github.com/rs/xid/id.go +++ b/vendor/github.com/rs/xid/id.go @@ -42,6 +42,7 @@ package xid import ( + "bytes" "crypto/md5" "crypto/rand" "database/sql/driver" @@ -51,10 +52,9 @@ import ( "hash/crc32" "io/ioutil" "os" + "sort" "sync/atomic" "time" - "bytes" - "sort" ) // Code inspired from mgo/bson ObjectId @@ -143,9 +143,14 @@ func randInt() uint32 { // New generates a globally unique ID func New() ID { + return NewWithTime(time.Now()) +} + +// NewWithTime generates a globally unique ID with the passed in time +func NewWithTime(t time.Time) ID { var id ID // Timestamp, 4 bytes, big endian - binary.BigEndian.PutUint32(id[:], uint32(time.Now().Unix())) + binary.BigEndian.PutUint32(id[:], uint32(t.Unix())) // Machine, first 3 bytes of md5(hostname) id[4] = machineID[0] id[5] = machineID[1] @@ -339,7 +344,6 @@ func (id ID) Compare(other ID) int { return bytes.Compare(id[:], other[:]) } - type sorter []ID func (s sorter) Len() int { diff --git a/vendor/github.com/spf13/afero/.travis.yml b/vendor/github.com/spf13/afero/.travis.yml index e002181f..0637db72 100644 --- a/vendor/github.com/spf13/afero/.travis.yml +++ b/vendor/github.com/spf13/afero/.travis.yml @@ -2,8 +2,8 @@ sudo: false language: go go: - - 1.8 - 1.9 + - "1.10" - tip os: diff --git a/vendor/github.com/spf13/afero/basepath.go b/vendor/github.com/spf13/afero/basepath.go index 5e4fc2ec..616ff8ff 100644 --- a/vendor/github.com/spf13/afero/basepath.go +++ b/vendor/github.com/spf13/afero/basepath.go @@ -1,7 +1,6 @@ package afero import ( - "errors" "os" "path/filepath" "runtime" @@ -9,6 +8,8 @@ import ( "time" ) +var _ Lstater = (*BasePathFs)(nil) + // The BasePathFs restricts all operations to a given path within an Fs. // The given file name to the operations on this Fs will be prepended with // the base path before calling the base Fs. @@ -22,6 +23,16 @@ type BasePathFs struct { path string } +type BasePathFile struct { + File + path string +} + +func (f *BasePathFile) Name() string { + sourcename := f.File.Name() + return strings.TrimPrefix(sourcename, filepath.Clean(f.path)) +} + func NewBasePathFs(source Fs, path string) Fs { return &BasePathFs{source: source, path: path} } @@ -30,7 +41,7 @@ func NewBasePathFs(source Fs, path string) Fs { // else the given file with the base path prepended func (b *BasePathFs) RealPath(name string) (path string, err error) { if err := validateBasePathName(name); err != nil { - return "", err + return name, err } bpath := filepath.Clean(b.path) @@ -52,7 +63,7 @@ func validateBasePathName(name string) error { // On Windows a common mistake would be to provide an absolute OS path // We could strip out the base part, but that would not be very portable. if filepath.IsAbs(name) { - return &os.PathError{Op: "realPath", Path: name, Err: errors.New("got a real OS path instead of a virtual")} + return os.ErrNotExist } return nil @@ -111,14 +122,22 @@ func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, if name, err = b.RealPath(name); err != nil { return nil, &os.PathError{Op: "openfile", Path: name, Err: err} } - return b.source.OpenFile(name, flag, mode) + sourcef, err := b.source.OpenFile(name, flag, mode) + if err != nil { + return nil, err + } + return &BasePathFile{sourcef, b.path}, nil } func (b *BasePathFs) Open(name string) (f File, err error) { if name, err = b.RealPath(name); err != nil { return nil, &os.PathError{Op: "open", Path: name, Err: err} } - return b.source.Open(name) + sourcef, err := b.source.Open(name) + if err != nil { + return nil, err + } + return &BasePathFile{File: sourcef, path: b.path}, nil } func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) { @@ -139,7 +158,23 @@ func (b *BasePathFs) Create(name string) (f File, err error) { if name, err = b.RealPath(name); err != nil { return nil, &os.PathError{Op: "create", Path: name, Err: err} } - return b.source.Create(name) + sourcef, err := b.source.Create(name) + if err != nil { + return nil, err + } + return &BasePathFile{File: sourcef, path: b.path}, nil +} + +func (b *BasePathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + name, err := b.RealPath(name) + if err != nil { + return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err} + } + if lstater, ok := b.source.(Lstater); ok { + return lstater.LstatIfPossible(name) + } + fi, err := b.source.Stat(name) + return fi, false, err } // vim: ts=4 sw=4 noexpandtab nolist syn=go diff --git a/vendor/github.com/spf13/afero/cacheOnReadFs.go b/vendor/github.com/spf13/afero/cacheOnReadFs.go index b026e0de..29a26c67 100644 --- a/vendor/github.com/spf13/afero/cacheOnReadFs.go +++ b/vendor/github.com/spf13/afero/cacheOnReadFs.go @@ -205,7 +205,7 @@ func (u *CacheOnReadFs) OpenFile(name string, flag int, perm os.FileMode) (File, bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...? return nil, err } - return &UnionFile{base: bfi, layer: lfi}, nil + return &UnionFile{Base: bfi, Layer: lfi}, nil } return u.layer.OpenFile(name, flag, perm) } @@ -251,7 +251,7 @@ func (u *CacheOnReadFs) Open(name string) (File, error) { if err != nil && bfile == nil { return nil, err } - return &UnionFile{base: bfile, layer: lfile}, nil + return &UnionFile{Base: bfile, Layer: lfile}, nil } func (u *CacheOnReadFs) Mkdir(name string, perm os.FileMode) error { @@ -286,5 +286,5 @@ func (u *CacheOnReadFs) Create(name string) (File, error) { bfh.Close() return nil, err } - return &UnionFile{base: bfh, layer: lfh}, nil + return &UnionFile{Base: bfh, Layer: lfh}, nil } diff --git a/vendor/github.com/spf13/afero/copyOnWriteFs.go b/vendor/github.com/spf13/afero/copyOnWriteFs.go index f2ebcd22..9aef3979 100644 --- a/vendor/github.com/spf13/afero/copyOnWriteFs.go +++ b/vendor/github.com/spf13/afero/copyOnWriteFs.go @@ -8,6 +8,8 @@ import ( "time" ) +var _ Lstater = (*CopyOnWriteFs)(nil) + // The CopyOnWriteFs is a union filesystem: a read only base file system with // a possibly writeable layer on top. Changes to the file system will only // be made in the overlay: Changing an existing file in the base layer which @@ -76,18 +78,55 @@ func (u *CopyOnWriteFs) Chmod(name string, mode os.FileMode) error { func (u *CopyOnWriteFs) Stat(name string) (os.FileInfo, error) { fi, err := u.layer.Stat(name) if err != nil { - origErr := err - if e, ok := err.(*os.PathError); ok { - err = e.Err - } - if err == os.ErrNotExist || err == syscall.ENOENT || err == syscall.ENOTDIR { + isNotExist := u.isNotExist(err) + if isNotExist { return u.base.Stat(name) } - return nil, origErr + return nil, err } return fi, nil } +func (u *CopyOnWriteFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + llayer, ok1 := u.layer.(Lstater) + lbase, ok2 := u.base.(Lstater) + + if ok1 { + fi, b, err := llayer.LstatIfPossible(name) + if err == nil { + return fi, b, nil + } + + if !u.isNotExist(err) { + return nil, b, err + } + } + + if ok2 { + fi, b, err := lbase.LstatIfPossible(name) + if err == nil { + return fi, b, nil + } + if !u.isNotExist(err) { + return nil, b, err + } + } + + fi, err := u.Stat(name) + + return fi, false, err +} + +func (u *CopyOnWriteFs) isNotExist(err error) bool { + if e, ok := err.(*os.PathError); ok { + err = e.Err + } + if err == os.ErrNotExist || err == syscall.ENOENT || err == syscall.ENOTDIR { + return true + } + return false +} + // Renaming files present only in the base layer is not permitted func (u *CopyOnWriteFs) Rename(oldname, newname string) error { b, err := u.isBaseFile(oldname) @@ -219,7 +258,7 @@ func (u *CopyOnWriteFs) Open(name string) (File, error) { return nil, fmt.Errorf("BaseErr: %v\nOverlayErr: %v", bErr, lErr) } - return &UnionFile{base: bfile, layer: lfile}, nil + return &UnionFile{Base: bfile, Layer: lfile}, nil } func (u *CopyOnWriteFs) Mkdir(name string, perm os.FileMode) error { diff --git a/vendor/github.com/spf13/afero/go.mod b/vendor/github.com/spf13/afero/go.mod new file mode 100644 index 00000000..9eff4fed --- /dev/null +++ b/vendor/github.com/spf13/afero/go.mod @@ -0,0 +1 @@ +module github.com/spf13/afero diff --git a/vendor/github.com/spf13/afero/lstater.go b/vendor/github.com/spf13/afero/lstater.go new file mode 100644 index 00000000..89c1bfc0 --- /dev/null +++ b/vendor/github.com/spf13/afero/lstater.go @@ -0,0 +1,27 @@ +// Copyright © 2018 Steve Francia . +// +// 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. + +package afero + +import ( + "os" +) + +// Lstater is an optional interface in Afero. It is only implemented by the +// filesystems saying so. +// It will call Lstat if the filesystem iself is, or it delegates to, the os filesystem. +// Else it will call Stat. +// In addtion to the FileInfo, it will return a boolean telling whether Lstat was called or not. +type Lstater interface { + LstatIfPossible(name string) (os.FileInfo, bool, error) +} diff --git a/vendor/github.com/spf13/afero/match.go b/vendor/github.com/spf13/afero/match.go index 08b3b7e0..c18a87fb 100644 --- a/vendor/github.com/spf13/afero/match.go +++ b/vendor/github.com/spf13/afero/match.go @@ -33,8 +33,8 @@ import ( // built-ins from that package. func Glob(fs Fs, pattern string) (matches []string, err error) { if !hasMeta(pattern) { - // afero does not support Lstat directly. - if _, err = lstatIfOs(fs, pattern); err != nil { + // Lstat not supported by a ll filesystems. + if _, err = lstatIfPossible(fs, pattern); err != nil { return nil, nil } return []string{pattern}, nil diff --git a/vendor/github.com/spf13/afero/mem/file.go b/vendor/github.com/spf13/afero/mem/file.go index 885e5542..7af2fb56 100644 --- a/vendor/github.com/spf13/afero/mem/file.go +++ b/vendor/github.com/spf13/afero/mem/file.go @@ -131,6 +131,9 @@ func (f *File) Sync() error { } func (f *File) Readdir(count int) (res []os.FileInfo, err error) { + if !f.fileData.dir { + return nil, &os.PathError{Op: "readdir", Path: f.fileData.name, Err: errors.New("not a dir")} + } var outLength int64 f.fileData.Lock() diff --git a/vendor/github.com/spf13/afero/os.go b/vendor/github.com/spf13/afero/os.go index 6b8bce1c..13cc1b84 100644 --- a/vendor/github.com/spf13/afero/os.go +++ b/vendor/github.com/spf13/afero/os.go @@ -19,6 +19,8 @@ import ( "time" ) +var _ Lstater = (*OsFs)(nil) + // OsFs is a Fs implementation that uses functions provided by the os package. // // For details in any method, check the documentation of the os package @@ -92,3 +94,8 @@ func (OsFs) Chmod(name string, mode os.FileMode) error { func (OsFs) Chtimes(name string, atime time.Time, mtime time.Time) error { return os.Chtimes(name, atime, mtime) } + +func (OsFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + fi, err := os.Lstat(name) + return fi, true, err +} diff --git a/vendor/github.com/spf13/afero/path.go b/vendor/github.com/spf13/afero/path.go index 1d90e46d..18f60a0f 100644 --- a/vendor/github.com/spf13/afero/path.go +++ b/vendor/github.com/spf13/afero/path.go @@ -60,7 +60,7 @@ func walk(fs Fs, path string, info os.FileInfo, walkFn filepath.WalkFunc) error for _, name := range names { filename := filepath.Join(path, name) - fileInfo, err := lstatIfOs(fs, filename) + fileInfo, err := lstatIfPossible(fs, filename) if err != nil { if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { return err @@ -77,15 +77,13 @@ func walk(fs Fs, path string, info os.FileInfo, walkFn filepath.WalkFunc) error return nil } -// if the filesystem is OsFs use Lstat, else use fs.Stat -func lstatIfOs(fs Fs, path string) (info os.FileInfo, err error) { - _, ok := fs.(*OsFs) - if ok { - info, err = os.Lstat(path) - } else { - info, err = fs.Stat(path) +// if the filesystem supports it, use Lstat, else use fs.Stat +func lstatIfPossible(fs Fs, path string) (os.FileInfo, error) { + if lfs, ok := fs.(Lstater); ok { + fi, _, err := lfs.LstatIfPossible(path) + return fi, err } - return + return fs.Stat(path) } // Walk walks the file tree rooted at root, calling walkFn for each file or @@ -100,7 +98,7 @@ func (a Afero) Walk(root string, walkFn filepath.WalkFunc) error { } func Walk(fs Fs, root string, walkFn filepath.WalkFunc) error { - info, err := lstatIfOs(fs, root) + info, err := lstatIfPossible(fs, root) if err != nil { return walkFn(root, nil, err) } diff --git a/vendor/github.com/spf13/afero/readonlyfs.go b/vendor/github.com/spf13/afero/readonlyfs.go index f1fa55bc..c6376ec3 100644 --- a/vendor/github.com/spf13/afero/readonlyfs.go +++ b/vendor/github.com/spf13/afero/readonlyfs.go @@ -6,6 +6,8 @@ import ( "time" ) +var _ Lstater = (*ReadOnlyFs)(nil) + type ReadOnlyFs struct { source Fs } @@ -34,6 +36,14 @@ func (r *ReadOnlyFs) Stat(name string) (os.FileInfo, error) { return r.source.Stat(name) } +func (r *ReadOnlyFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { + if lsf, ok := r.source.(Lstater); ok { + return lsf.LstatIfPossible(name) + } + fi, err := r.Stat(name) + return fi, false, err +} + func (r *ReadOnlyFs) Rename(o, n string) error { return syscall.EPERM } diff --git a/vendor/github.com/spf13/afero/unionFile.go b/vendor/github.com/spf13/afero/unionFile.go index 99f9e5db..1e78f7d1 100644 --- a/vendor/github.com/spf13/afero/unionFile.go +++ b/vendor/github.com/spf13/afero/unionFile.go @@ -21,32 +21,33 @@ import ( // successful read in the overlay will move the cursor position in the base layer // by the number of bytes read. type UnionFile struct { - base File - layer File - off int - files []os.FileInfo + Base File + Layer File + Merger DirsMerger + off int + files []os.FileInfo } func (f *UnionFile) Close() error { // first close base, so we have a newer timestamp in the overlay. If we'd close // the overlay first, we'd get a cacheStale the next time we access this file // -> cache would be useless ;-) - if f.base != nil { - f.base.Close() + if f.Base != nil { + f.Base.Close() } - if f.layer != nil { - return f.layer.Close() + if f.Layer != nil { + return f.Layer.Close() } return BADFD } func (f *UnionFile) Read(s []byte) (int, error) { - if f.layer != nil { - n, err := f.layer.Read(s) - if (err == nil || err == io.EOF) && f.base != nil { + if f.Layer != nil { + n, err := f.Layer.Read(s) + if (err == nil || err == io.EOF) && f.Base != nil { // advance the file position also in the base file, the next // call may be a write at this position (or a seek with SEEK_CUR) - if _, seekErr := f.base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { + if _, seekErr := f.Base.Seek(int64(n), os.SEEK_CUR); seekErr != nil { // only overwrite err in case the seek fails: we need to // report an eventual io.EOF to the caller err = seekErr @@ -54,105 +55,135 @@ func (f *UnionFile) Read(s []byte) (int, error) { } return n, err } - if f.base != nil { - return f.base.Read(s) + if f.Base != nil { + return f.Base.Read(s) } return 0, BADFD } func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) { - if f.layer != nil { - n, err := f.layer.ReadAt(s, o) - if (err == nil || err == io.EOF) && f.base != nil { - _, err = f.base.Seek(o+int64(n), os.SEEK_SET) + if f.Layer != nil { + n, err := f.Layer.ReadAt(s, o) + if (err == nil || err == io.EOF) && f.Base != nil { + _, err = f.Base.Seek(o+int64(n), os.SEEK_SET) } return n, err } - if f.base != nil { - return f.base.ReadAt(s, o) + if f.Base != nil { + return f.Base.ReadAt(s, o) } return 0, BADFD } func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) { - if f.layer != nil { - pos, err = f.layer.Seek(o, w) - if (err == nil || err == io.EOF) && f.base != nil { - _, err = f.base.Seek(o, w) + if f.Layer != nil { + pos, err = f.Layer.Seek(o, w) + if (err == nil || err == io.EOF) && f.Base != nil { + _, err = f.Base.Seek(o, w) } return pos, err } - if f.base != nil { - return f.base.Seek(o, w) + if f.Base != nil { + return f.Base.Seek(o, w) } return 0, BADFD } func (f *UnionFile) Write(s []byte) (n int, err error) { - if f.layer != nil { - n, err = f.layer.Write(s) - if err == nil && f.base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark? - _, err = f.base.Write(s) + if f.Layer != nil { + n, err = f.Layer.Write(s) + if err == nil && f.Base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark? + _, err = f.Base.Write(s) } return n, err } - if f.base != nil { - return f.base.Write(s) + if f.Base != nil { + return f.Base.Write(s) } return 0, BADFD } func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) { - if f.layer != nil { - n, err = f.layer.WriteAt(s, o) - if err == nil && f.base != nil { - _, err = f.base.WriteAt(s, o) + if f.Layer != nil { + n, err = f.Layer.WriteAt(s, o) + if err == nil && f.Base != nil { + _, err = f.Base.WriteAt(s, o) } return n, err } - if f.base != nil { - return f.base.WriteAt(s, o) + if f.Base != nil { + return f.Base.WriteAt(s, o) } return 0, BADFD } func (f *UnionFile) Name() string { - if f.layer != nil { - return f.layer.Name() + if f.Layer != nil { + return f.Layer.Name() } - return f.base.Name() + return f.Base.Name() +} + +// DirsMerger is how UnionFile weaves two directories together. +// It takes the FileInfo slices from the layer and the base and returns a +// single view. +type DirsMerger func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) + +var defaultUnionMergeDirsFn = func(lofi, bofi []os.FileInfo) ([]os.FileInfo, error) { + var files = make(map[string]os.FileInfo) + + for _, fi := range lofi { + files[fi.Name()] = fi + } + + for _, fi := range bofi { + if _, exists := files[fi.Name()]; !exists { + files[fi.Name()] = fi + } + } + + rfi := make([]os.FileInfo, len(files)) + + i := 0 + for _, fi := range files { + rfi[i] = fi + i++ + } + + return rfi, nil + } // Readdir will weave the two directories together and // return a single view of the overlayed directories func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) { + var merge DirsMerger = f.Merger + if merge == nil { + merge = defaultUnionMergeDirsFn + } + if f.off == 0 { - var files = make(map[string]os.FileInfo) - var rfi []os.FileInfo - if f.layer != nil { - rfi, err = f.layer.Readdir(-1) + var lfi []os.FileInfo + if f.Layer != nil { + lfi, err = f.Layer.Readdir(-1) if err != nil { return nil, err } - for _, fi := range rfi { - files[fi.Name()] = fi - } } - if f.base != nil { - rfi, err = f.base.Readdir(-1) + var bfi []os.FileInfo + if f.Base != nil { + bfi, err = f.Base.Readdir(-1) if err != nil { return nil, err } - for _, fi := range rfi { - if _, exists := files[fi.Name()]; !exists { - files[fi.Name()] = fi - } - } + } - for _, fi := range files { - f.files = append(f.files, fi) + merged, err := merge(lfi, bfi) + if err != nil { + return nil, err } + f.files = append(f.files, merged...) } if c == -1 { return f.files[f.off:], nil @@ -174,53 +205,53 @@ func (f *UnionFile) Readdirnames(c int) ([]string, error) { } func (f *UnionFile) Stat() (os.FileInfo, error) { - if f.layer != nil { - return f.layer.Stat() + if f.Layer != nil { + return f.Layer.Stat() } - if f.base != nil { - return f.base.Stat() + if f.Base != nil { + return f.Base.Stat() } return nil, BADFD } func (f *UnionFile) Sync() (err error) { - if f.layer != nil { - err = f.layer.Sync() - if err == nil && f.base != nil { - err = f.base.Sync() + if f.Layer != nil { + err = f.Layer.Sync() + if err == nil && f.Base != nil { + err = f.Base.Sync() } return err } - if f.base != nil { - return f.base.Sync() + if f.Base != nil { + return f.Base.Sync() } return BADFD } func (f *UnionFile) Truncate(s int64) (err error) { - if f.layer != nil { - err = f.layer.Truncate(s) - if err == nil && f.base != nil { - err = f.base.Truncate(s) + if f.Layer != nil { + err = f.Layer.Truncate(s) + if err == nil && f.Base != nil { + err = f.Base.Truncate(s) } return err } - if f.base != nil { - return f.base.Truncate(s) + if f.Base != nil { + return f.Base.Truncate(s) } return BADFD } func (f *UnionFile) WriteString(s string) (n int, err error) { - if f.layer != nil { - n, err = f.layer.WriteString(s) - if err == nil && f.base != nil { - _, err = f.base.WriteString(s) + if f.Layer != nil { + n, err = f.Layer.WriteString(s) + if err == nil && f.Base != nil { + _, err = f.Base.WriteString(s) } return n, err } - if f.base != nil { - return f.base.WriteString(s) + if f.Base != nil { + return f.Base.WriteString(s) } return 0, BADFD } diff --git a/vendor/github.com/spf13/cast/.travis.yml b/vendor/github.com/spf13/cast/.travis.yml index 4da97668..6420d1c2 100644 --- a/vendor/github.com/spf13/cast/.travis.yml +++ b/vendor/github.com/spf13/cast/.travis.yml @@ -1,8 +1,9 @@ language: go +env: + - GO111MODULE=on sudo: required go: - - 1.7.5 - - 1.8 + - "1.11.x" - tip os: - linux diff --git a/vendor/github.com/spf13/cast/cast.go b/vendor/github.com/spf13/cast/cast.go index 8b8c208b..9fba638d 100644 --- a/vendor/github.com/spf13/cast/cast.go +++ b/vendor/github.com/spf13/cast/cast.go @@ -122,6 +122,18 @@ func ToStringMapBool(i interface{}) map[string]bool { return v } +// ToStringMapInt casts an interface to a map[string]int type. +func ToStringMapInt(i interface{}) map[string]int { + v, _ := ToStringMapIntE(i) + return v +} + +// ToStringMapInt64 casts an interface to a map[string]int64 type. +func ToStringMapInt64(i interface{}) map[string]int64 { + v, _ := ToStringMapInt64E(i) + return v +} + // ToStringMap casts an interface to a map[string]interface{} type. func ToStringMap(i interface{}) map[string]interface{} { v, _ := ToStringMapE(i) diff --git a/vendor/github.com/spf13/cast/caste.go b/vendor/github.com/spf13/cast/caste.go index 4fe19289..a4859fb0 100644 --- a/vendor/github.com/spf13/cast/caste.go +++ b/vendor/github.com/spf13/cast/caste.go @@ -990,6 +990,87 @@ func ToStringMapE(i interface{}) (map[string]interface{}, error) { } } +// ToStringMapIntE casts an interface to a map[string]int{} type. +func ToStringMapIntE(i interface{}) (map[string]int, error) { + var m = map[string]int{} + if i == nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int", i, i) + } + + switch v := i.(type) { + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = ToInt(val) + } + return m, nil + case map[string]interface{}: + for k, val := range v { + m[k] = ToInt(val) + } + return m, nil + case map[string]int: + return v, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + } + + if reflect.TypeOf(i).Kind() != reflect.Map { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int", i, i) + } + + mVal := reflect.ValueOf(m) + v := reflect.ValueOf(i) + for _, keyVal := range v.MapKeys() { + val, err := ToIntE(v.MapIndex(keyVal).Interface()) + if err != nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int", i, i) + } + mVal.SetMapIndex(keyVal, reflect.ValueOf(val)) + } + return m, nil +} + +// ToStringMapInt64E casts an interface to a map[string]int64{} type. +func ToStringMapInt64E(i interface{}) (map[string]int64, error) { + var m = map[string]int64{} + if i == nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int64", i, i) + } + + switch v := i.(type) { + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = ToInt64(val) + } + return m, nil + case map[string]interface{}: + for k, val := range v { + m[k] = ToInt64(val) + } + return m, nil + case map[string]int64: + return v, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + } + + if reflect.TypeOf(i).Kind() != reflect.Map { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int64", i, i) + } + mVal := reflect.ValueOf(m) + v := reflect.ValueOf(i) + for _, keyVal := range v.MapKeys() { + val, err := ToInt64E(v.MapIndex(keyVal).Interface()) + if err != nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int64", i, i) + } + mVal.SetMapIndex(keyVal, reflect.ValueOf(val)) + } + return m, nil +} + // ToSliceE casts an interface to a []interface{} type. func ToSliceE(i interface{}) ([]interface{}, error) { var s []interface{} @@ -1137,9 +1218,11 @@ func StringToDate(s string) (time.Time, error) { "2006-01-02 15:04:05.999999999 -0700 MST", // Time.String() "2006-01-02", "02 Jan 2006", + "2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon "2006-01-02 15:04:05 -07:00", "2006-01-02 15:04:05 -0700", "2006-01-02 15:04:05Z07:00", // RFC3339 without T + "2006-01-02 15:04:05Z0700", // RFC3339 without T or timezone hh:mm colon "2006-01-02 15:04:05", time.Kitchen, time.Stamp, diff --git a/vendor/github.com/spf13/cast/go.mod b/vendor/github.com/spf13/cast/go.mod new file mode 100644 index 00000000..c1c0232d --- /dev/null +++ b/vendor/github.com/spf13/cast/go.mod @@ -0,0 +1,7 @@ +module github.com/spf13/cast + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 +) diff --git a/vendor/github.com/spf13/cast/go.sum b/vendor/github.com/spf13/cast/go.sum new file mode 100644 index 00000000..e03ee77d --- /dev/null +++ b/vendor/github.com/spf13/cast/go.sum @@ -0,0 +1,6 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/vendor/github.com/spf13/jwalterweatherman/README.md b/vendor/github.com/spf13/jwalterweatherman/README.md index d8cfd27a..932a23fc 100644 --- a/vendor/github.com/spf13/jwalterweatherman/README.md +++ b/vendor/github.com/spf13/jwalterweatherman/README.md @@ -18,7 +18,7 @@ provides a few advantages over using the standard log library alone. I really wanted a very straightforward library that could seamlessly do the following things. -1. Replace all the println, printf, etc statements thought my code with +1. Replace all the println, printf, etc statements thoughout my code with something more useful 2. Allow the user to easily control what levels are printed to stdout 3. Allow the user to easily control what levels are logged diff --git a/vendor/github.com/spf13/jwalterweatherman/go.mod b/vendor/github.com/spf13/jwalterweatherman/go.mod new file mode 100644 index 00000000..bce549c0 --- /dev/null +++ b/vendor/github.com/spf13/jwalterweatherman/go.mod @@ -0,0 +1 @@ +module github.com/spf13/jwalterweatherman diff --git a/vendor/github.com/spf13/pflag/bytes.go b/vendor/github.com/spf13/pflag/bytes.go new file mode 100644 index 00000000..67d53045 --- /dev/null +++ b/vendor/github.com/spf13/pflag/bytes.go @@ -0,0 +1,209 @@ +package pflag + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strings" +) + +// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded +type bytesHexValue []byte + +// String implements pflag.Value.String. +func (bytesHex bytesHexValue) String() string { + return fmt.Sprintf("%X", []byte(bytesHex)) +} + +// Set implements pflag.Value.Set. +func (bytesHex *bytesHexValue) Set(value string) error { + bin, err := hex.DecodeString(strings.TrimSpace(value)) + + if err != nil { + return err + } + + *bytesHex = bin + + return nil +} + +// Type implements pflag.Value.Type. +func (*bytesHexValue) Type() string { + return "bytesHex" +} + +func newBytesHexValue(val []byte, p *[]byte) *bytesHexValue { + *p = val + return (*bytesHexValue)(p) +} + +func bytesHexConv(sval string) (interface{}, error) { + + bin, err := hex.DecodeString(sval) + + if err == nil { + return bin, nil + } + + return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err) +} + +// GetBytesHex return the []byte value of a flag with the given name +func (f *FlagSet) GetBytesHex(name string) ([]byte, error) { + val, err := f.getFlagType(name, "bytesHex", bytesHexConv) + + if err != nil { + return []byte{}, err + } + + return val.([]byte), nil +} + +// BytesHexVar defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func (f *FlagSet) BytesHexVar(p *[]byte, name string, value []byte, usage string) { + f.VarP(newBytesHexValue(value, p), name, "", usage) +} + +// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) { + f.VarP(newBytesHexValue(value, p), name, shorthand, usage) +} + +// BytesHexVar defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func BytesHexVar(p *[]byte, name string, value []byte, usage string) { + CommandLine.VarP(newBytesHexValue(value, p), name, "", usage) +} + +// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash. +func BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) { + CommandLine.VarP(newBytesHexValue(value, p), name, shorthand, usage) +} + +// BytesHex defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func (f *FlagSet) BytesHex(name string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesHexVarP(p, name, "", value, usage) + return p +} + +// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesHexP(name, shorthand string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesHexVarP(p, name, shorthand, value, usage) + return p +} + +// BytesHex defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func BytesHex(name string, value []byte, usage string) *[]byte { + return CommandLine.BytesHexP(name, "", value, usage) +} + +// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash. +func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte { + return CommandLine.BytesHexP(name, shorthand, value, usage) +} + +// BytesBase64 adapts []byte for use as a flag. Value of flag is Base64 encoded +type bytesBase64Value []byte + +// String implements pflag.Value.String. +func (bytesBase64 bytesBase64Value) String() string { + return base64.StdEncoding.EncodeToString([]byte(bytesBase64)) +} + +// Set implements pflag.Value.Set. +func (bytesBase64 *bytesBase64Value) Set(value string) error { + bin, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value)) + + if err != nil { + return err + } + + *bytesBase64 = bin + + return nil +} + +// Type implements pflag.Value.Type. +func (*bytesBase64Value) Type() string { + return "bytesBase64" +} + +func newBytesBase64Value(val []byte, p *[]byte) *bytesBase64Value { + *p = val + return (*bytesBase64Value)(p) +} + +func bytesBase64ValueConv(sval string) (interface{}, error) { + + bin, err := base64.StdEncoding.DecodeString(sval) + if err == nil { + return bin, nil + } + + return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err) +} + +// GetBytesBase64 return the []byte value of a flag with the given name +func (f *FlagSet) GetBytesBase64(name string) ([]byte, error) { + val, err := f.getFlagType(name, "bytesBase64", bytesBase64ValueConv) + + if err != nil { + return []byte{}, err + } + + return val.([]byte), nil +} + +// BytesBase64Var defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func (f *FlagSet) BytesBase64Var(p *[]byte, name string, value []byte, usage string) { + f.VarP(newBytesBase64Value(value, p), name, "", usage) +} + +// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) { + f.VarP(newBytesBase64Value(value, p), name, shorthand, usage) +} + +// BytesBase64Var defines an []byte flag with specified name, default value, and usage string. +// The argument p points to an []byte variable in which to store the value of the flag. +func BytesBase64Var(p *[]byte, name string, value []byte, usage string) { + CommandLine.VarP(newBytesBase64Value(value, p), name, "", usage) +} + +// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash. +func BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) { + CommandLine.VarP(newBytesBase64Value(value, p), name, shorthand, usage) +} + +// BytesBase64 defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func (f *FlagSet) BytesBase64(name string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesBase64VarP(p, name, "", value, usage) + return p +} + +// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte { + p := new([]byte) + f.BytesBase64VarP(p, name, shorthand, value, usage) + return p +} + +// BytesBase64 defines an []byte flag with specified name, default value, and usage string. +// The return value is the address of an []byte variable that stores the value of the flag. +func BytesBase64(name string, value []byte, usage string) *[]byte { + return CommandLine.BytesBase64P(name, "", value, usage) +} + +// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash. +func BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte { + return CommandLine.BytesBase64P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/flag.go b/vendor/github.com/spf13/pflag/flag.go index 28538c07..9beeda8e 100644 --- a/vendor/github.com/spf13/pflag/flag.go +++ b/vendor/github.com/spf13/pflag/flag.go @@ -101,6 +101,7 @@ package pflag import ( "bytes" "errors" + goflag "flag" "fmt" "io" "os" @@ -123,6 +124,12 @@ const ( PanicOnError ) +// ParseErrorsWhitelist defines the parsing errors that can be ignored +type ParseErrorsWhitelist struct { + // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags + UnknownFlags bool +} + // NormalizedName is a flag name that has been normalized according to rules // for the FlagSet (e.g. making '-' and '_' equivalent). type NormalizedName string @@ -138,6 +145,9 @@ type FlagSet struct { // help/usage messages. SortFlags bool + // ParseErrorsWhitelist is used to configure a whitelist of errors + ParseErrorsWhitelist ParseErrorsWhitelist + name string parsed bool actual map[NormalizedName]*Flag @@ -153,6 +163,8 @@ type FlagSet struct { output io.Writer // nil means stderr; use out() accessor interspersed bool // allow interspersed option/non-option args normalizeNameFunc func(f *FlagSet, name string) NormalizedName + + addedGoFlagSets []*goflag.FlagSet } // A Flag represents the state of a flag. @@ -267,16 +279,16 @@ func (f *FlagSet) VisitAll(fn func(*Flag)) { } } -// HasFlags returns a bool to indicate if the FlagSet has any flags definied. +// HasFlags returns a bool to indicate if the FlagSet has any flags defined. func (f *FlagSet) HasFlags() bool { return len(f.formal) > 0 } // HasAvailableFlags returns a bool to indicate if the FlagSet has any flags -// definied that are not hidden or deprecated. +// that are not hidden. func (f *FlagSet) HasAvailableFlags() bool { for _, flag := range f.formal { - if !flag.Hidden && len(flag.Deprecated) == 0 { + if !flag.Hidden { return true } } @@ -386,6 +398,7 @@ func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error { return fmt.Errorf("deprecated message for flag %q must be set", name) } flag.Deprecated = usageMessage + flag.Hidden = true return nil } @@ -586,11 +599,14 @@ func wrapN(i, slop int, s string) (string, string) { return s, "" } - w := strings.LastIndexAny(s[:i], " \t") + w := strings.LastIndexAny(s[:i], " \t\n") if w <= 0 { return s, "" } - + nlPos := strings.LastIndex(s[:i], "\n") + if nlPos > 0 && nlPos < w { + return s[:nlPos], s[nlPos+1:] + } return s[:w], s[w+1:] } @@ -599,7 +615,7 @@ func wrapN(i, slop int, s string) (string, string) { // caller). Pass `w` == 0 to do no wrapping func wrap(i, w int, s string) string { if w == 0 { - return s + return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1) } // space between indent i and end of line width w into which @@ -617,7 +633,7 @@ func wrap(i, w int, s string) string { } // If still not enough space then don't even try to wrap. if wrap < 24 { - return s + return strings.Replace(s, "\n", r, -1) } // Try to avoid short orphan words on the final line, by @@ -629,14 +645,14 @@ func wrap(i, w int, s string) string { // Handle first line, which is indented by the caller (or the // special case above) l, s = wrapN(wrap, slop, s) - r = r + l + r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1) // Now wrap the rest for s != "" { var t string t, s = wrapN(wrap, slop, s) - r = r + "\n" + strings.Repeat(" ", i) + t + r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1) } return r @@ -653,7 +669,7 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string { maxlen := 0 f.VisitAll(func(flag *Flag) { - if flag.Deprecated != "" || flag.Hidden { + if flag.Hidden { return } @@ -700,6 +716,9 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string { line += fmt.Sprintf(" (default %s)", flag.DefValue) } } + if len(flag.Deprecated) != 0 { + line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated) + } lines = append(lines, line) }) @@ -896,6 +915,28 @@ func (f *FlagSet) usage() { } } +//--unknown (args will be empty) +//--unknown --next-flag ... (args will be --next-flag ...) +//--unknown arg ... (args will be arg ...) +func stripUnknownFlagValue(args []string) []string { + if len(args) == 0 { + //--unknown + return args + } + + first := args[0] + if len(first) > 0 && first[0] == '-' { + //--unknown --next-flag ... + return args + } + + //--unknown arg ... (args will be arg ...) + if len(args) > 1 { + return args[1:] + } + return nil +} + func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) { a = args name := s[2:] @@ -907,13 +948,24 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin split := strings.SplitN(name, "=", 2) name = split[0] flag, exists := f.formal[f.normalizeFlagName(name)] + if !exists { - if name == "help" { // special case for nice help message. + switch { + case name == "help": f.usage() return a, ErrHelp + case f.ParseErrorsWhitelist.UnknownFlags: + // --unknown=unknownval arg ... + // we do not want to lose arg in this case + if len(split) >= 2 { + return a, nil + } + + return stripUnknownFlagValue(a), nil + default: + err = f.failf("unknown flag: --%s", name) + return } - err = f.failf("unknown flag: --%s", name) - return } var value string @@ -941,23 +993,36 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin } func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) { + outArgs = args + if strings.HasPrefix(shorthands, "test.") { return } - outArgs = args outShorts = shorthands[1:] c := shorthands[0] flag, exists := f.shorthands[c] if !exists { - if c == 'h' { // special case for nice help message. + switch { + case c == 'h': f.usage() err = ErrHelp return + case f.ParseErrorsWhitelist.UnknownFlags: + // '-f=arg arg ...' + // we do not want to lose arg in this case + if len(shorthands) > 2 && shorthands[1] == '=' { + outShorts = "" + return + } + + outArgs = stripUnknownFlagValue(outArgs) + return + default: + err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) + return } - err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) - return } var value string @@ -1044,6 +1109,11 @@ func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) { // are defined and before flags are accessed by the program. // The return value will be ErrHelp if -help was set but not defined. func (f *FlagSet) Parse(arguments []string) error { + if f.addedGoFlagSets != nil { + for _, goFlagSet := range f.addedGoFlagSets { + goFlagSet.Parse(nil) + } + } f.parsed = true if len(arguments) < 0 { diff --git a/vendor/github.com/spf13/pflag/golangflag.go b/vendor/github.com/spf13/pflag/golangflag.go index c4f47ebe..d3dd72b7 100644 --- a/vendor/github.com/spf13/pflag/golangflag.go +++ b/vendor/github.com/spf13/pflag/golangflag.go @@ -98,4 +98,8 @@ func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) { newSet.VisitAll(func(goflag *goflag.Flag) { f.AddGoFlag(goflag) }) + if f.addedGoFlagSets == nil { + f.addedGoFlagSets = make([]*goflag.FlagSet, 0) + } + f.addedGoFlagSets = append(f.addedGoFlagSets, newSet) } diff --git a/vendor/github.com/spf13/pflag/string_to_int.go b/vendor/github.com/spf13/pflag/string_to_int.go new file mode 100644 index 00000000..5ceda396 --- /dev/null +++ b/vendor/github.com/spf13/pflag/string_to_int.go @@ -0,0 +1,149 @@ +package pflag + +import ( + "bytes" + "fmt" + "strconv" + "strings" +) + +// -- stringToInt Value +type stringToIntValue struct { + value *map[string]int + changed bool +} + +func newStringToIntValue(val map[string]int, p *map[string]int) *stringToIntValue { + ssv := new(stringToIntValue) + ssv.value = p + *ssv.value = val + return ssv +} + +// Format: a=1,b=2 +func (s *stringToIntValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make(map[string]int, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return fmt.Errorf("%s must be formatted as key=value", pair) + } + var err error + out[kv[0]], err = strconv.Atoi(kv[1]) + if err != nil { + return err + } + } + if !s.changed { + *s.value = out + } else { + for k, v := range out { + (*s.value)[k] = v + } + } + s.changed = true + return nil +} + +func (s *stringToIntValue) Type() string { + return "stringToInt" +} + +func (s *stringToIntValue) String() string { + var buf bytes.Buffer + i := 0 + for k, v := range *s.value { + if i > 0 { + buf.WriteRune(',') + } + buf.WriteString(k) + buf.WriteRune('=') + buf.WriteString(strconv.Itoa(v)) + i++ + } + return "[" + buf.String() + "]" +} + +func stringToIntConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // An empty string would cause an empty map + if len(val) == 0 { + return map[string]int{}, nil + } + ss := strings.Split(val, ",") + out := make(map[string]int, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return nil, fmt.Errorf("%s must be formatted as key=value", pair) + } + var err error + out[kv[0]], err = strconv.Atoi(kv[1]) + if err != nil { + return nil, err + } + } + return out, nil +} + +// GetStringToInt return the map[string]int value of a flag with the given name +func (f *FlagSet) GetStringToInt(name string) (map[string]int, error) { + val, err := f.getFlagType(name, "stringToInt", stringToIntConv) + if err != nil { + return map[string]int{}, err + } + return val.(map[string]int), nil +} + +// StringToIntVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a map[string]int variable in which to store the values of the multiple flags. +// The value of each argument will not try to be separated by comma +func (f *FlagSet) StringToIntVar(p *map[string]int, name string, value map[string]int, usage string) { + f.VarP(newStringToIntValue(value, p), name, "", usage) +} + +// StringToIntVarP is like StringToIntVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringToIntVarP(p *map[string]int, name, shorthand string, value map[string]int, usage string) { + f.VarP(newStringToIntValue(value, p), name, shorthand, usage) +} + +// StringToIntVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a map[string]int variable in which to store the value of the flag. +// The value of each argument will not try to be separated by comma +func StringToIntVar(p *map[string]int, name string, value map[string]int, usage string) { + CommandLine.VarP(newStringToIntValue(value, p), name, "", usage) +} + +// StringToIntVarP is like StringToIntVar, but accepts a shorthand letter that can be used after a single dash. +func StringToIntVarP(p *map[string]int, name, shorthand string, value map[string]int, usage string) { + CommandLine.VarP(newStringToIntValue(value, p), name, shorthand, usage) +} + +// StringToInt defines a string flag with specified name, default value, and usage string. +// The return value is the address of a map[string]int variable that stores the value of the flag. +// The value of each argument will not try to be separated by comma +func (f *FlagSet) StringToInt(name string, value map[string]int, usage string) *map[string]int { + p := map[string]int{} + f.StringToIntVarP(&p, name, "", value, usage) + return &p +} + +// StringToIntP is like StringToInt, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringToIntP(name, shorthand string, value map[string]int, usage string) *map[string]int { + p := map[string]int{} + f.StringToIntVarP(&p, name, shorthand, value, usage) + return &p +} + +// StringToInt defines a string flag with specified name, default value, and usage string. +// The return value is the address of a map[string]int variable that stores the value of the flag. +// The value of each argument will not try to be separated by comma +func StringToInt(name string, value map[string]int, usage string) *map[string]int { + return CommandLine.StringToIntP(name, "", value, usage) +} + +// StringToIntP is like StringToInt, but accepts a shorthand letter that can be used after a single dash. +func StringToIntP(name, shorthand string, value map[string]int, usage string) *map[string]int { + return CommandLine.StringToIntP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/string_to_string.go b/vendor/github.com/spf13/pflag/string_to_string.go new file mode 100644 index 00000000..890a01af --- /dev/null +++ b/vendor/github.com/spf13/pflag/string_to_string.go @@ -0,0 +1,160 @@ +package pflag + +import ( + "bytes" + "encoding/csv" + "fmt" + "strings" +) + +// -- stringToString Value +type stringToStringValue struct { + value *map[string]string + changed bool +} + +func newStringToStringValue(val map[string]string, p *map[string]string) *stringToStringValue { + ssv := new(stringToStringValue) + ssv.value = p + *ssv.value = val + return ssv +} + +// Format: a=1,b=2 +func (s *stringToStringValue) Set(val string) error { + var ss []string + n := strings.Count(val, "=") + switch n { + case 0: + return fmt.Errorf("%s must be formatted as key=value", val) + case 1: + ss = append(ss, strings.Trim(val, `"`)) + default: + r := csv.NewReader(strings.NewReader(val)) + var err error + ss, err = r.Read() + if err != nil { + return err + } + } + + out := make(map[string]string, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return fmt.Errorf("%s must be formatted as key=value", pair) + } + out[kv[0]] = kv[1] + } + if !s.changed { + *s.value = out + } else { + for k, v := range out { + (*s.value)[k] = v + } + } + s.changed = true + return nil +} + +func (s *stringToStringValue) Type() string { + return "stringToString" +} + +func (s *stringToStringValue) String() string { + records := make([]string, 0, len(*s.value)>>1) + for k, v := range *s.value { + records = append(records, k+"="+v) + } + + var buf bytes.Buffer + w := csv.NewWriter(&buf) + if err := w.Write(records); err != nil { + panic(err) + } + w.Flush() + return "[" + strings.TrimSpace(buf.String()) + "]" +} + +func stringToStringConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // An empty string would cause an empty map + if len(val) == 0 { + return map[string]string{}, nil + } + r := csv.NewReader(strings.NewReader(val)) + ss, err := r.Read() + if err != nil { + return nil, err + } + out := make(map[string]string, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return nil, fmt.Errorf("%s must be formatted as key=value", pair) + } + out[kv[0]] = kv[1] + } + return out, nil +} + +// GetStringToString return the map[string]string value of a flag with the given name +func (f *FlagSet) GetStringToString(name string) (map[string]string, error) { + val, err := f.getFlagType(name, "stringToString", stringToStringConv) + if err != nil { + return map[string]string{}, err + } + return val.(map[string]string), nil +} + +// StringToStringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a map[string]string variable in which to store the values of the multiple flags. +// The value of each argument will not try to be separated by comma +func (f *FlagSet) StringToStringVar(p *map[string]string, name string, value map[string]string, usage string) { + f.VarP(newStringToStringValue(value, p), name, "", usage) +} + +// StringToStringVarP is like StringToStringVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringToStringVarP(p *map[string]string, name, shorthand string, value map[string]string, usage string) { + f.VarP(newStringToStringValue(value, p), name, shorthand, usage) +} + +// StringToStringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a map[string]string variable in which to store the value of the flag. +// The value of each argument will not try to be separated by comma +func StringToStringVar(p *map[string]string, name string, value map[string]string, usage string) { + CommandLine.VarP(newStringToStringValue(value, p), name, "", usage) +} + +// StringToStringVarP is like StringToStringVar, but accepts a shorthand letter that can be used after a single dash. +func StringToStringVarP(p *map[string]string, name, shorthand string, value map[string]string, usage string) { + CommandLine.VarP(newStringToStringValue(value, p), name, shorthand, usage) +} + +// StringToString defines a string flag with specified name, default value, and usage string. +// The return value is the address of a map[string]string variable that stores the value of the flag. +// The value of each argument will not try to be separated by comma +func (f *FlagSet) StringToString(name string, value map[string]string, usage string) *map[string]string { + p := map[string]string{} + f.StringToStringVarP(&p, name, "", value, usage) + return &p +} + +// StringToStringP is like StringToString, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringToStringP(name, shorthand string, value map[string]string, usage string) *map[string]string { + p := map[string]string{} + f.StringToStringVarP(&p, name, shorthand, value, usage) + return &p +} + +// StringToString defines a string flag with specified name, default value, and usage string. +// The return value is the address of a map[string]string variable that stores the value of the flag. +// The value of each argument will not try to be separated by comma +func StringToString(name string, value map[string]string, usage string) *map[string]string { + return CommandLine.StringToStringP(name, "", value, usage) +} + +// StringToStringP is like StringToString, but accepts a shorthand letter that can be used after a single dash. +func StringToStringP(name, shorthand string, value map[string]string, usage string) *map[string]string { + return CommandLine.StringToStringP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/viper/.travis.yml b/vendor/github.com/spf13/viper/.travis.yml index 55960d11..22a8a00e 100644 --- a/vendor/github.com/spf13/viper/.travis.yml +++ b/vendor/github.com/spf13/viper/.travis.yml @@ -2,9 +2,8 @@ go_import_path: github.com/spf13/viper language: go go: - - 1.7.x - - 1.8.x - - 1.9.x + - 1.10.x + - 1.11.x - tip os: diff --git a/vendor/github.com/spf13/viper/README.md b/vendor/github.com/spf13/viper/README.md index 64bf4743..87bbc8b4 100644 --- a/vendor/github.com/spf13/viper/README.md +++ b/vendor/github.com/spf13/viper/README.md @@ -191,7 +191,7 @@ _When working with ENV variables, it’s important to recognize that Viper treats ENV variables as case sensitive._ Viper provides a mechanism to try to ensure that ENV variables are unique. By -using `SetEnvPrefix`, you can tell Viper to use add a prefix while reading from +using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from the environment variables. Both `BindEnv` and `AutomaticEnv` will use this prefix. @@ -373,12 +373,33 @@ how to use Consul. ### Remote Key/Value Store Example - Unencrypted +#### etcd ```go viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" err := viper.ReadRemoteConfig() ``` +#### Consul +You need to set a key to Consul key/value storage with JSON value containing your desired config. +For example, create a Consul key/value store key `MY_CONSUL_KEY` with value: + +```json +{ + "port": 8080, + "hostname": "myhostname.com" +} +``` + +```go +viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") +viper.SetConfigType("json") // Need to explicitly set this to json +err := viper.ReadRemoteConfig() + +fmt.Println(viper.Get("port")) // 8080 +fmt.Println(viper.Get("hostname")) // myhostname.com +``` + ### Remote Key/Value Store Example - Encrypted ```go @@ -437,6 +458,7 @@ The following functions and methods exist: * `GetTime(key string) : time.Time` * `GetDuration(key string) : time.Duration` * `IsSet(key string) : bool` + * `AllSettings() : map[string]interface{}` One important thing to recognize is that each Get function will return a zero value if it’s not found. To check if a given key exists, the `IsSet()` method @@ -590,6 +612,27 @@ if err != nil { } ``` +### Marshalling to string + +You may need to marhsal all the settings held in viper into a string rather than write them to a file. +You can use your favorite format's marshaller with the config returned by `AllSettings()`. + +```go +import ( + yaml "gopkg.in/yaml.v2" + // ... +) + +func yamlStringSettings() string { + c := viper.AllSettings() + bs, err := yaml.Marshal(c) + if err != nil { + t.Fatalf("unable to marshal config to YAML: %v", err) + } + return string(bs) +} +``` + ## Viper or Vipers? Viper comes ready to use out of the box. There is no configuration or diff --git a/vendor/github.com/spf13/viper/go.mod b/vendor/github.com/spf13/viper/go.mod new file mode 100644 index 00000000..3f4e1c22 --- /dev/null +++ b/vendor/github.com/spf13/viper/go.mod @@ -0,0 +1,16 @@ +module github.com/spf13/viper + +require ( + github.com/fsnotify/fsnotify v1.4.7 + github.com/hashicorp/hcl v1.0.0 + github.com/magiconair/properties v1.8.0 + github.com/mitchellh/mapstructure v1.0.0 + github.com/pelletier/go-toml v1.2.0 + github.com/spf13/afero v1.1.2 + github.com/spf13/cast v1.2.0 + github.com/spf13/jwalterweatherman v1.0.0 + github.com/spf13/pflag v1.0.2 + golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 // indirect + golang.org/x/text v0.3.0 // indirect + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/vendor/github.com/spf13/viper/go.sum b/vendor/github.com/spf13/viper/go.sum new file mode 100644 index 00000000..3e3b874b --- /dev/null +++ b/vendor/github.com/spf13/viper/go.sum @@ -0,0 +1,26 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/spf13/viper/nohup.out b/vendor/github.com/spf13/viper/nohup.out deleted file mode 100644 index 8973bf27..00000000 --- a/vendor/github.com/spf13/viper/nohup.out +++ /dev/null @@ -1 +0,0 @@ -QProcess::start: Process is already running diff --git a/vendor/github.com/spf13/viper/viper.go b/vendor/github.com/spf13/viper/viper.go index ad8a0372..3debcdec 100644 --- a/vendor/github.com/spf13/viper/viper.go +++ b/vendor/github.com/spf13/viper/viper.go @@ -113,6 +113,23 @@ func (fnfe ConfigFileNotFoundError) Error() string { return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations) } +// A DecoderConfigOption can be passed to viper.Unmarshal to configure +// mapstructure.DecoderConfig options +type DecoderConfigOption func(*mapstructure.DecoderConfig) + +// DecodeHook returns a DecoderConfigOption which overrides the default +// DecoderConfig.DecodeHook value, the default is: +// +// mapstructure.ComposeDecodeHookFunc( +// mapstructure.StringToTimeDurationHookFunc(), +// mapstructure.StringToSliceHookFunc(","), +// ) +func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption { + return func(c *mapstructure.DecoderConfig) { + c.DecodeHook = hook + } +} + // Viper is a prioritized configuration registry. It // maintains a set of configuration sources, fetches // values to populate those, and provides them according @@ -289,7 +306,9 @@ func (v *Viper) WatchConfig() { if err != nil { log.Println("error:", err) } - v.onConfigChange(event) + if v.onConfigChange != nil { + v.onConfigChange(event) + } } } case err := <-watcher.Errors: @@ -631,8 +650,10 @@ func (v *Viper) Get(key string) interface{} { return cast.ToBool(val) case string: return cast.ToString(val) - case int64, int32, int16, int8, int: + case int32, int16, int8, int: return cast.ToInt(val) + case int64: + return cast.ToInt64(val) case float64, float32: return cast.ToFloat64(val) case time.Time: @@ -682,6 +703,12 @@ func (v *Viper) GetInt(key string) int { return cast.ToInt(v.Get(key)) } +// GetInt32 returns the value associated with the key as an integer. +func GetInt32(key string) int32 { return v.GetInt32(key) } +func (v *Viper) GetInt32(key string) int32 { + return cast.ToInt32(v.Get(key)) +} + // GetInt64 returns the value associated with the key as an integer. func GetInt64(key string) int64 { return v.GetInt64(key) } func (v *Viper) GetInt64(key string) int64 { @@ -739,9 +766,11 @@ func (v *Viper) GetSizeInBytes(key string) uint { } // UnmarshalKey takes a single key and unmarshals it into a Struct. -func UnmarshalKey(key string, rawVal interface{}) error { return v.UnmarshalKey(key, rawVal) } -func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error { - err := decode(v.Get(key), defaultDecoderConfig(rawVal)) +func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { + return v.UnmarshalKey(key, rawVal, opts...) +} +func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error { + err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...)) if err != nil { return err @@ -754,9 +783,11 @@ func (v *Viper) UnmarshalKey(key string, rawVal interface{}) error { // Unmarshal unmarshals the config into a Struct. Make sure that the tags // on the fields of the structure are properly set. -func Unmarshal(rawVal interface{}) error { return v.Unmarshal(rawVal) } -func (v *Viper) Unmarshal(rawVal interface{}) error { - err := decode(v.AllSettings(), defaultDecoderConfig(rawVal)) +func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error { + return v.Unmarshal(rawVal, opts...) +} +func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error { + err := decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...)) if err != nil { return err @@ -769,8 +800,8 @@ func (v *Viper) Unmarshal(rawVal interface{}) error { // defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot // of time.Duration values & string slices -func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { - return &mapstructure.DecoderConfig{ +func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig { + c := &mapstructure.DecoderConfig{ Metadata: nil, Result: output, WeaklyTypedInput: true, @@ -779,6 +810,10 @@ func defaultDecoderConfig(output interface{}) *mapstructure.DecoderConfig { mapstructure.StringToSliceHookFunc(","), ), } + for _, opt := range opts { + opt(c) + } + return c } // A wrapper around mapstructure.Decode that mimics the WeakDecode functionality @@ -1108,7 +1143,7 @@ func (v *Viper) SetDefault(key string, value interface{}) { deepestMap[lastKey] = value } -// Set sets the value for the key in the override regiser. +// Set sets the value for the key in the override register. // Set is case-insensitive for a key. // Will be used instead of values obtained via // flags, config file, ENV, default, or key/value store. @@ -1720,18 +1755,14 @@ func (v *Viper) getConfigType() string { } func (v *Viper) getConfigFile() (string, error) { - // if explicitly set, then use it - if v.configFile != "" { - return v.configFile, nil - } - - cf, err := v.findConfigFile() - if err != nil { - return "", err + if v.configFile == "" { + cf, err := v.findConfigFile() + if err != nil { + return "", err + } + v.configFile = cf } - - v.configFile = cf - return v.getConfigFile() + return v.configFile, nil } func (v *Viper) searchInPath(in string) (filename string) { -- cgit v1.2.3