summaryrefslogtreecommitdiffstats
path: root/vendor/github.com
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/dustin/go-humanize/.travis.yml21
-rw-r--r--vendor/github.com/dustin/go-humanize/LICENSE21
-rw-r--r--vendor/github.com/dustin/go-humanize/README.markdown124
-rw-r--r--vendor/github.com/dustin/go-humanize/big.go31
-rw-r--r--vendor/github.com/dustin/go-humanize/bigbytes.go173
-rw-r--r--vendor/github.com/dustin/go-humanize/bytes.go143
-rw-r--r--vendor/github.com/dustin/go-humanize/comma.go116
-rw-r--r--vendor/github.com/dustin/go-humanize/commaf.go40
-rw-r--r--vendor/github.com/dustin/go-humanize/ftoa.go46
-rw-r--r--vendor/github.com/dustin/go-humanize/humanize.go8
-rw-r--r--vendor/github.com/dustin/go-humanize/number.go192
-rw-r--r--vendor/github.com/dustin/go-humanize/ordinals.go25
-rw-r--r--vendor/github.com/dustin/go-humanize/si.go123
-rw-r--r--vendor/github.com/dustin/go-humanize/times.go117
-rw-r--r--vendor/github.com/dyatlov/go-opengraph/opengraph/opengraph.go3
-rw-r--r--vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml4
-rw-r--r--vendor/github.com/google/uuid/hash.go4
-rw-r--r--vendor/github.com/google/uuid/sql.go2
-rw-r--r--vendor/github.com/google/uuid/uuid.go10
-rw-r--r--vendor/github.com/google/uuid/version4.go8
-rw-r--r--vendor/github.com/hashicorp/go-multierror/.travis.yml12
-rw-r--r--vendor/github.com/hashicorp/go-multierror/README.md29
-rw-r--r--vendor/github.com/hashicorp/go-multierror/append.go2
-rw-r--r--vendor/github.com/hashicorp/go-multierror/go.mod2
-rw-r--r--vendor/github.com/hashicorp/go-multierror/multierror.go15
-rw-r--r--vendor/github.com/json-iterator/go/.codecov.yml3
-rw-r--r--vendor/github.com/json-iterator/go/.gitignore4
-rw-r--r--vendor/github.com/json-iterator/go/.travis.yml14
-rw-r--r--vendor/github.com/json-iterator/go/Gopkg.lock21
-rw-r--r--vendor/github.com/json-iterator/go/Gopkg.toml26
-rw-r--r--vendor/github.com/json-iterator/go/LICENSE21
-rw-r--r--vendor/github.com/json-iterator/go/README.md87
-rw-r--r--vendor/github.com/json-iterator/go/adapter.go150
-rw-r--r--vendor/github.com/json-iterator/go/any.go325
-rw-r--r--vendor/github.com/json-iterator/go/any_array.go278
-rw-r--r--vendor/github.com/json-iterator/go/any_bool.go137
-rw-r--r--vendor/github.com/json-iterator/go/any_float.go83
-rw-r--r--vendor/github.com/json-iterator/go/any_int32.go74
-rw-r--r--vendor/github.com/json-iterator/go/any_int64.go74
-rw-r--r--vendor/github.com/json-iterator/go/any_invalid.go82
-rw-r--r--vendor/github.com/json-iterator/go/any_nil.go69
-rw-r--r--vendor/github.com/json-iterator/go/any_number.go123
-rw-r--r--vendor/github.com/json-iterator/go/any_object.go374
-rw-r--r--vendor/github.com/json-iterator/go/any_str.go166
-rw-r--r--vendor/github.com/json-iterator/go/any_uint32.go74
-rw-r--r--vendor/github.com/json-iterator/go/any_uint64.go74
-rw-r--r--vendor/github.com/json-iterator/go/build.sh12
-rw-r--r--vendor/github.com/json-iterator/go/config.go375
-rw-r--r--vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md7
-rw-r--r--vendor/github.com/json-iterator/go/go.mod11
-rw-r--r--vendor/github.com/json-iterator/go/go.sum15
-rw-r--r--vendor/github.com/json-iterator/go/iter.go349
-rw-r--r--vendor/github.com/json-iterator/go/iter_array.go64
-rw-r--r--vendor/github.com/json-iterator/go/iter_float.go342
-rw-r--r--vendor/github.com/json-iterator/go/iter_int.go346
-rw-r--r--vendor/github.com/json-iterator/go/iter_object.go267
-rw-r--r--vendor/github.com/json-iterator/go/iter_skip.go130
-rw-r--r--vendor/github.com/json-iterator/go/iter_skip_sloppy.go163
-rw-r--r--vendor/github.com/json-iterator/go/iter_skip_strict.go99
-rw-r--r--vendor/github.com/json-iterator/go/iter_str.go215
-rw-r--r--vendor/github.com/json-iterator/go/jsoniter.go18
-rw-r--r--vendor/github.com/json-iterator/go/pool.go42
-rw-r--r--vendor/github.com/json-iterator/go/reflect.go337
-rw-r--r--vendor/github.com/json-iterator/go/reflect_array.go104
-rw-r--r--vendor/github.com/json-iterator/go/reflect_dynamic.go70
-rw-r--r--vendor/github.com/json-iterator/go/reflect_extension.go483
-rw-r--r--vendor/github.com/json-iterator/go/reflect_json_number.go112
-rw-r--r--vendor/github.com/json-iterator/go/reflect_json_raw_message.go76
-rw-r--r--vendor/github.com/json-iterator/go/reflect_map.go346
-rw-r--r--vendor/github.com/json-iterator/go/reflect_marshaler.go225
-rw-r--r--vendor/github.com/json-iterator/go/reflect_native.go453
-rw-r--r--vendor/github.com/json-iterator/go/reflect_optional.go129
-rw-r--r--vendor/github.com/json-iterator/go/reflect_slice.go99
-rw-r--r--vendor/github.com/json-iterator/go/reflect_struct_decoder.go1097
-rw-r--r--vendor/github.com/json-iterator/go/reflect_struct_encoder.go211
-rw-r--r--vendor/github.com/json-iterator/go/stream.go210
-rw-r--r--vendor/github.com/json-iterator/go/stream_float.go111
-rw-r--r--vendor/github.com/json-iterator/go/stream_int.go190
-rw-r--r--vendor/github.com/json-iterator/go/stream_str.go372
-rw-r--r--vendor/github.com/json-iterator/go/test.sh12
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/.gitignore24
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/.goreleaser.yml74
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/.travis.yml67
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/CONTRIBUTING.txt35
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/LICENSE22
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/README.md137
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/cpuid.go1023
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/cpuid_386.s47
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/cpuid_amd64.s72
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/cpuid_arm64.s26
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/detect_arm64.go246
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/detect_ref.go14
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/detect_x86.go35
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/featureid_string.go173
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/go.mod3
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/os_darwin_arm64.go19
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/os_linux_arm64.go130
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/os_other_arm64.go17
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/os_safe_linux_arm64.go7
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/os_unsafe_linux_arm64.go10
-rw-r--r--vendor/github.com/klauspost/cpuid/v2/test-architectures.sh15
-rw-r--r--vendor/github.com/matterbridge/matterclient/channels.go291
-rw-r--r--vendor/github.com/matterbridge/matterclient/go.mod14
-rw-r--r--vendor/github.com/matterbridge/matterclient/go.sum1404
-rw-r--r--vendor/github.com/matterbridge/matterclient/matterclient.go743
-rw-r--r--vendor/github.com/matterbridge/matterclient/messages.go280
-rw-r--r--vendor/github.com/matterbridge/matterclient/users.go212
-rw-r--r--vendor/github.com/mattermost/ldap/conn.go2
-rw-r--r--vendor/github.com/mattermost/logr/v2/.gitignore37
-rw-r--r--vendor/github.com/mattermost/logr/v2/.travis.yml4
-rw-r--r--vendor/github.com/mattermost/logr/v2/LICENSE21
-rw-r--r--vendor/github.com/mattermost/logr/v2/README.md188
-rw-r--r--vendor/github.com/mattermost/logr/v2/config/config.go209
-rw-r--r--vendor/github.com/mattermost/logr/v2/config/sample-config.json90
-rw-r--r--vendor/github.com/mattermost/logr/v2/const.go34
-rw-r--r--vendor/github.com/mattermost/logr/v2/field.go403
-rw-r--r--vendor/github.com/mattermost/logr/v2/fieldapi.go110
-rw-r--r--vendor/github.com/mattermost/logr/v2/filter.go10
-rw-r--r--vendor/github.com/mattermost/logr/v2/filtercustom.go47
-rw-r--r--vendor/github.com/mattermost/logr/v2/filterstd.go65
-rw-r--r--vendor/github.com/mattermost/logr/v2/formatter.go184
-rw-r--r--vendor/github.com/mattermost/logr/v2/formatters/gelf.go152
-rw-r--r--vendor/github.com/mattermost/logr/v2/formatters/json.go273
-rw-r--r--vendor/github.com/mattermost/logr/v2/formatters/plain.go146
-rw-r--r--vendor/github.com/mattermost/logr/v2/go.mod11
-rw-r--r--vendor/github.com/mattermost/logr/v2/go.sum178
-rw-r--r--vendor/github.com/mattermost/logr/v2/level.go34
-rw-r--r--vendor/github.com/mattermost/logr/v2/levelcache.go98
-rw-r--r--vendor/github.com/mattermost/logr/v2/logger.go99
-rw-r--r--vendor/github.com/mattermost/logr/v2/logr.go471
-rw-r--r--vendor/github.com/mattermost/logr/v2/logrec.go182
-rw-r--r--vendor/github.com/mattermost/logr/v2/metrics.go140
-rw-r--r--vendor/github.com/mattermost/logr/v2/options.go192
-rw-r--r--vendor/github.com/mattermost/logr/v2/pkg.go57
-rw-r--r--vendor/github.com/mattermost/logr/v2/stdlogger.go56
-rw-r--r--vendor/github.com/mattermost/logr/v2/sugar.go119
-rw-r--r--vendor/github.com/mattermost/logr/v2/target.go304
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/file.go78
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/syslog.go112
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go56
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/tcp.go251
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem43
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/utils.go33
-rw-r--r--vendor/github.com/mattermost/logr/v2/targets/writer.go38
-rw-r--r--vendor/github.com/mattermost/logr/v2/timeout.go34
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/LICENSE.txt897
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/NOTICE.txt4844
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/access.go72
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/analytics_row.go11
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/audit.go14
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/auditconv.go713
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/audits.go14
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/authorize.go118
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/bot.go204
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/builtin.go9
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/bundle_info.go34
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel.go338
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_count.go42
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_data.go18
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_list.go56
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_member.go163
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history.go11
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history_result.go17
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_mentions.go28
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_search.go22
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_sidebar.go85
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_stats.go11
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/channel_view.go15
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/client4.go7690
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/cloud.go188
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/cluster_discovery.go115
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/cluster_info.go12
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/cluster_message.go62
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/cluster_stats.go11
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/command.go136
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/command_args.go45
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/command_autocomplete.go410
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/command_request.go8
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/command_response.go72
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/command_webhook.go60
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/compliance.go109
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/compliance_post.go121
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/config.go3915
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/custom_status.go140
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/data_retention_policy.go70
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/emoji.go95
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/emoji_data.go7
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/emoji_search.go9
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/feature_flags.go104
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/file.go13
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/file_info.go184
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/file_info_list.go111
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/file_info_search_results.go18
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/gitlab.go8
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/group.go190
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/group_member.go23
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/group_syncable.go175
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/guest_invite.go39
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/incoming_webhook.go184
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/initial_load.go14
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/integration_action.go470
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/integrity.go58
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/job.go130
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/ldap.go10
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/license.go343
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/link_metadata.go193
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/manifest.go455
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/marketplace_plugin.go127
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/mention_map.go80
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/message_export.go50
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/mfa_secret.go9
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/migration.go38
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/oauth.go127
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/outgoing_webhook.go224
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/permalink.go27
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/permission.go2192
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugin_cluster_event.go28
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugin_event_data.go9
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugin_key_value.go33
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugin_kvset_options.go47
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugin_status.go26
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugin_valid.go39
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/plugins_response.go13
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/post.go719
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/post_embed.go24
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/post_list.go177
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/post_metadata.go64
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/post_search_results.go29
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/preference.go122
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/product_notices.go220
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/push_notification.go82
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/push_response.go33
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/reaction.go65
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/remote_cluster.go302
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/role.go939
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/saml.go176
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/scheduled_task.go100
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/scheme.go167
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/search_params.go397
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/security_bulletin.go11
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/session.go197
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/session_serial_gen.go540
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/shared_channel.go249
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/slack_attachment.go196
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/slack_compatibility.go30
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/status.go55
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/suggest_command.go9
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/switch_request.go39
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/system.go182
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/team.go253
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/team_member.go118
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/team_member_serial_gen.go193
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/team_search.go22
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/team_stats.go10
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/terms_of_service.go54
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/thread.go72
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/token.go42
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/typing_request.go9
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/upload_session.go113
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user.go912
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token.go41
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token_search.go8
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_autocomplete.go18
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_count.go26
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_get.go46
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_search.go54
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_serial_gen.go826
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/user_terms_of_service.go48
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/users_stats.go8
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/utils.go587
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/version.go190
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/websocket_client.go324
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/websocket_message.go293
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/model/websocket_request.go36
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/services/timezones/default.go599
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/services/timezones/timezones.go29
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/filesstore.go83
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/localstore.go211
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3_overrides.go56
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3store.go442
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/i18n/i18n.go185
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/autolink.go255
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/block_quote.go62
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/blocks.go154
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/document.go22
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/fenced_code.go112
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/html.go192
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/html_entities.go2132
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/indented_code.go98
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/inlines.go663
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/inspect.go78
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/lines.go32
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/links.go184
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/list.go220
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/markdown.go147
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/paragraph.go71
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/reference_definition.go75
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/default.go63
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/global.go132
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/levels.go58
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/mlog.go419
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/options.go55
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/tlog.go79
-rw-r--r--vendor/github.com/mattermost/mattermost-server/v6/utils/jsonutils/json.go56
-rw-r--r--vendor/github.com/mattn/go-isatty/isatty_others.go2
-rw-r--r--vendor/github.com/mattn/go-isatty/isatty_solaris.go5
-rw-r--r--vendor/github.com/mattn/go-isatty/isatty_tcgets.go2
-rw-r--r--vendor/github.com/mattn/go-isatty/renovate.json8
-rw-r--r--vendor/github.com/mattn/go-runewidth/go.mod2
-rw-r--r--vendor/github.com/mattn/go-runewidth/go.sum2
-rw-r--r--vendor/github.com/mattn/go-runewidth/runewidth.go122
-rw-r--r--vendor/github.com/mattn/go-runewidth/runewidth_table.go6
-rw-r--r--vendor/github.com/minio/md5-simd/LICENSE202
-rw-r--r--vendor/github.com/minio/md5-simd/LICENSE.Golang27
-rw-r--r--vendor/github.com/minio/md5-simd/README.md198
-rw-r--r--vendor/github.com/minio/md5-simd/block16_amd64.s228
-rw-r--r--vendor/github.com/minio/md5-simd/block8_amd64.s281
-rw-r--r--vendor/github.com/minio/md5-simd/block_amd64.go210
-rw-r--r--vendor/github.com/minio/md5-simd/go.mod5
-rw-r--r--vendor/github.com/minio/md5-simd/go.sum2
-rw-r--r--vendor/github.com/minio/md5-simd/md5-digest_amd64.go188
-rw-r--r--vendor/github.com/minio/md5-simd/md5-server_amd64.go397
-rw-r--r--vendor/github.com/minio/md5-simd/md5-server_fallback.go12
-rw-r--r--vendor/github.com/minio/md5-simd/md5-util_amd64.go85
-rw-r--r--vendor/github.com/minio/md5-simd/md5.go63
-rw-r--r--vendor/github.com/minio/md5-simd/md5block_amd64.go11
-rw-r--r--vendor/github.com/minio/md5-simd/md5block_amd64.s714
-rw-r--r--vendor/github.com/minio/minio-go/v7/.gitignore4
-rw-r--r--vendor/github.com/minio/minio-go/v7/.golangci.yml16
-rw-r--r--vendor/github.com/minio/minio-go/v7/CNAME1
-rw-r--r--vendor/github.com/minio/minio-go/v7/CONTRIBUTING.md23
-rw-r--r--vendor/github.com/minio/minio-go/v7/LICENSE202
-rw-r--r--vendor/github.com/minio/minio-go/v7/MAINTAINERS.md35
-rw-r--r--vendor/github.com/minio/minio-go/v7/Makefile35
-rw-r--r--vendor/github.com/minio/minio-go/v7/NOTICE9
-rw-r--r--vendor/github.com/minio/minio-go/v7/README.md251
-rw-r--r--vendor/github.com/minio/minio-go/v7/README_zh_CN.md260
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-encryption.go134
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-lifecycle.go147
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-notification.go255
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-policy.go142
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-replication.go228
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-tagging.go135
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-bucket-versioning.go137
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-compose-object.go580
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-copy-object.go77
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-datatypes.go173
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-error-response.go271
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-get-object-acl.go140
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-get-object-file.go127
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-get-object.go681
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-get-options.go140
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-list.go950
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-object-legal-hold.go176
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-object-lock.go241
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-object-retention.go165
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-object-tagging.go157
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-presigned.go216
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-put-bucket.go123
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-put-object-common.go148
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-put-object-file-context.go64
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go393
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go487
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-put-object.go370
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-remove.go419
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go361
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-select.go751
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-stat.go127
-rw-r--r--vendor/github.com/minio/minio-go/v7/api.go896
-rw-r--r--vendor/github.com/minio/minio-go/v7/bucket-cache.go253
-rw-r--r--vendor/github.com/minio/minio-go/v7/code_of_conduct.md80
-rw-r--r--vendor/github.com/minio/minio-go/v7/constants.go92
-rw-r--r--vendor/github.com/minio/minio-go/v7/core.go133
-rw-r--r--vendor/github.com/minio/minio-go/v7/functional_tests.go11502
-rw-r--r--vendor/github.com/minio/minio-go/v7/go.mod27
-rw-r--r--vendor/github.com/minio/minio-go/v7/go.sum76
-rw-r--r--vendor/github.com/minio/minio-go/v7/hook-reader.go85
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/assume_role.go214
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/chain.go89
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/config.json.sample17
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.go182
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.sample12
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/doc.go62
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/env_aws.go71
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/env_minio.go68
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go120
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/file_minio_client.go135
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go367
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/signature-type.go77
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/static.go67
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_client_grants.go162
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_ldap_identity.go124
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go181
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/encrypt/server-side.go198
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/lifecycle/lifecycle.go303
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/notification/info.go78
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/notification/notification.go395
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go696
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go391
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/set/stringset.go200
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-streaming.go306
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v2.go317
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v4.go318
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/signer/utils.go59
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/sse/sse.go66
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/tags/tags.go341
-rw-r--r--vendor/github.com/minio/minio-go/v7/post-policy.go327
-rw-r--r--vendor/github.com/minio/minio-go/v7/retry-continous.go69
-rw-r--r--vendor/github.com/minio/minio-go/v7/retry.go124
-rw-r--r--vendor/github.com/minio/minio-go/v7/s3-endpoints.go57
-rw-r--r--vendor/github.com/minio/minio-go/v7/s3-error.go61
-rw-r--r--vendor/github.com/minio/minio-go/v7/transport.go83
-rw-r--r--vendor/github.com/minio/minio-go/v7/utils.go488
-rw-r--r--vendor/github.com/minio/sha256-simd/.gitignore1
-rw-r--r--vendor/github.com/minio/sha256-simd/LICENSE202
-rw-r--r--vendor/github.com/minio/sha256-simd/README.md137
-rw-r--r--vendor/github.com/minio/sha256-simd/cpuid_other.go46
-rw-r--r--vendor/github.com/minio/sha256-simd/go.mod5
-rw-r--r--vendor/github.com/minio/sha256-simd/go.sum4
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256.go399
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.asm686
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.go500
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.s267
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.go6
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.s266
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256block_amd64.go27
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256block_arm64.go36
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256block_arm64.s192
-rw-r--r--vendor/github.com/minio/sha256-simd/sha256block_other.go28
-rw-r--r--vendor/github.com/minio/sha256-simd/test-architectures.sh15
-rw-r--r--vendor/github.com/mitchellh/go-homedir/LICENSE21
-rw-r--r--vendor/github.com/mitchellh/go-homedir/README.md14
-rw-r--r--vendor/github.com/mitchellh/go-homedir/go.mod1
-rw-r--r--vendor/github.com/mitchellh/go-homedir/homedir.go167
-rw-r--r--vendor/github.com/modern-go/concurrent/.gitignore1
-rw-r--r--vendor/github.com/modern-go/concurrent/.travis.yml14
-rw-r--r--vendor/github.com/modern-go/concurrent/LICENSE201
-rw-r--r--vendor/github.com/modern-go/concurrent/README.md49
-rw-r--r--vendor/github.com/modern-go/concurrent/executor.go14
-rw-r--r--vendor/github.com/modern-go/concurrent/go_above_19.go15
-rw-r--r--vendor/github.com/modern-go/concurrent/go_below_19.go33
-rw-r--r--vendor/github.com/modern-go/concurrent/log.go13
-rw-r--r--vendor/github.com/modern-go/concurrent/test.sh12
-rw-r--r--vendor/github.com/modern-go/concurrent/unbounded_executor.go119
-rw-r--r--vendor/github.com/modern-go/reflect2/.gitignore2
-rw-r--r--vendor/github.com/modern-go/reflect2/.travis.yml15
-rw-r--r--vendor/github.com/modern-go/reflect2/Gopkg.lock15
-rw-r--r--vendor/github.com/modern-go/reflect2/Gopkg.toml35
-rw-r--r--vendor/github.com/modern-go/reflect2/LICENSE201
-rw-r--r--vendor/github.com/modern-go/reflect2/README.md71
-rw-r--r--vendor/github.com/modern-go/reflect2/go_above_17.go8
-rw-r--r--vendor/github.com/modern-go/reflect2/go_above_19.go14
-rw-r--r--vendor/github.com/modern-go/reflect2/go_below_17.go9
-rw-r--r--vendor/github.com/modern-go/reflect2/go_below_19.go14
-rw-r--r--vendor/github.com/modern-go/reflect2/reflect2.go298
-rw-r--r--vendor/github.com/modern-go/reflect2/reflect2_amd64.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/reflect2_kind.go30
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_386.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_amd64p32.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_arm.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_arm64.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_mips64x.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_mipsx.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_ppc64x.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/relfect2_s390x.s0
-rw-r--r--vendor/github.com/modern-go/reflect2/safe_field.go58
-rw-r--r--vendor/github.com/modern-go/reflect2/safe_map.go101
-rw-r--r--vendor/github.com/modern-go/reflect2/safe_slice.go92
-rw-r--r--vendor/github.com/modern-go/reflect2/safe_struct.go29
-rw-r--r--vendor/github.com/modern-go/reflect2/safe_type.go78
-rw-r--r--vendor/github.com/modern-go/reflect2/test.sh12
-rw-r--r--vendor/github.com/modern-go/reflect2/type_map.go113
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_array.go65
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_eface.go59
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_field.go74
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_iface.go64
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_link.go70
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_map.go138
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_ptr.go46
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_slice.go177
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_struct.go59
-rw-r--r--vendor/github.com/modern-go/reflect2/unsafe_type.go85
-rw-r--r--vendor/github.com/philhofer/fwd/reader.go92
-rw-r--r--vendor/github.com/philhofer/fwd/writer.go24
-rw-r--r--vendor/github.com/rivo/uniseg/LICENSE.txt21
-rw-r--r--vendor/github.com/rivo/uniseg/README.md62
-rw-r--r--vendor/github.com/rivo/uniseg/doc.go8
-rw-r--r--vendor/github.com/rivo/uniseg/go.mod3
-rw-r--r--vendor/github.com/rivo/uniseg/grapheme.go268
-rw-r--r--vendor/github.com/rivo/uniseg/properties.go1658
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/errors.go3
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/json.go4
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/read.go5
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/read_bytes.go40
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/unsafe.go17
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/write.go24
-rw-r--r--vendor/github.com/tinylib/msgp/msgp/write_bytes.go20
-rw-r--r--vendor/github.com/wiggin77/merror/.travis.yml5
-rw-r--r--vendor/github.com/wiggin77/merror/README.md6
-rw-r--r--vendor/github.com/wiggin77/merror/go.mod4
-rw-r--r--vendor/github.com/wiggin77/merror/go.sum10
-rw-r--r--vendor/github.com/wiggin77/merror/merror.go41
502 files changed, 103270 insertions, 176 deletions
diff --git a/vendor/github.com/dustin/go-humanize/.travis.yml b/vendor/github.com/dustin/go-humanize/.travis.yml
new file mode 100644
index 00000000..ba95cdd1
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/.travis.yml
@@ -0,0 +1,21 @@
+sudo: false
+language: go
+go:
+ - 1.3.x
+ - 1.5.x
+ - 1.6.x
+ - 1.7.x
+ - 1.8.x
+ - 1.9.x
+ - master
+matrix:
+ allow_failures:
+ - go: master
+ fast_finish: true
+install:
+ - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d -s .)
+ - go tool vet .
+ - go test -v -race ./...
diff --git a/vendor/github.com/dustin/go-humanize/LICENSE b/vendor/github.com/dustin/go-humanize/LICENSE
new file mode 100644
index 00000000..8d9a94a9
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
+
+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.
+
+<http://www.opensource.org/licenses/mit-license.php>
diff --git a/vendor/github.com/dustin/go-humanize/README.markdown b/vendor/github.com/dustin/go-humanize/README.markdown
new file mode 100644
index 00000000..91b4ae56
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/README.markdown
@@ -0,0 +1,124 @@
+# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
+
+Just a few functions for helping humanize times and sizes.
+
+`go get` it as `github.com/dustin/go-humanize`, import it as
+`"github.com/dustin/go-humanize"`, use it as `humanize`.
+
+See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
+complete documentation.
+
+## Sizes
+
+This lets you take numbers like `82854982` and convert them to useful
+strings like, `83 MB` or `79 MiB` (whichever you prefer).
+
+Example:
+
+```go
+fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
+```
+
+## Times
+
+This lets you take a `time.Time` and spit it out in relative terms.
+For example, `12 seconds ago` or `3 days from now`.
+
+Example:
+
+```go
+fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
+```
+
+Thanks to Kyle Lemons for the time implementation from an IRC
+conversation one day. It's pretty neat.
+
+## Ordinals
+
+From a [mailing list discussion][odisc] where a user wanted to be able
+to label ordinals.
+
+ 0 -> 0th
+ 1 -> 1st
+ 2 -> 2nd
+ 3 -> 3rd
+ 4 -> 4th
+ [...]
+
+Example:
+
+```go
+fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
+```
+
+## Commas
+
+Want to shove commas into numbers? Be my guest.
+
+ 0 -> 0
+ 100 -> 100
+ 1000 -> 1,000
+ 1000000000 -> 1,000,000,000
+ -100000 -> -100,000
+
+Example:
+
+```go
+fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
+```
+
+## Ftoa
+
+Nicer float64 formatter that removes trailing zeros.
+
+```go
+fmt.Printf("%f", 2.24) // 2.240000
+fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
+fmt.Printf("%f", 2.0) // 2.000000
+fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
+```
+
+## SI notation
+
+Format numbers with [SI notation][sinotation].
+
+Example:
+
+```go
+humanize.SI(0.00000000223, "M") // 2.23 nM
+```
+
+## English-specific functions
+
+The following functions are in the `humanize/english` subpackage.
+
+### Plurals
+
+Simple English pluralization
+
+```go
+english.PluralWord(1, "object", "") // object
+english.PluralWord(42, "object", "") // objects
+english.PluralWord(2, "bus", "") // buses
+english.PluralWord(99, "locus", "loci") // loci
+
+english.Plural(1, "object", "") // 1 object
+english.Plural(42, "object", "") // 42 objects
+english.Plural(2, "bus", "") // 2 buses
+english.Plural(99, "locus", "loci") // 99 loci
+```
+
+### Word series
+
+Format comma-separated words lists with conjuctions:
+
+```go
+english.WordSeries([]string{"foo"}, "and") // foo
+english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
+english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
+
+english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
+```
+
+[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
+[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
diff --git a/vendor/github.com/dustin/go-humanize/big.go b/vendor/github.com/dustin/go-humanize/big.go
new file mode 100644
index 00000000..f49dc337
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/big.go
@@ -0,0 +1,31 @@
+package humanize
+
+import (
+ "math/big"
+)
+
+// order of magnitude (to a max order)
+func oomm(n, b *big.Int, maxmag int) (float64, int) {
+ mag := 0
+ m := &big.Int{}
+ for n.Cmp(b) >= 0 {
+ n.DivMod(n, b, m)
+ mag++
+ if mag == maxmag && maxmag >= 0 {
+ break
+ }
+ }
+ return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
+}
+
+// total order of magnitude
+// (same as above, but with no upper limit)
+func oom(n, b *big.Int) (float64, int) {
+ mag := 0
+ m := &big.Int{}
+ for n.Cmp(b) >= 0 {
+ n.DivMod(n, b, m)
+ mag++
+ }
+ return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
+}
diff --git a/vendor/github.com/dustin/go-humanize/bigbytes.go b/vendor/github.com/dustin/go-humanize/bigbytes.go
new file mode 100644
index 00000000..1a2bf617
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/bigbytes.go
@@ -0,0 +1,173 @@
+package humanize
+
+import (
+ "fmt"
+ "math/big"
+ "strings"
+ "unicode"
+)
+
+var (
+ bigIECExp = big.NewInt(1024)
+
+ // BigByte is one byte in bit.Ints
+ BigByte = big.NewInt(1)
+ // BigKiByte is 1,024 bytes in bit.Ints
+ BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
+ // BigMiByte is 1,024 k bytes in bit.Ints
+ BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
+ // BigGiByte is 1,024 m bytes in bit.Ints
+ BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
+ // BigTiByte is 1,024 g bytes in bit.Ints
+ BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
+ // BigPiByte is 1,024 t bytes in bit.Ints
+ BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
+ // BigEiByte is 1,024 p bytes in bit.Ints
+ BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
+ // BigZiByte is 1,024 e bytes in bit.Ints
+ BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
+ // BigYiByte is 1,024 z bytes in bit.Ints
+ BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
+)
+
+var (
+ bigSIExp = big.NewInt(1000)
+
+ // BigSIByte is one SI byte in big.Ints
+ BigSIByte = big.NewInt(1)
+ // BigKByte is 1,000 SI bytes in big.Ints
+ BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
+ // BigMByte is 1,000 SI k bytes in big.Ints
+ BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
+ // BigGByte is 1,000 SI m bytes in big.Ints
+ BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
+ // BigTByte is 1,000 SI g bytes in big.Ints
+ BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
+ // BigPByte is 1,000 SI t bytes in big.Ints
+ BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
+ // BigEByte is 1,000 SI p bytes in big.Ints
+ BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
+ // BigZByte is 1,000 SI e bytes in big.Ints
+ BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
+ // BigYByte is 1,000 SI z bytes in big.Ints
+ BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
+)
+
+var bigBytesSizeTable = map[string]*big.Int{
+ "b": BigByte,
+ "kib": BigKiByte,
+ "kb": BigKByte,
+ "mib": BigMiByte,
+ "mb": BigMByte,
+ "gib": BigGiByte,
+ "gb": BigGByte,
+ "tib": BigTiByte,
+ "tb": BigTByte,
+ "pib": BigPiByte,
+ "pb": BigPByte,
+ "eib": BigEiByte,
+ "eb": BigEByte,
+ "zib": BigZiByte,
+ "zb": BigZByte,
+ "yib": BigYiByte,
+ "yb": BigYByte,
+ // Without suffix
+ "": BigByte,
+ "ki": BigKiByte,
+ "k": BigKByte,
+ "mi": BigMiByte,
+ "m": BigMByte,
+ "gi": BigGiByte,
+ "g": BigGByte,
+ "ti": BigTiByte,
+ "t": BigTByte,
+ "pi": BigPiByte,
+ "p": BigPByte,
+ "ei": BigEiByte,
+ "e": BigEByte,
+ "z": BigZByte,
+ "zi": BigZiByte,
+ "y": BigYByte,
+ "yi": BigYiByte,
+}
+
+var ten = big.NewInt(10)
+
+func humanateBigBytes(s, base *big.Int, sizes []string) string {
+ if s.Cmp(ten) < 0 {
+ return fmt.Sprintf("%d B", s)
+ }
+ c := (&big.Int{}).Set(s)
+ val, mag := oomm(c, base, len(sizes)-1)
+ suffix := sizes[mag]
+ f := "%.0f %s"
+ if val < 10 {
+ f = "%.1f %s"
+ }
+
+ return fmt.Sprintf(f, val, suffix)
+
+}
+
+// BigBytes produces a human readable representation of an SI size.
+//
+// See also: ParseBigBytes.
+//
+// BigBytes(82854982) -> 83 MB
+func BigBytes(s *big.Int) string {
+ sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
+ return humanateBigBytes(s, bigSIExp, sizes)
+}
+
+// BigIBytes produces a human readable representation of an IEC size.
+//
+// See also: ParseBigBytes.
+//
+// BigIBytes(82854982) -> 79 MiB
+func BigIBytes(s *big.Int) string {
+ sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
+ return humanateBigBytes(s, bigIECExp, sizes)
+}
+
+// ParseBigBytes parses a string representation of bytes into the number
+// of bytes it represents.
+//
+// See also: BigBytes, BigIBytes.
+//
+// ParseBigBytes("42 MB") -> 42000000, nil
+// ParseBigBytes("42 mib") -> 44040192, nil
+func ParseBigBytes(s string) (*big.Int, error) {
+ lastDigit := 0
+ hasComma := false
+ for _, r := range s {
+ if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+ break
+ }
+ if r == ',' {
+ hasComma = true
+ }
+ lastDigit++
+ }
+
+ num := s[:lastDigit]
+ if hasComma {
+ num = strings.Replace(num, ",", "", -1)
+ }
+
+ val := &big.Rat{}
+ _, err := fmt.Sscanf(num, "%f", val)
+ if err != nil {
+ return nil, err
+ }
+
+ extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+ if m, ok := bigBytesSizeTable[extra]; ok {
+ mv := (&big.Rat{}).SetInt(m)
+ val.Mul(val, mv)
+ rv := &big.Int{}
+ rv.Div(val.Num(), val.Denom())
+ return rv, nil
+ }
+
+ return nil, fmt.Errorf("unhandled size name: %v", extra)
+}
diff --git a/vendor/github.com/dustin/go-humanize/bytes.go b/vendor/github.com/dustin/go-humanize/bytes.go
new file mode 100644
index 00000000..0b498f48
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/bytes.go
@@ -0,0 +1,143 @@
+package humanize
+
+import (
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+ "unicode"
+)
+
+// IEC Sizes.
+// kibis of bits
+const (
+ Byte = 1 << (iota * 10)
+ KiByte
+ MiByte
+ GiByte
+ TiByte
+ PiByte
+ EiByte
+)
+
+// SI Sizes.
+const (
+ IByte = 1
+ KByte = IByte * 1000
+ MByte = KByte * 1000
+ GByte = MByte * 1000
+ TByte = GByte * 1000
+ PByte = TByte * 1000
+ EByte = PByte * 1000
+)
+
+var bytesSizeTable = map[string]uint64{
+ "b": Byte,
+ "kib": KiByte,
+ "kb": KByte,
+ "mib": MiByte,
+ "mb": MByte,
+ "gib": GiByte,
+ "gb": GByte,
+ "tib": TiByte,
+ "tb": TByte,
+ "pib": PiByte,
+ "pb": PByte,
+ "eib": EiByte,
+ "eb": EByte,
+ // Without suffix
+ "": Byte,
+ "ki": KiByte,
+ "k": KByte,
+ "mi": MiByte,
+ "m": MByte,
+ "gi": GiByte,
+ "g": GByte,
+ "ti": TiByte,
+ "t": TByte,
+ "pi": PiByte,
+ "p": PByte,
+ "ei": EiByte,
+ "e": EByte,
+}
+
+func logn(n, b float64) float64 {
+ return math.Log(n) / math.Log(b)
+}
+
+func humanateBytes(s uint64, base float64, sizes []string) string {
+ if s < 10 {
+ return fmt.Sprintf("%d B", s)
+ }
+ e := math.Floor(logn(float64(s), base))
+ suffix := sizes[int(e)]
+ val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
+ f := "%.0f %s"
+ if val < 10 {
+ f = "%.1f %s"
+ }
+
+ return fmt.Sprintf(f, val, suffix)
+}
+
+// Bytes produces a human readable representation of an SI size.
+//
+// See also: ParseBytes.
+//
+// Bytes(82854982) -> 83 MB
+func Bytes(s uint64) string {
+ sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
+ return humanateBytes(s, 1000, sizes)
+}
+
+// IBytes produces a human readable representation of an IEC size.
+//
+// See also: ParseBytes.
+//
+// IBytes(82854982) -> 79 MiB
+func IBytes(s uint64) string {
+ sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
+ return humanateBytes(s, 1024, sizes)
+}
+
+// ParseBytes parses a string representation of bytes into the number
+// of bytes it represents.
+//
+// See Also: Bytes, IBytes.
+//
+// ParseBytes("42 MB") -> 42000000, nil
+// ParseBytes("42 mib") -> 44040192, nil
+func ParseBytes(s string) (uint64, error) {
+ lastDigit := 0
+ hasComma := false
+ for _, r := range s {
+ if !(unicode.IsDigit(r) || r == '.' || r == ',') {
+ break
+ }
+ if r == ',' {
+ hasComma = true
+ }
+ lastDigit++
+ }
+
+ num := s[:lastDigit]
+ if hasComma {
+ num = strings.Replace(num, ",", "", -1)
+ }
+
+ f, err := strconv.ParseFloat(num, 64)
+ if err != nil {
+ return 0, err
+ }
+
+ extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
+ if m, ok := bytesSizeTable[extra]; ok {
+ f *= float64(m)
+ if f >= math.MaxUint64 {
+ return 0, fmt.Errorf("too large: %v", s)
+ }
+ return uint64(f), nil
+ }
+
+ return 0, fmt.Errorf("unhandled size name: %v", extra)
+}
diff --git a/vendor/github.com/dustin/go-humanize/comma.go b/vendor/github.com/dustin/go-humanize/comma.go
new file mode 100644
index 00000000..520ae3e5
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/comma.go
@@ -0,0 +1,116 @@
+package humanize
+
+import (
+ "bytes"
+ "math"
+ "math/big"
+ "strconv"
+ "strings"
+)
+
+// Comma produces a string form of the given number in base 10 with
+// commas after every three orders of magnitude.
+//
+// e.g. Comma(834142) -> 834,142
+func Comma(v int64) string {
+ sign := ""
+
+ // Min int64 can't be negated to a usable value, so it has to be special cased.
+ if v == math.MinInt64 {
+ return "-9,223,372,036,854,775,808"
+ }
+
+ if v < 0 {
+ sign = "-"
+ v = 0 - v
+ }
+
+ parts := []string{"", "", "", "", "", "", ""}
+ j := len(parts) - 1
+
+ for v > 999 {
+ parts[j] = strconv.FormatInt(v%1000, 10)
+ switch len(parts[j]) {
+ case 2:
+ parts[j] = "0" + parts[j]
+ case 1:
+ parts[j] = "00" + parts[j]
+ }
+ v = v / 1000
+ j--
+ }
+ parts[j] = strconv.Itoa(int(v))
+ return sign + strings.Join(parts[j:], ",")
+}
+
+// Commaf produces a string form of the given number in base 10 with
+// commas after every three orders of magnitude.
+//
+// e.g. Commaf(834142.32) -> 834,142.32
+func Commaf(v float64) string {
+ buf := &bytes.Buffer{}
+ if v < 0 {
+ buf.Write([]byte{'-'})
+ v = 0 - v
+ }
+
+ comma := []byte{','}
+
+ parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
+ pos := 0
+ if len(parts[0])%3 != 0 {
+ pos += len(parts[0]) % 3
+ buf.WriteString(parts[0][:pos])
+ buf.Write(comma)
+ }
+ for ; pos < len(parts[0]); pos += 3 {
+ buf.WriteString(parts[0][pos : pos+3])
+ buf.Write(comma)
+ }
+ buf.Truncate(buf.Len() - 1)
+
+ if len(parts) > 1 {
+ buf.Write([]byte{'.'})
+ buf.WriteString(parts[1])
+ }
+ return buf.String()
+}
+
+// CommafWithDigits works like the Commaf but limits the resulting
+// string to the given number of decimal places.
+//
+// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
+func CommafWithDigits(f float64, decimals int) string {
+ return stripTrailingDigits(Commaf(f), decimals)
+}
+
+// BigComma produces a string form of the given big.Int in base 10
+// with commas after every three orders of magnitude.
+func BigComma(b *big.Int) string {
+ sign := ""
+ if b.Sign() < 0 {
+ sign = "-"
+ b.Abs(b)
+ }
+
+ athousand := big.NewInt(1000)
+ c := (&big.Int{}).Set(b)
+ _, m := oom(c, athousand)
+ parts := make([]string, m+1)
+ j := len(parts) - 1
+
+ mod := &big.Int{}
+ for b.Cmp(athousand) >= 0 {
+ b.DivMod(b, athousand, mod)
+ parts[j] = strconv.FormatInt(mod.Int64(), 10)
+ switch len(parts[j]) {
+ case 2:
+ parts[j] = "0" + parts[j]
+ case 1:
+ parts[j] = "00" + parts[j]
+ }
+ j--
+ }
+ parts[j] = strconv.Itoa(int(b.Int64()))
+ return sign + strings.Join(parts[j:], ",")
+}
diff --git a/vendor/github.com/dustin/go-humanize/commaf.go b/vendor/github.com/dustin/go-humanize/commaf.go
new file mode 100644
index 00000000..620690de
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/commaf.go
@@ -0,0 +1,40 @@
+// +build go1.6
+
+package humanize
+
+import (
+ "bytes"
+ "math/big"
+ "strings"
+)
+
+// BigCommaf produces a string form of the given big.Float in base 10
+// with commas after every three orders of magnitude.
+func BigCommaf(v *big.Float) string {
+ buf := &bytes.Buffer{}
+ if v.Sign() < 0 {
+ buf.Write([]byte{'-'})
+ v.Abs(v)
+ }
+
+ comma := []byte{','}
+
+ parts := strings.Split(v.Text('f', -1), ".")
+ pos := 0
+ if len(parts[0])%3 != 0 {
+ pos += len(parts[0]) % 3
+ buf.WriteString(parts[0][:pos])
+ buf.Write(comma)
+ }
+ for ; pos < len(parts[0]); pos += 3 {
+ buf.WriteString(parts[0][pos : pos+3])
+ buf.Write(comma)
+ }
+ buf.Truncate(buf.Len() - 1)
+
+ if len(parts) > 1 {
+ buf.Write([]byte{'.'})
+ buf.WriteString(parts[1])
+ }
+ return buf.String()
+}
diff --git a/vendor/github.com/dustin/go-humanize/ftoa.go b/vendor/github.com/dustin/go-humanize/ftoa.go
new file mode 100644
index 00000000..1c62b640
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/ftoa.go
@@ -0,0 +1,46 @@
+package humanize
+
+import (
+ "strconv"
+ "strings"
+)
+
+func stripTrailingZeros(s string) string {
+ offset := len(s) - 1
+ for offset > 0 {
+ if s[offset] == '.' {
+ offset--
+ break
+ }
+ if s[offset] != '0' {
+ break
+ }
+ offset--
+ }
+ return s[:offset+1]
+}
+
+func stripTrailingDigits(s string, digits int) string {
+ if i := strings.Index(s, "."); i >= 0 {
+ if digits <= 0 {
+ return s[:i]
+ }
+ i++
+ if i+digits >= len(s) {
+ return s
+ }
+ return s[:i+digits]
+ }
+ return s
+}
+
+// Ftoa converts a float to a string with no trailing zeros.
+func Ftoa(num float64) string {
+ return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
+}
+
+// FtoaWithDigits converts a float to a string but limits the resulting string
+// to the given number of decimal places, and no trailing zeros.
+func FtoaWithDigits(num float64, digits int) string {
+ return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
+}
diff --git a/vendor/github.com/dustin/go-humanize/humanize.go b/vendor/github.com/dustin/go-humanize/humanize.go
new file mode 100644
index 00000000..a2c2da31
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/humanize.go
@@ -0,0 +1,8 @@
+/*
+Package humanize converts boring ugly numbers to human-friendly strings and back.
+
+Durations can be turned into strings such as "3 days ago", numbers
+representing sizes like 82854982 into useful strings like, "83 MB" or
+"79 MiB" (whichever you prefer).
+*/
+package humanize
diff --git a/vendor/github.com/dustin/go-humanize/number.go b/vendor/github.com/dustin/go-humanize/number.go
new file mode 100644
index 00000000..dec61865
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/number.go
@@ -0,0 +1,192 @@
+package humanize
+
+/*
+Slightly adapted from the source to fit go-humanize.
+
+Author: https://github.com/gorhill
+Source: https://gist.github.com/gorhill/5285193
+
+*/
+
+import (
+ "math"
+ "strconv"
+)
+
+var (
+ renderFloatPrecisionMultipliers = [...]float64{
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000,
+ 1000000,
+ 10000000,
+ 100000000,
+ 1000000000,
+ }
+
+ renderFloatPrecisionRounders = [...]float64{
+ 0.5,
+ 0.05,
+ 0.005,
+ 0.0005,
+ 0.00005,
+ 0.000005,
+ 0.0000005,
+ 0.00000005,
+ 0.000000005,
+ 0.0000000005,
+ }
+)
+
+// FormatFloat produces a formatted number as string based on the following user-specified criteria:
+// * thousands separator
+// * decimal separator
+// * decimal precision
+//
+// Usage: s := RenderFloat(format, n)
+// The format parameter tells how to render the number n.
+//
+// See examples: http://play.golang.org/p/LXc1Ddm1lJ
+//
+// Examples of format strings, given n = 12345.6789:
+// "#,###.##" => "12,345.67"
+// "#,###." => "12,345"
+// "#,###" => "12345,678"
+// "#\u202F###,##" => "12โ€ฏ345,68"
+// "#.###,###### => 12.345,678900
+// "" (aka default format) => 12,345.67
+//
+// The highest precision allowed is 9 digits after the decimal symbol.
+// There is also a version for integer number, FormatInteger(),
+// which is convenient for calls within template.
+func FormatFloat(format string, n float64) string {
+ // Special cases:
+ // NaN = "NaN"
+ // +Inf = "+Infinity"
+ // -Inf = "-Infinity"
+ if math.IsNaN(n) {
+ return "NaN"
+ }
+ if n > math.MaxFloat64 {
+ return "Infinity"
+ }
+ if n < -math.MaxFloat64 {
+ return "-Infinity"
+ }
+
+ // default format
+ precision := 2
+ decimalStr := "."
+ thousandStr := ","
+ positiveStr := ""
+ negativeStr := "-"
+
+ if len(format) > 0 {
+ format := []rune(format)
+
+ // If there is an explicit format directive,
+ // then default values are these:
+ precision = 9
+ thousandStr = ""
+
+ // collect indices of meaningful formatting directives
+ formatIndx := []int{}
+ for i, char := range format {
+ if char != '#' && char != '0' {
+ formatIndx = append(formatIndx, i)
+ }
+ }
+
+ if len(formatIndx) > 0 {
+ // Directive at index 0:
+ // Must be a '+'
+ // Raise an error if not the case
+ // index: 0123456789
+ // +0.000,000
+ // +000,000.0
+ // +0000.00
+ // +0000
+ if formatIndx[0] == 0 {
+ if format[formatIndx[0]] != '+' {
+ panic("RenderFloat(): invalid positive sign directive")
+ }
+ positiveStr = "+"
+ formatIndx = formatIndx[1:]
+ }
+
+ // Two directives:
+ // First is thousands separator
+ // Raise an error if not followed by 3-digit
+ // 0123456789
+ // 0.000,000
+ // 000,000.00
+ if len(formatIndx) == 2 {
+ if (formatIndx[1] - formatIndx[0]) != 4 {
+ panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
+ }
+ thousandStr = string(format[formatIndx[0]])
+ formatIndx = formatIndx[1:]
+ }
+
+ // One directive:
+ // Directive is decimal separator
+ // The number of digit-specifier following the separator indicates wanted precision
+ // 0123456789
+ // 0.00
+ // 000,0000
+ if len(formatIndx) == 1 {
+ decimalStr = string(format[formatIndx[0]])
+ precision = len(format) - formatIndx[0] - 1
+ }
+ }
+ }
+
+ // generate sign part
+ var signStr string
+ if n >= 0.000000001 {
+ signStr = positiveStr
+ } else if n <= -0.000000001 {
+ signStr = negativeStr
+ n = -n
+ } else {
+ signStr = ""
+ n = 0.0
+ }
+
+ // split number into integer and fractional parts
+ intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
+
+ // generate integer part string
+ intStr := strconv.FormatInt(int64(intf), 10)
+
+ // add thousand separator if required
+ if len(thousandStr) > 0 {
+ for i := len(intStr); i > 3; {
+ i -= 3
+ intStr = intStr[:i] + thousandStr + intStr[i:]
+ }
+ }
+
+ // no fractional part, we can leave now
+ if precision == 0 {
+ return signStr + intStr
+ }
+
+ // generate fractional part
+ fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
+ // may need padding
+ if len(fracStr) < precision {
+ fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
+ }
+
+ return signStr + intStr + decimalStr + fracStr
+}
+
+// FormatInteger produces a formatted number as string.
+// See FormatFloat.
+func FormatInteger(format string, n int) string {
+ return FormatFloat(format, float64(n))
+}
diff --git a/vendor/github.com/dustin/go-humanize/ordinals.go b/vendor/github.com/dustin/go-humanize/ordinals.go
new file mode 100644
index 00000000..43d88a86
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/ordinals.go
@@ -0,0 +1,25 @@
+package humanize
+
+import "strconv"
+
+// Ordinal gives you the input number in a rank/ordinal format.
+//
+// Ordinal(3) -> 3rd
+func Ordinal(x int) string {
+ suffix := "th"
+ switch x % 10 {
+ case 1:
+ if x%100 != 11 {
+ suffix = "st"
+ }
+ case 2:
+ if x%100 != 12 {
+ suffix = "nd"
+ }
+ case 3:
+ if x%100 != 13 {
+ suffix = "rd"
+ }
+ }
+ return strconv.Itoa(x) + suffix
+}
diff --git a/vendor/github.com/dustin/go-humanize/si.go b/vendor/github.com/dustin/go-humanize/si.go
new file mode 100644
index 00000000..ae659e0e
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/si.go
@@ -0,0 +1,123 @@
+package humanize
+
+import (
+ "errors"
+ "math"
+ "regexp"
+ "strconv"
+)
+
+var siPrefixTable = map[float64]string{
+ -24: "y", // yocto
+ -21: "z", // zepto
+ -18: "a", // atto
+ -15: "f", // femto
+ -12: "p", // pico
+ -9: "n", // nano
+ -6: "ยต", // micro
+ -3: "m", // milli
+ 0: "",
+ 3: "k", // kilo
+ 6: "M", // mega
+ 9: "G", // giga
+ 12: "T", // tera
+ 15: "P", // peta
+ 18: "E", // exa
+ 21: "Z", // zetta
+ 24: "Y", // yotta
+}
+
+var revSIPrefixTable = revfmap(siPrefixTable)
+
+// revfmap reverses the map and precomputes the power multiplier
+func revfmap(in map[float64]string) map[string]float64 {
+ rv := map[string]float64{}
+ for k, v := range in {
+ rv[v] = math.Pow(10, k)
+ }
+ return rv
+}
+
+var riParseRegex *regexp.Regexp
+
+func init() {
+ ri := `^([\-0-9.]+)\s?([`
+ for _, v := range siPrefixTable {
+ ri += v
+ }
+ ri += `]?)(.*)`
+
+ riParseRegex = regexp.MustCompile(ri)
+}
+
+// ComputeSI finds the most appropriate SI prefix for the given number
+// and returns the prefix along with the value adjusted to be within
+// that prefix.
+//
+// See also: SI, ParseSI.
+//
+// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
+func ComputeSI(input float64) (float64, string) {
+ if input == 0 {
+ return 0, ""
+ }
+ mag := math.Abs(input)
+ exponent := math.Floor(logn(mag, 10))
+ exponent = math.Floor(exponent/3) * 3
+
+ value := mag / math.Pow(10, exponent)
+
+ // Handle special case where value is exactly 1000.0
+ // Should return 1 M instead of 1000 k
+ if value == 1000.0 {
+ exponent += 3
+ value = mag / math.Pow(10, exponent)
+ }
+
+ value = math.Copysign(value, input)
+
+ prefix := siPrefixTable[exponent]
+ return value, prefix
+}
+
+// SI returns a string with default formatting.
+//
+// SI uses Ftoa to format float value, removing trailing zeros.
+//
+// See also: ComputeSI, ParseSI.
+//
+// e.g. SI(1000000, "B") -> 1 MB
+// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
+func SI(input float64, unit string) string {
+ value, prefix := ComputeSI(input)
+ return Ftoa(value) + " " + prefix + unit
+}
+
+// SIWithDigits works like SI but limits the resulting string to the
+// given number of decimal places.
+//
+// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
+// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
+func SIWithDigits(input float64, decimals int, unit string) string {
+ value, prefix := ComputeSI(input)
+ return FtoaWithDigits(value, decimals) + " " + prefix + unit
+}
+
+var errInvalid = errors.New("invalid input")
+
+// ParseSI parses an SI string back into the number and unit.
+//
+// See also: SI, ComputeSI.
+//
+// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
+func ParseSI(input string) (float64, string, error) {
+ found := riParseRegex.FindStringSubmatch(input)
+ if len(found) != 4 {
+ return 0, "", errInvalid
+ }
+ mag := revSIPrefixTable[found[2]]
+ unit := found[3]
+
+ base, err := strconv.ParseFloat(found[1], 64)
+ return base * mag, unit, err
+}
diff --git a/vendor/github.com/dustin/go-humanize/times.go b/vendor/github.com/dustin/go-humanize/times.go
new file mode 100644
index 00000000..dd3fbf5e
--- /dev/null
+++ b/vendor/github.com/dustin/go-humanize/times.go
@@ -0,0 +1,117 @@
+package humanize
+
+import (
+ "fmt"
+ "math"
+ "sort"
+ "time"
+)
+
+// Seconds-based time units
+const (
+ Day = 24 * time.Hour
+ Week = 7 * Day
+ Month = 30 * Day
+ Year = 12 * Month
+ LongTime = 37 * Year
+)
+
+// Time formats a time into a relative string.
+//
+// Time(someT) -> "3 weeks ago"
+func Time(then time.Time) string {
+ return RelTime(then, time.Now(), "ago", "from now")
+}
+
+// A RelTimeMagnitude struct contains a relative time point at which
+// the relative format of time will switch to a new format string. A
+// slice of these in ascending order by their "D" field is passed to
+// CustomRelTime to format durations.
+//
+// The Format field is a string that may contain a "%s" which will be
+// replaced with the appropriate signed label (e.g. "ago" or "from
+// now") and a "%d" that will be replaced by the quantity.
+//
+// The DivBy field is the amount of time the time difference must be
+// divided by in order to display correctly.
+//
+// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
+// DivBy should be time.Minute so whatever the duration is will be
+// expressed in minutes.
+type RelTimeMagnitude struct {
+ D time.Duration
+ Format string
+ DivBy time.Duration
+}
+
+var defaultMagnitudes = []RelTimeMagnitude{
+ {time.Second, "now", time.Second},
+ {2 * time.Second, "1 second %s", 1},
+ {time.Minute, "%d seconds %s", time.Second},
+ {2 * time.Minute, "1 minute %s", 1},
+ {time.Hour, "%d minutes %s", time.Minute},
+ {2 * time.Hour, "1 hour %s", 1},
+ {Day, "%d hours %s", time.Hour},
+ {2 * Day, "1 day %s", 1},
+ {Week, "%d days %s", Day},
+ {2 * Week, "1 week %s", 1},
+ {Month, "%d weeks %s", Week},
+ {2 * Month, "1 month %s", 1},
+ {Year, "%d months %s", Month},
+ {18 * Month, "1 year %s", 1},
+ {2 * Year, "2 years %s", 1},
+ {LongTime, "%d years %s", Year},
+ {math.MaxInt64, "a long while %s", 1},
+}
+
+// RelTime formats a time into a relative string.
+//
+// It takes two times and two labels. In addition to the generic time
+// delta string (e.g. 5 minutes), the labels are used applied so that
+// the label corresponding to the smaller time is applied.
+//
+// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
+func RelTime(a, b time.Time, albl, blbl string) string {
+ return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
+}
+
+// CustomRelTime formats a time into a relative string.
+//
+// It takes two times two labels and a table of relative time formats.
+// In addition to the generic time delta string (e.g. 5 minutes), the
+// labels are used applied so that the label corresponding to the
+// smaller time is applied.
+func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
+ lbl := albl
+ diff := b.Sub(a)
+
+ if a.After(b) {
+ lbl = blbl
+ diff = a.Sub(b)
+ }
+
+ n := sort.Search(len(magnitudes), func(i int) bool {
+ return magnitudes[i].D > diff
+ })
+
+ if n >= len(magnitudes) {
+ n = len(magnitudes) - 1
+ }
+ mag := magnitudes[n]
+ args := []interface{}{}
+ escaped := false
+ for _, ch := range mag.Format {
+ if escaped {
+ switch ch {
+ case 's':
+ args = append(args, lbl)
+ case 'd':
+ args = append(args, diff/mag.DivBy)
+ }
+ escaped = false
+ } else {
+ escaped = ch == '%'
+ }
+ }
+ return fmt.Sprintf(mag.Format, args...)
+}
diff --git a/vendor/github.com/dyatlov/go-opengraph/opengraph/opengraph.go b/vendor/github.com/dyatlov/go-opengraph/opengraph/opengraph.go
index 96feb78a..fa18673a 100644
--- a/vendor/github.com/dyatlov/go-opengraph/opengraph/opengraph.go
+++ b/vendor/github.com/dyatlov/go-opengraph/opengraph/opengraph.go
@@ -119,9 +119,6 @@ func (og *OpenGraph) ProcessHTML(buffer io.Reader) error {
return z.Err()
case html.StartTagToken, html.SelfClosingTagToken, html.EndTagToken:
name, hasAttr := z.TagName()
- if atom.Lookup(name) == atom.Body {
- return nil // OpenGraph is only in head, so we don't need body
- }
if atom.Lookup(name) != atom.Meta || !hasAttr {
continue
}
diff --git a/vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml b/vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml
index 8bffb901..764b5418 100644
--- a/vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml
+++ b/vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml
@@ -15,6 +15,7 @@ os:
arch:
- amd64
+ - ppc64le
dist: xenial
@@ -37,3 +38,6 @@ jobs:
script:
- go test -v -cover ./... || go test -v ./...
+matrix:
+ allowfailures:
+ go: 1.2.x
diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go
index b1746163..b404f4be 100644
--- a/vendor/github.com/google/uuid/hash.go
+++ b/vendor/github.com/google/uuid/hash.go
@@ -26,8 +26,8 @@ var (
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
- h.Write(space[:])
- h.Write(data)
+ h.Write(space[:]) //nolint:errcheck
+ h.Write(data) //nolint:errcheck
s := h.Sum(nil)
var uuid UUID
copy(uuid[:], s)
diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go
index f326b54d..2e02ec06 100644
--- a/vendor/github.com/google/uuid/sql.go
+++ b/vendor/github.com/google/uuid/sql.go
@@ -9,7 +9,7 @@ import (
"fmt"
)
-// Scan implements sql.Scanner so UUIDs can be read from databases transparently
+// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go
index 524404cc..60d26bb5 100644
--- a/vendor/github.com/google/uuid/uuid.go
+++ b/vendor/github.com/google/uuid/uuid.go
@@ -35,6 +35,12 @@ const (
var rander = rand.Reader // random function
+type invalidLengthError struct{ len int }
+
+func (err invalidLengthError) Error() string {
+ return fmt.Sprintf("invalid UUID length: %d", err.len)
+}
+
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
@@ -68,7 +74,7 @@ func Parse(s string) (UUID, error) {
}
return uuid, nil
default:
- return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
+ return uuid, invalidLengthError{len(s)}
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
@@ -112,7 +118,7 @@ func ParseBytes(b []byte) (UUID, error) {
}
return uuid, nil
default:
- return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
+ return uuid, invalidLengthError{len(b)}
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go
index c110465d..86160fbd 100644
--- a/vendor/github.com/google/uuid/version4.go
+++ b/vendor/github.com/google/uuid/version4.go
@@ -14,6 +14,14 @@ func New() UUID {
return Must(NewRandom())
}
+// NewString creates a new random UUID and returns it as a string or panics.
+// NewString is equivalent to the expression
+//
+// uuid.New().String()
+func NewString() string {
+ return Must(NewRandom()).String()
+}
+
// NewRandom returns a Random (Version 4) UUID.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
diff --git a/vendor/github.com/hashicorp/go-multierror/.travis.yml b/vendor/github.com/hashicorp/go-multierror/.travis.yml
deleted file mode 100644
index 24b80388..00000000
--- a/vendor/github.com/hashicorp/go-multierror/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-sudo: false
-
-language: go
-
-go:
- - 1.x
-
-branches:
- only:
- - master
-
-script: env GO111MODULE=on make test testrace
diff --git a/vendor/github.com/hashicorp/go-multierror/README.md b/vendor/github.com/hashicorp/go-multierror/README.md
index e92fa614..71dd308e 100644
--- a/vendor/github.com/hashicorp/go-multierror/README.md
+++ b/vendor/github.com/hashicorp/go-multierror/README.md
@@ -1,10 +1,11 @@
# go-multierror
-[![Build Status](http://img.shields.io/travis/hashicorp/go-multierror.svg?style=flat-square)][travis]
-[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs]
+[![CircleCI](https://img.shields.io/circleci/build/github/hashicorp/go-multierror/master)](https://circleci.com/gh/hashicorp/go-multierror)
+[![Go Reference](https://pkg.go.dev/badge/github.com/hashicorp/go-multierror.svg)](https://pkg.go.dev/github.com/hashicorp/go-multierror)
+![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/hashicorp/go-multierror)
-[travis]: https://travis-ci.org/hashicorp/go-multierror
-[godocs]: https://godoc.org/github.com/hashicorp/go-multierror
+[circleci]: https://app.circleci.com/pipelines/github/hashicorp/go-multierror
+[godocs]: https://pkg.go.dev/github.com/hashicorp/go-multierror
`go-multierror` is a package for Go that provides a mechanism for
representing a list of `error` values as a single `error`.
@@ -24,7 +25,25 @@ for introspecting on error values.
Install using `go get github.com/hashicorp/go-multierror`.
Full documentation is available at
-http://godoc.org/github.com/hashicorp/go-multierror
+https://pkg.go.dev/github.com/hashicorp/go-multierror
+
+### Requires go version 1.13 or newer
+
+`go-multierror` requires go version 1.13 or newer. Go 1.13 introduced
+[error wrapping](https://golang.org/doc/go1.13#error_wrapping), which
+this library takes advantage of.
+
+If you need to use an earlier version of go, you can use the
+[v1.0.0](https://github.com/hashicorp/go-multierror/tree/v1.0.0)
+tag, which doesn't rely on features in go 1.13.
+
+If you see compile errors that look like the below, it's likely that
+you're on an older version of go:
+
+```
+/go/src/github.com/hashicorp/go-multierror/multierror.go:112:9: undefined: errors.As
+/go/src/github.com/hashicorp/go-multierror/multierror.go:117:9: undefined: errors.Is
+```
## Usage
diff --git a/vendor/github.com/hashicorp/go-multierror/append.go b/vendor/github.com/hashicorp/go-multierror/append.go
index 775b6e75..3e2589bf 100644
--- a/vendor/github.com/hashicorp/go-multierror/append.go
+++ b/vendor/github.com/hashicorp/go-multierror/append.go
@@ -6,6 +6,8 @@ package multierror
// If err is not a multierror.Error, then it will be turned into
// one. If any of the errs are multierr.Error, they will be flattened
// one level into err.
+// Any nil errors within errs will be ignored. If err is nil, a new
+// *Error will be returned.
func Append(err error, errs ...error) *Error {
switch err := err.(type) {
case *Error:
diff --git a/vendor/github.com/hashicorp/go-multierror/go.mod b/vendor/github.com/hashicorp/go-multierror/go.mod
index 0afe8e6f..141cc4cc 100644
--- a/vendor/github.com/hashicorp/go-multierror/go.mod
+++ b/vendor/github.com/hashicorp/go-multierror/go.mod
@@ -1,5 +1,5 @@
module github.com/hashicorp/go-multierror
-go 1.14
+go 1.13
require github.com/hashicorp/errwrap v1.0.0
diff --git a/vendor/github.com/hashicorp/go-multierror/multierror.go b/vendor/github.com/hashicorp/go-multierror/multierror.go
index d05dd926..f5457432 100644
--- a/vendor/github.com/hashicorp/go-multierror/multierror.go
+++ b/vendor/github.com/hashicorp/go-multierror/multierror.go
@@ -40,14 +40,17 @@ func (e *Error) GoString() string {
return fmt.Sprintf("*%#v", *e)
}
-// WrappedErrors returns the list of errors that this Error is wrapping.
-// It is an implementation of the errwrap.Wrapper interface so that
-// multierror.Error can be used with that library.
+// WrappedErrors returns the list of errors that this Error is wrapping. It is
+// an implementation of the errwrap.Wrapper interface so that multierror.Error
+// can be used with that library.
//
-// This method is not safe to be called concurrently and is no different
-// than accessing the Errors field directly. It is implemented only to
-// satisfy the errwrap.Wrapper interface.
+// This method is not safe to be called concurrently. Unlike accessing the
+// Errors field directly, this function also checks if the multierror is nil to
+// prevent a null-pointer panic. It satisfies the errwrap.Wrapper interface.
func (e *Error) WrappedErrors() []error {
+ if e == nil {
+ return nil
+ }
return e.Errors
}
diff --git a/vendor/github.com/json-iterator/go/.codecov.yml b/vendor/github.com/json-iterator/go/.codecov.yml
new file mode 100644
index 00000000..955dc0be
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/.codecov.yml
@@ -0,0 +1,3 @@
+ignore:
+ - "output_tests/.*"
+
diff --git a/vendor/github.com/json-iterator/go/.gitignore b/vendor/github.com/json-iterator/go/.gitignore
new file mode 100644
index 00000000..15556530
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/.gitignore
@@ -0,0 +1,4 @@
+/vendor
+/bug_test.go
+/coverage.txt
+/.idea
diff --git a/vendor/github.com/json-iterator/go/.travis.yml b/vendor/github.com/json-iterator/go/.travis.yml
new file mode 100644
index 00000000..449e67cd
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+
+go:
+ - 1.8.x
+ - 1.x
+
+before_install:
+ - go get -t -v ./...
+
+script:
+ - ./test.sh
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
diff --git a/vendor/github.com/json-iterator/go/Gopkg.lock b/vendor/github.com/json-iterator/go/Gopkg.lock
new file mode 100644
index 00000000..c8a9fbb3
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/Gopkg.lock
@@ -0,0 +1,21 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/modern-go/concurrent"
+ packages = ["."]
+ revision = "e0a39a4cb4216ea8db28e22a69f4ec25610d513a"
+ version = "1.0.0"
+
+[[projects]]
+ name = "github.com/modern-go/reflect2"
+ packages = ["."]
+ revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
+ version = "1.0.1"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "ea54a775e5a354cb015502d2e7aa4b74230fc77e894f34a838b268c25ec8eeb8"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/vendor/github.com/json-iterator/go/Gopkg.toml b/vendor/github.com/json-iterator/go/Gopkg.toml
new file mode 100644
index 00000000..313a0f88
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/Gopkg.toml
@@ -0,0 +1,26 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+
+ignored = ["github.com/davecgh/go-spew*","github.com/google/gofuzz*","github.com/stretchr/testify*"]
+
+[[constraint]]
+ name = "github.com/modern-go/reflect2"
+ version = "1.0.1"
diff --git a/vendor/github.com/json-iterator/go/LICENSE b/vendor/github.com/json-iterator/go/LICENSE
new file mode 100644
index 00000000..2cf4f5ab
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 json-iterator
+
+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/json-iterator/go/README.md b/vendor/github.com/json-iterator/go/README.md
new file mode 100644
index 00000000..52b111d5
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/README.md
@@ -0,0 +1,87 @@
+[![Sourcegraph](https://sourcegraph.com/github.com/json-iterator/go/-/badge.svg)](https://sourcegraph.com/github.com/json-iterator/go?badge)
+[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/json-iterator/go)
+[![Build Status](https://travis-ci.org/json-iterator/go.svg?branch=master)](https://travis-ci.org/json-iterator/go)
+[![codecov](https://codecov.io/gh/json-iterator/go/branch/master/graph/badge.svg)](https://codecov.io/gh/json-iterator/go)
+[![rcard](https://goreportcard.com/badge/github.com/json-iterator/go)](https://goreportcard.com/report/github.com/json-iterator/go)
+[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/json-iterator/go/master/LICENSE)
+[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/json-iterator/Lobby)
+
+A high-performance 100% compatible drop-in replacement of "encoding/json"
+
+You can also use thrift like JSON using [thrift-iterator](https://github.com/thrift-iterator/go)
+
+# Benchmark
+
+![benchmark](http://jsoniter.com/benchmarks/go-benchmark.png)
+
+Source code: https://github.com/json-iterator/go-benchmark/blob/master/src/github.com/json-iterator/go-benchmark/benchmark_medium_payload_test.go
+
+Raw Result (easyjson requires static code generation)
+
+| | ns/op | allocation bytes | allocation times |
+| --------------- | ----------- | ---------------- | ---------------- |
+| std decode | 35510 ns/op | 1960 B/op | 99 allocs/op |
+| easyjson decode | 8499 ns/op | 160 B/op | 4 allocs/op |
+| jsoniter decode | 5623 ns/op | 160 B/op | 3 allocs/op |
+| std encode | 2213 ns/op | 712 B/op | 5 allocs/op |
+| easyjson encode | 883 ns/op | 576 B/op | 3 allocs/op |
+| jsoniter encode | 837 ns/op | 384 B/op | 4 allocs/op |
+
+Always benchmark with your own workload.
+The result depends heavily on the data input.
+
+# Usage
+
+100% compatibility with standard lib
+
+Replace
+
+```go
+import "encoding/json"
+json.Marshal(&data)
+```
+
+with
+
+```go
+import jsoniter "github.com/json-iterator/go"
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+json.Marshal(&data)
+```
+
+Replace
+
+```go
+import "encoding/json"
+json.Unmarshal(input, &data)
+```
+
+with
+
+```go
+import jsoniter "github.com/json-iterator/go"
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+json.Unmarshal(input, &data)
+```
+
+[More documentation](http://jsoniter.com/migrate-from-go-std.html)
+
+# How to get
+
+```
+go get github.com/json-iterator/go
+```
+
+# Contribution Welcomed !
+
+Contributors
+
+- [thockin](https://github.com/thockin)
+- [mattn](https://github.com/mattn)
+- [cch123](https://github.com/cch123)
+- [Oleg Shaldybin](https://github.com/olegshaldybin)
+- [Jason Toffaletti](https://github.com/toffaletti)
+
+Report issue or pull request, or email taowen@gmail.com, or [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/json-iterator/Lobby)
diff --git a/vendor/github.com/json-iterator/go/adapter.go b/vendor/github.com/json-iterator/go/adapter.go
new file mode 100644
index 00000000..92d2cc4a
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/adapter.go
@@ -0,0 +1,150 @@
+package jsoniter
+
+import (
+ "bytes"
+ "io"
+)
+
+// RawMessage to make replace json with jsoniter
+type RawMessage []byte
+
+// Unmarshal adapts to json/encoding Unmarshal API
+//
+// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
+// Refer to https://godoc.org/encoding/json#Unmarshal for more information
+func Unmarshal(data []byte, v interface{}) error {
+ return ConfigDefault.Unmarshal(data, v)
+}
+
+// UnmarshalFromString is a convenient method to read from string instead of []byte
+func UnmarshalFromString(str string, v interface{}) error {
+ return ConfigDefault.UnmarshalFromString(str, v)
+}
+
+// Get quick method to get value from deeply nested JSON structure
+func Get(data []byte, path ...interface{}) Any {
+ return ConfigDefault.Get(data, path...)
+}
+
+// Marshal adapts to json/encoding Marshal API
+//
+// Marshal returns the JSON encoding of v, adapts to json/encoding Marshal API
+// Refer to https://godoc.org/encoding/json#Marshal for more information
+func Marshal(v interface{}) ([]byte, error) {
+ return ConfigDefault.Marshal(v)
+}
+
+// MarshalIndent same as json.MarshalIndent. Prefix is not supported.
+func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
+ return ConfigDefault.MarshalIndent(v, prefix, indent)
+}
+
+// MarshalToString convenient method to write as string instead of []byte
+func MarshalToString(v interface{}) (string, error) {
+ return ConfigDefault.MarshalToString(v)
+}
+
+// NewDecoder adapts to json/stream NewDecoder API.
+//
+// NewDecoder returns a new decoder that reads from r.
+//
+// Instead of a json/encoding Decoder, an Decoder is returned
+// Refer to https://godoc.org/encoding/json#NewDecoder for more information
+func NewDecoder(reader io.Reader) *Decoder {
+ return ConfigDefault.NewDecoder(reader)
+}
+
+// Decoder reads and decodes JSON values from an input stream.
+// Decoder provides identical APIs with json/stream Decoder (Token() and UseNumber() are in progress)
+type Decoder struct {
+ iter *Iterator
+}
+
+// Decode decode JSON into interface{}
+func (adapter *Decoder) Decode(obj interface{}) error {
+ if adapter.iter.head == adapter.iter.tail && adapter.iter.reader != nil {
+ if !adapter.iter.loadMore() {
+ return io.EOF
+ }
+ }
+ adapter.iter.ReadVal(obj)
+ err := adapter.iter.Error
+ if err == io.EOF {
+ return nil
+ }
+ return adapter.iter.Error
+}
+
+// More is there more?
+func (adapter *Decoder) More() bool {
+ iter := adapter.iter
+ if iter.Error != nil {
+ return false
+ }
+ c := iter.nextToken()
+ if c == 0 {
+ return false
+ }
+ iter.unreadByte()
+ return c != ']' && c != '}'
+}
+
+// Buffered remaining buffer
+func (adapter *Decoder) Buffered() io.Reader {
+ remaining := adapter.iter.buf[adapter.iter.head:adapter.iter.tail]
+ return bytes.NewReader(remaining)
+}
+
+// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
+// Number instead of as a float64.
+func (adapter *Decoder) UseNumber() {
+ cfg := adapter.iter.cfg.configBeforeFrozen
+ cfg.UseNumber = true
+ adapter.iter.cfg = cfg.frozeWithCacheReuse(adapter.iter.cfg.extraExtensions)
+}
+
+// DisallowUnknownFields causes the Decoder to return an error when the destination
+// is a struct and the input contains object keys which do not match any
+// non-ignored, exported fields in the destination.
+func (adapter *Decoder) DisallowUnknownFields() {
+ cfg := adapter.iter.cfg.configBeforeFrozen
+ cfg.DisallowUnknownFields = true
+ adapter.iter.cfg = cfg.frozeWithCacheReuse(adapter.iter.cfg.extraExtensions)
+}
+
+// NewEncoder same as json.NewEncoder
+func NewEncoder(writer io.Writer) *Encoder {
+ return ConfigDefault.NewEncoder(writer)
+}
+
+// Encoder same as json.Encoder
+type Encoder struct {
+ stream *Stream
+}
+
+// Encode encode interface{} as JSON to io.Writer
+func (adapter *Encoder) Encode(val interface{}) error {
+ adapter.stream.WriteVal(val)
+ adapter.stream.WriteRaw("\n")
+ adapter.stream.Flush()
+ return adapter.stream.Error
+}
+
+// SetIndent set the indention. Prefix is not supported
+func (adapter *Encoder) SetIndent(prefix, indent string) {
+ config := adapter.stream.cfg.configBeforeFrozen
+ config.IndentionStep = len(indent)
+ adapter.stream.cfg = config.frozeWithCacheReuse(adapter.stream.cfg.extraExtensions)
+}
+
+// SetEscapeHTML escape html by default, set to false to disable
+func (adapter *Encoder) SetEscapeHTML(escapeHTML bool) {
+ config := adapter.stream.cfg.configBeforeFrozen
+ config.EscapeHTML = escapeHTML
+ adapter.stream.cfg = config.frozeWithCacheReuse(adapter.stream.cfg.extraExtensions)
+}
+
+// Valid reports whether data is a valid JSON encoding.
+func Valid(data []byte) bool {
+ return ConfigDefault.Valid(data)
+}
diff --git a/vendor/github.com/json-iterator/go/any.go b/vendor/github.com/json-iterator/go/any.go
new file mode 100644
index 00000000..f6b8aeab
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any.go
@@ -0,0 +1,325 @@
+package jsoniter
+
+import (
+ "errors"
+ "fmt"
+ "github.com/modern-go/reflect2"
+ "io"
+ "reflect"
+ "strconv"
+ "unsafe"
+)
+
+// Any generic object representation.
+// The lazy json implementation holds []byte and parse lazily.
+type Any interface {
+ LastError() error
+ ValueType() ValueType
+ MustBeValid() Any
+ ToBool() bool
+ ToInt() int
+ ToInt32() int32
+ ToInt64() int64
+ ToUint() uint
+ ToUint32() uint32
+ ToUint64() uint64
+ ToFloat32() float32
+ ToFloat64() float64
+ ToString() string
+ ToVal(val interface{})
+ Get(path ...interface{}) Any
+ Size() int
+ Keys() []string
+ GetInterface() interface{}
+ WriteTo(stream *Stream)
+}
+
+type baseAny struct{}
+
+func (any *baseAny) Get(path ...interface{}) Any {
+ return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)}
+}
+
+func (any *baseAny) Size() int {
+ return 0
+}
+
+func (any *baseAny) Keys() []string {
+ return []string{}
+}
+
+func (any *baseAny) ToVal(obj interface{}) {
+ panic("not implemented")
+}
+
+// WrapInt32 turn int32 into Any interface
+func WrapInt32(val int32) Any {
+ return &int32Any{baseAny{}, val}
+}
+
+// WrapInt64 turn int64 into Any interface
+func WrapInt64(val int64) Any {
+ return &int64Any{baseAny{}, val}
+}
+
+// WrapUint32 turn uint32 into Any interface
+func WrapUint32(val uint32) Any {
+ return &uint32Any{baseAny{}, val}
+}
+
+// WrapUint64 turn uint64 into Any interface
+func WrapUint64(val uint64) Any {
+ return &uint64Any{baseAny{}, val}
+}
+
+// WrapFloat64 turn float64 into Any interface
+func WrapFloat64(val float64) Any {
+ return &floatAny{baseAny{}, val}
+}
+
+// WrapString turn string into Any interface
+func WrapString(val string) Any {
+ return &stringAny{baseAny{}, val}
+}
+
+// Wrap turn a go object into Any interface
+func Wrap(val interface{}) Any {
+ if val == nil {
+ return &nilAny{}
+ }
+ asAny, isAny := val.(Any)
+ if isAny {
+ return asAny
+ }
+ typ := reflect2.TypeOf(val)
+ switch typ.Kind() {
+ case reflect.Slice:
+ return wrapArray(val)
+ case reflect.Struct:
+ return wrapStruct(val)
+ case reflect.Map:
+ return wrapMap(val)
+ case reflect.String:
+ return WrapString(val.(string))
+ case reflect.Int:
+ if strconv.IntSize == 32 {
+ return WrapInt32(int32(val.(int)))
+ }
+ return WrapInt64(int64(val.(int)))
+ case reflect.Int8:
+ return WrapInt32(int32(val.(int8)))
+ case reflect.Int16:
+ return WrapInt32(int32(val.(int16)))
+ case reflect.Int32:
+ return WrapInt32(val.(int32))
+ case reflect.Int64:
+ return WrapInt64(val.(int64))
+ case reflect.Uint:
+ if strconv.IntSize == 32 {
+ return WrapUint32(uint32(val.(uint)))
+ }
+ return WrapUint64(uint64(val.(uint)))
+ case reflect.Uintptr:
+ if ptrSize == 32 {
+ return WrapUint32(uint32(val.(uintptr)))
+ }
+ return WrapUint64(uint64(val.(uintptr)))
+ case reflect.Uint8:
+ return WrapUint32(uint32(val.(uint8)))
+ case reflect.Uint16:
+ return WrapUint32(uint32(val.(uint16)))
+ case reflect.Uint32:
+ return WrapUint32(uint32(val.(uint32)))
+ case reflect.Uint64:
+ return WrapUint64(val.(uint64))
+ case reflect.Float32:
+ return WrapFloat64(float64(val.(float32)))
+ case reflect.Float64:
+ return WrapFloat64(val.(float64))
+ case reflect.Bool:
+ if val.(bool) == true {
+ return &trueAny{}
+ }
+ return &falseAny{}
+ }
+ return &invalidAny{baseAny{}, fmt.Errorf("unsupported type: %v", typ)}
+}
+
+// ReadAny read next JSON element as an Any object. It is a better json.RawMessage.
+func (iter *Iterator) ReadAny() Any {
+ return iter.readAny()
+}
+
+func (iter *Iterator) readAny() Any {
+ c := iter.nextToken()
+ switch c {
+ case '"':
+ iter.unreadByte()
+ return &stringAny{baseAny{}, iter.ReadString()}
+ case 'n':
+ iter.skipThreeBytes('u', 'l', 'l') // null
+ return &nilAny{}
+ case 't':
+ iter.skipThreeBytes('r', 'u', 'e') // true
+ return &trueAny{}
+ case 'f':
+ iter.skipFourBytes('a', 'l', 's', 'e') // false
+ return &falseAny{}
+ case '{':
+ return iter.readObjectAny()
+ case '[':
+ return iter.readArrayAny()
+ case '-':
+ return iter.readNumberAny(false)
+ case 0:
+ return &invalidAny{baseAny{}, errors.New("input is empty")}
+ default:
+ return iter.readNumberAny(true)
+ }
+}
+
+func (iter *Iterator) readNumberAny(positive bool) Any {
+ iter.startCapture(iter.head - 1)
+ iter.skipNumber()
+ lazyBuf := iter.stopCapture()
+ return &numberLazyAny{baseAny{}, iter.cfg, lazyBuf, nil}
+}
+
+func (iter *Iterator) readObjectAny() Any {
+ iter.startCapture(iter.head - 1)
+ iter.skipObject()
+ lazyBuf := iter.stopCapture()
+ return &objectLazyAny{baseAny{}, iter.cfg, lazyBuf, nil}
+}
+
+func (iter *Iterator) readArrayAny() Any {
+ iter.startCapture(iter.head - 1)
+ iter.skipArray()
+ lazyBuf := iter.stopCapture()
+ return &arrayLazyAny{baseAny{}, iter.cfg, lazyBuf, nil}
+}
+
+func locateObjectField(iter *Iterator, target string) []byte {
+ var found []byte
+ iter.ReadObjectCB(func(iter *Iterator, field string) bool {
+ if field == target {
+ found = iter.SkipAndReturnBytes()
+ return false
+ }
+ iter.Skip()
+ return true
+ })
+ return found
+}
+
+func locateArrayElement(iter *Iterator, target int) []byte {
+ var found []byte
+ n := 0
+ iter.ReadArrayCB(func(iter *Iterator) bool {
+ if n == target {
+ found = iter.SkipAndReturnBytes()
+ return false
+ }
+ iter.Skip()
+ n++
+ return true
+ })
+ return found
+}
+
+func locatePath(iter *Iterator, path []interface{}) Any {
+ for i, pathKeyObj := range path {
+ switch pathKey := pathKeyObj.(type) {
+ case string:
+ valueBytes := locateObjectField(iter, pathKey)
+ if valueBytes == nil {
+ return newInvalidAny(path[i:])
+ }
+ iter.ResetBytes(valueBytes)
+ case int:
+ valueBytes := locateArrayElement(iter, pathKey)
+ if valueBytes == nil {
+ return newInvalidAny(path[i:])
+ }
+ iter.ResetBytes(valueBytes)
+ case int32:
+ if '*' == pathKey {
+ return iter.readAny().Get(path[i:]...)
+ }
+ return newInvalidAny(path[i:])
+ default:
+ return newInvalidAny(path[i:])
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF {
+ return &invalidAny{baseAny{}, iter.Error}
+ }
+ return iter.readAny()
+}
+
+var anyType = reflect2.TypeOfPtr((*Any)(nil)).Elem()
+
+func createDecoderOfAny(ctx *ctx, typ reflect2.Type) ValDecoder {
+ if typ == anyType {
+ return &directAnyCodec{}
+ }
+ if typ.Implements(anyType) {
+ return &anyCodec{
+ valType: typ,
+ }
+ }
+ return nil
+}
+
+func createEncoderOfAny(ctx *ctx, typ reflect2.Type) ValEncoder {
+ if typ == anyType {
+ return &directAnyCodec{}
+ }
+ if typ.Implements(anyType) {
+ return &anyCodec{
+ valType: typ,
+ }
+ }
+ return nil
+}
+
+type anyCodec struct {
+ valType reflect2.Type
+}
+
+func (codec *anyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ panic("not implemented")
+}
+
+func (codec *anyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ obj := codec.valType.UnsafeIndirect(ptr)
+ any := obj.(Any)
+ any.WriteTo(stream)
+}
+
+func (codec *anyCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ obj := codec.valType.UnsafeIndirect(ptr)
+ any := obj.(Any)
+ return any.Size() == 0
+}
+
+type directAnyCodec struct {
+}
+
+func (codec *directAnyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ *(*Any)(ptr) = iter.readAny()
+}
+
+func (codec *directAnyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ any := *(*Any)(ptr)
+ if any == nil {
+ stream.WriteNil()
+ return
+ }
+ any.WriteTo(stream)
+}
+
+func (codec *directAnyCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ any := *(*Any)(ptr)
+ return any.Size() == 0
+}
diff --git a/vendor/github.com/json-iterator/go/any_array.go b/vendor/github.com/json-iterator/go/any_array.go
new file mode 100644
index 00000000..0449e9aa
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_array.go
@@ -0,0 +1,278 @@
+package jsoniter
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type arrayLazyAny struct {
+ baseAny
+ cfg *frozenConfig
+ buf []byte
+ err error
+}
+
+func (any *arrayLazyAny) ValueType() ValueType {
+ return ArrayValue
+}
+
+func (any *arrayLazyAny) MustBeValid() Any {
+ return any
+}
+
+func (any *arrayLazyAny) LastError() error {
+ return any.err
+}
+
+func (any *arrayLazyAny) ToBool() bool {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ return iter.ReadArray()
+}
+
+func (any *arrayLazyAny) ToInt() int {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToInt32() int32 {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToInt64() int64 {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToUint() uint {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToUint32() uint32 {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToUint64() uint64 {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToFloat32() float32 {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToFloat64() float64 {
+ if any.ToBool() {
+ return 1
+ }
+ return 0
+}
+
+func (any *arrayLazyAny) ToString() string {
+ return *(*string)(unsafe.Pointer(&any.buf))
+}
+
+func (any *arrayLazyAny) ToVal(val interface{}) {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ iter.ReadVal(val)
+}
+
+func (any *arrayLazyAny) Get(path ...interface{}) Any {
+ if len(path) == 0 {
+ return any
+ }
+ switch firstPath := path[0].(type) {
+ case int:
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ valueBytes := locateArrayElement(iter, firstPath)
+ if valueBytes == nil {
+ return newInvalidAny(path)
+ }
+ iter.ResetBytes(valueBytes)
+ return locatePath(iter, path[1:])
+ case int32:
+ if '*' == firstPath {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ arr := make([]Any, 0)
+ iter.ReadArrayCB(func(iter *Iterator) bool {
+ found := iter.readAny().Get(path[1:]...)
+ if found.ValueType() != InvalidValue {
+ arr = append(arr, found)
+ }
+ return true
+ })
+ return wrapArray(arr)
+ }
+ return newInvalidAny(path)
+ default:
+ return newInvalidAny(path)
+ }
+}
+
+func (any *arrayLazyAny) Size() int {
+ size := 0
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ iter.ReadArrayCB(func(iter *Iterator) bool {
+ size++
+ iter.Skip()
+ return true
+ })
+ return size
+}
+
+func (any *arrayLazyAny) WriteTo(stream *Stream) {
+ stream.Write(any.buf)
+}
+
+func (any *arrayLazyAny) GetInterface() interface{} {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ return iter.Read()
+}
+
+type arrayAny struct {
+ baseAny
+ val reflect.Value
+}
+
+func wrapArray(val interface{}) *arrayAny {
+ return &arrayAny{baseAny{}, reflect.ValueOf(val)}
+}
+
+func (any *arrayAny) ValueType() ValueType {
+ return ArrayValue
+}
+
+func (any *arrayAny) MustBeValid() Any {
+ return any
+}
+
+func (any *arrayAny) LastError() error {
+ return nil
+}
+
+func (any *arrayAny) ToBool() bool {
+ return any.val.Len() != 0
+}
+
+func (any *arrayAny) ToInt() int {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToInt32() int32 {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToInt64() int64 {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToUint() uint {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToUint32() uint32 {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToUint64() uint64 {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToFloat32() float32 {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToFloat64() float64 {
+ if any.val.Len() == 0 {
+ return 0
+ }
+ return 1
+}
+
+func (any *arrayAny) ToString() string {
+ str, _ := MarshalToString(any.val.Interface())
+ return str
+}
+
+func (any *arrayAny) Get(path ...interface{}) Any {
+ if len(path) == 0 {
+ return any
+ }
+ switch firstPath := path[0].(type) {
+ case int:
+ if firstPath < 0 || firstPath >= any.val.Len() {
+ return newInvalidAny(path)
+ }
+ return Wrap(any.val.Index(firstPath).Interface())
+ case int32:
+ if '*' == firstPath {
+ mappedAll := make([]Any, 0)
+ for i := 0; i < any.val.Len(); i++ {
+ mapped := Wrap(any.val.Index(i).Interface()).Get(path[1:]...)
+ if mapped.ValueType() != InvalidValue {
+ mappedAll = append(mappedAll, mapped)
+ }
+ }
+ return wrapArray(mappedAll)
+ }
+ return newInvalidAny(path)
+ default:
+ return newInvalidAny(path)
+ }
+}
+
+func (any *arrayAny) Size() int {
+ return any.val.Len()
+}
+
+func (any *arrayAny) WriteTo(stream *Stream) {
+ stream.WriteVal(any.val)
+}
+
+func (any *arrayAny) GetInterface() interface{} {
+ return any.val.Interface()
+}
diff --git a/vendor/github.com/json-iterator/go/any_bool.go b/vendor/github.com/json-iterator/go/any_bool.go
new file mode 100644
index 00000000..9452324a
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_bool.go
@@ -0,0 +1,137 @@
+package jsoniter
+
+type trueAny struct {
+ baseAny
+}
+
+func (any *trueAny) LastError() error {
+ return nil
+}
+
+func (any *trueAny) ToBool() bool {
+ return true
+}
+
+func (any *trueAny) ToInt() int {
+ return 1
+}
+
+func (any *trueAny) ToInt32() int32 {
+ return 1
+}
+
+func (any *trueAny) ToInt64() int64 {
+ return 1
+}
+
+func (any *trueAny) ToUint() uint {
+ return 1
+}
+
+func (any *trueAny) ToUint32() uint32 {
+ return 1
+}
+
+func (any *trueAny) ToUint64() uint64 {
+ return 1
+}
+
+func (any *trueAny) ToFloat32() float32 {
+ return 1
+}
+
+func (any *trueAny) ToFloat64() float64 {
+ return 1
+}
+
+func (any *trueAny) ToString() string {
+ return "true"
+}
+
+func (any *trueAny) WriteTo(stream *Stream) {
+ stream.WriteTrue()
+}
+
+func (any *trueAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *trueAny) GetInterface() interface{} {
+ return true
+}
+
+func (any *trueAny) ValueType() ValueType {
+ return BoolValue
+}
+
+func (any *trueAny) MustBeValid() Any {
+ return any
+}
+
+type falseAny struct {
+ baseAny
+}
+
+func (any *falseAny) LastError() error {
+ return nil
+}
+
+func (any *falseAny) ToBool() bool {
+ return false
+}
+
+func (any *falseAny) ToInt() int {
+ return 0
+}
+
+func (any *falseAny) ToInt32() int32 {
+ return 0
+}
+
+func (any *falseAny) ToInt64() int64 {
+ return 0
+}
+
+func (any *falseAny) ToUint() uint {
+ return 0
+}
+
+func (any *falseAny) ToUint32() uint32 {
+ return 0
+}
+
+func (any *falseAny) ToUint64() uint64 {
+ return 0
+}
+
+func (any *falseAny) ToFloat32() float32 {
+ return 0
+}
+
+func (any *falseAny) ToFloat64() float64 {
+ return 0
+}
+
+func (any *falseAny) ToString() string {
+ return "false"
+}
+
+func (any *falseAny) WriteTo(stream *Stream) {
+ stream.WriteFalse()
+}
+
+func (any *falseAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *falseAny) GetInterface() interface{} {
+ return false
+}
+
+func (any *falseAny) ValueType() ValueType {
+ return BoolValue
+}
+
+func (any *falseAny) MustBeValid() Any {
+ return any
+}
diff --git a/vendor/github.com/json-iterator/go/any_float.go b/vendor/github.com/json-iterator/go/any_float.go
new file mode 100644
index 00000000..35fdb094
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_float.go
@@ -0,0 +1,83 @@
+package jsoniter
+
+import (
+ "strconv"
+)
+
+type floatAny struct {
+ baseAny
+ val float64
+}
+
+func (any *floatAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *floatAny) ValueType() ValueType {
+ return NumberValue
+}
+
+func (any *floatAny) MustBeValid() Any {
+ return any
+}
+
+func (any *floatAny) LastError() error {
+ return nil
+}
+
+func (any *floatAny) ToBool() bool {
+ return any.ToFloat64() != 0
+}
+
+func (any *floatAny) ToInt() int {
+ return int(any.val)
+}
+
+func (any *floatAny) ToInt32() int32 {
+ return int32(any.val)
+}
+
+func (any *floatAny) ToInt64() int64 {
+ return int64(any.val)
+}
+
+func (any *floatAny) ToUint() uint {
+ if any.val > 0 {
+ return uint(any.val)
+ }
+ return 0
+}
+
+func (any *floatAny) ToUint32() uint32 {
+ if any.val > 0 {
+ return uint32(any.val)
+ }
+ return 0
+}
+
+func (any *floatAny) ToUint64() uint64 {
+ if any.val > 0 {
+ return uint64(any.val)
+ }
+ return 0
+}
+
+func (any *floatAny) ToFloat32() float32 {
+ return float32(any.val)
+}
+
+func (any *floatAny) ToFloat64() float64 {
+ return any.val
+}
+
+func (any *floatAny) ToString() string {
+ return strconv.FormatFloat(any.val, 'E', -1, 64)
+}
+
+func (any *floatAny) WriteTo(stream *Stream) {
+ stream.WriteFloat64(any.val)
+}
+
+func (any *floatAny) GetInterface() interface{} {
+ return any.val
+}
diff --git a/vendor/github.com/json-iterator/go/any_int32.go b/vendor/github.com/json-iterator/go/any_int32.go
new file mode 100644
index 00000000..1b56f399
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_int32.go
@@ -0,0 +1,74 @@
+package jsoniter
+
+import (
+ "strconv"
+)
+
+type int32Any struct {
+ baseAny
+ val int32
+}
+
+func (any *int32Any) LastError() error {
+ return nil
+}
+
+func (any *int32Any) ValueType() ValueType {
+ return NumberValue
+}
+
+func (any *int32Any) MustBeValid() Any {
+ return any
+}
+
+func (any *int32Any) ToBool() bool {
+ return any.val != 0
+}
+
+func (any *int32Any) ToInt() int {
+ return int(any.val)
+}
+
+func (any *int32Any) ToInt32() int32 {
+ return any.val
+}
+
+func (any *int32Any) ToInt64() int64 {
+ return int64(any.val)
+}
+
+func (any *int32Any) ToUint() uint {
+ return uint(any.val)
+}
+
+func (any *int32Any) ToUint32() uint32 {
+ return uint32(any.val)
+}
+
+func (any *int32Any) ToUint64() uint64 {
+ return uint64(any.val)
+}
+
+func (any *int32Any) ToFloat32() float32 {
+ return float32(any.val)
+}
+
+func (any *int32Any) ToFloat64() float64 {
+ return float64(any.val)
+}
+
+func (any *int32Any) ToString() string {
+ return strconv.FormatInt(int64(any.val), 10)
+}
+
+func (any *int32Any) WriteTo(stream *Stream) {
+ stream.WriteInt32(any.val)
+}
+
+func (any *int32Any) Parse() *Iterator {
+ return nil
+}
+
+func (any *int32Any) GetInterface() interface{} {
+ return any.val
+}
diff --git a/vendor/github.com/json-iterator/go/any_int64.go b/vendor/github.com/json-iterator/go/any_int64.go
new file mode 100644
index 00000000..c440d72b
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_int64.go
@@ -0,0 +1,74 @@
+package jsoniter
+
+import (
+ "strconv"
+)
+
+type int64Any struct {
+ baseAny
+ val int64
+}
+
+func (any *int64Any) LastError() error {
+ return nil
+}
+
+func (any *int64Any) ValueType() ValueType {
+ return NumberValue
+}
+
+func (any *int64Any) MustBeValid() Any {
+ return any
+}
+
+func (any *int64Any) ToBool() bool {
+ return any.val != 0
+}
+
+func (any *int64Any) ToInt() int {
+ return int(any.val)
+}
+
+func (any *int64Any) ToInt32() int32 {
+ return int32(any.val)
+}
+
+func (any *int64Any) ToInt64() int64 {
+ return any.val
+}
+
+func (any *int64Any) ToUint() uint {
+ return uint(any.val)
+}
+
+func (any *int64Any) ToUint32() uint32 {
+ return uint32(any.val)
+}
+
+func (any *int64Any) ToUint64() uint64 {
+ return uint64(any.val)
+}
+
+func (any *int64Any) ToFloat32() float32 {
+ return float32(any.val)
+}
+
+func (any *int64Any) ToFloat64() float64 {
+ return float64(any.val)
+}
+
+func (any *int64Any) ToString() string {
+ return strconv.FormatInt(any.val, 10)
+}
+
+func (any *int64Any) WriteTo(stream *Stream) {
+ stream.WriteInt64(any.val)
+}
+
+func (any *int64Any) Parse() *Iterator {
+ return nil
+}
+
+func (any *int64Any) GetInterface() interface{} {
+ return any.val
+}
diff --git a/vendor/github.com/json-iterator/go/any_invalid.go b/vendor/github.com/json-iterator/go/any_invalid.go
new file mode 100644
index 00000000..1d859eac
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_invalid.go
@@ -0,0 +1,82 @@
+package jsoniter
+
+import "fmt"
+
+type invalidAny struct {
+ baseAny
+ err error
+}
+
+func newInvalidAny(path []interface{}) *invalidAny {
+ return &invalidAny{baseAny{}, fmt.Errorf("%v not found", path)}
+}
+
+func (any *invalidAny) LastError() error {
+ return any.err
+}
+
+func (any *invalidAny) ValueType() ValueType {
+ return InvalidValue
+}
+
+func (any *invalidAny) MustBeValid() Any {
+ panic(any.err)
+}
+
+func (any *invalidAny) ToBool() bool {
+ return false
+}
+
+func (any *invalidAny) ToInt() int {
+ return 0
+}
+
+func (any *invalidAny) ToInt32() int32 {
+ return 0
+}
+
+func (any *invalidAny) ToInt64() int64 {
+ return 0
+}
+
+func (any *invalidAny) ToUint() uint {
+ return 0
+}
+
+func (any *invalidAny) ToUint32() uint32 {
+ return 0
+}
+
+func (any *invalidAny) ToUint64() uint64 {
+ return 0
+}
+
+func (any *invalidAny) ToFloat32() float32 {
+ return 0
+}
+
+func (any *invalidAny) ToFloat64() float64 {
+ return 0
+}
+
+func (any *invalidAny) ToString() string {
+ return ""
+}
+
+func (any *invalidAny) WriteTo(stream *Stream) {
+}
+
+func (any *invalidAny) Get(path ...interface{}) Any {
+ if any.err == nil {
+ return &invalidAny{baseAny{}, fmt.Errorf("get %v from invalid", path)}
+ }
+ return &invalidAny{baseAny{}, fmt.Errorf("%v, get %v from invalid", any.err, path)}
+}
+
+func (any *invalidAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *invalidAny) GetInterface() interface{} {
+ return nil
+}
diff --git a/vendor/github.com/json-iterator/go/any_nil.go b/vendor/github.com/json-iterator/go/any_nil.go
new file mode 100644
index 00000000..d04cb54c
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_nil.go
@@ -0,0 +1,69 @@
+package jsoniter
+
+type nilAny struct {
+ baseAny
+}
+
+func (any *nilAny) LastError() error {
+ return nil
+}
+
+func (any *nilAny) ValueType() ValueType {
+ return NilValue
+}
+
+func (any *nilAny) MustBeValid() Any {
+ return any
+}
+
+func (any *nilAny) ToBool() bool {
+ return false
+}
+
+func (any *nilAny) ToInt() int {
+ return 0
+}
+
+func (any *nilAny) ToInt32() int32 {
+ return 0
+}
+
+func (any *nilAny) ToInt64() int64 {
+ return 0
+}
+
+func (any *nilAny) ToUint() uint {
+ return 0
+}
+
+func (any *nilAny) ToUint32() uint32 {
+ return 0
+}
+
+func (any *nilAny) ToUint64() uint64 {
+ return 0
+}
+
+func (any *nilAny) ToFloat32() float32 {
+ return 0
+}
+
+func (any *nilAny) ToFloat64() float64 {
+ return 0
+}
+
+func (any *nilAny) ToString() string {
+ return ""
+}
+
+func (any *nilAny) WriteTo(stream *Stream) {
+ stream.WriteNil()
+}
+
+func (any *nilAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *nilAny) GetInterface() interface{} {
+ return nil
+}
diff --git a/vendor/github.com/json-iterator/go/any_number.go b/vendor/github.com/json-iterator/go/any_number.go
new file mode 100644
index 00000000..9d1e901a
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_number.go
@@ -0,0 +1,123 @@
+package jsoniter
+
+import (
+ "io"
+ "unsafe"
+)
+
+type numberLazyAny struct {
+ baseAny
+ cfg *frozenConfig
+ buf []byte
+ err error
+}
+
+func (any *numberLazyAny) ValueType() ValueType {
+ return NumberValue
+}
+
+func (any *numberLazyAny) MustBeValid() Any {
+ return any
+}
+
+func (any *numberLazyAny) LastError() error {
+ return any.err
+}
+
+func (any *numberLazyAny) ToBool() bool {
+ return any.ToFloat64() != 0
+}
+
+func (any *numberLazyAny) ToInt() int {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadInt()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToInt32() int32 {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadInt32()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToInt64() int64 {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadInt64()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToUint() uint {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadUint()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToUint32() uint32 {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadUint32()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToUint64() uint64 {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadUint64()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToFloat32() float32 {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadFloat32()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToFloat64() float64 {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ val := iter.ReadFloat64()
+ if iter.Error != nil && iter.Error != io.EOF {
+ any.err = iter.Error
+ }
+ return val
+}
+
+func (any *numberLazyAny) ToString() string {
+ return *(*string)(unsafe.Pointer(&any.buf))
+}
+
+func (any *numberLazyAny) WriteTo(stream *Stream) {
+ stream.Write(any.buf)
+}
+
+func (any *numberLazyAny) GetInterface() interface{} {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ return iter.Read()
+}
diff --git a/vendor/github.com/json-iterator/go/any_object.go b/vendor/github.com/json-iterator/go/any_object.go
new file mode 100644
index 00000000..c44ef5c9
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_object.go
@@ -0,0 +1,374 @@
+package jsoniter
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type objectLazyAny struct {
+ baseAny
+ cfg *frozenConfig
+ buf []byte
+ err error
+}
+
+func (any *objectLazyAny) ValueType() ValueType {
+ return ObjectValue
+}
+
+func (any *objectLazyAny) MustBeValid() Any {
+ return any
+}
+
+func (any *objectLazyAny) LastError() error {
+ return any.err
+}
+
+func (any *objectLazyAny) ToBool() bool {
+ return true
+}
+
+func (any *objectLazyAny) ToInt() int {
+ return 0
+}
+
+func (any *objectLazyAny) ToInt32() int32 {
+ return 0
+}
+
+func (any *objectLazyAny) ToInt64() int64 {
+ return 0
+}
+
+func (any *objectLazyAny) ToUint() uint {
+ return 0
+}
+
+func (any *objectLazyAny) ToUint32() uint32 {
+ return 0
+}
+
+func (any *objectLazyAny) ToUint64() uint64 {
+ return 0
+}
+
+func (any *objectLazyAny) ToFloat32() float32 {
+ return 0
+}
+
+func (any *objectLazyAny) ToFloat64() float64 {
+ return 0
+}
+
+func (any *objectLazyAny) ToString() string {
+ return *(*string)(unsafe.Pointer(&any.buf))
+}
+
+func (any *objectLazyAny) ToVal(obj interface{}) {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ iter.ReadVal(obj)
+}
+
+func (any *objectLazyAny) Get(path ...interface{}) Any {
+ if len(path) == 0 {
+ return any
+ }
+ switch firstPath := path[0].(type) {
+ case string:
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ valueBytes := locateObjectField(iter, firstPath)
+ if valueBytes == nil {
+ return newInvalidAny(path)
+ }
+ iter.ResetBytes(valueBytes)
+ return locatePath(iter, path[1:])
+ case int32:
+ if '*' == firstPath {
+ mappedAll := map[string]Any{}
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ iter.ReadMapCB(func(iter *Iterator, field string) bool {
+ mapped := locatePath(iter, path[1:])
+ if mapped.ValueType() != InvalidValue {
+ mappedAll[field] = mapped
+ }
+ return true
+ })
+ return wrapMap(mappedAll)
+ }
+ return newInvalidAny(path)
+ default:
+ return newInvalidAny(path)
+ }
+}
+
+func (any *objectLazyAny) Keys() []string {
+ keys := []string{}
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ iter.ReadMapCB(func(iter *Iterator, field string) bool {
+ iter.Skip()
+ keys = append(keys, field)
+ return true
+ })
+ return keys
+}
+
+func (any *objectLazyAny) Size() int {
+ size := 0
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ iter.ReadObjectCB(func(iter *Iterator, field string) bool {
+ iter.Skip()
+ size++
+ return true
+ })
+ return size
+}
+
+func (any *objectLazyAny) WriteTo(stream *Stream) {
+ stream.Write(any.buf)
+}
+
+func (any *objectLazyAny) GetInterface() interface{} {
+ iter := any.cfg.BorrowIterator(any.buf)
+ defer any.cfg.ReturnIterator(iter)
+ return iter.Read()
+}
+
+type objectAny struct {
+ baseAny
+ err error
+ val reflect.Value
+}
+
+func wrapStruct(val interface{}) *objectAny {
+ return &objectAny{baseAny{}, nil, reflect.ValueOf(val)}
+}
+
+func (any *objectAny) ValueType() ValueType {
+ return ObjectValue
+}
+
+func (any *objectAny) MustBeValid() Any {
+ return any
+}
+
+func (any *objectAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *objectAny) LastError() error {
+ return any.err
+}
+
+func (any *objectAny) ToBool() bool {
+ return any.val.NumField() != 0
+}
+
+func (any *objectAny) ToInt() int {
+ return 0
+}
+
+func (any *objectAny) ToInt32() int32 {
+ return 0
+}
+
+func (any *objectAny) ToInt64() int64 {
+ return 0
+}
+
+func (any *objectAny) ToUint() uint {
+ return 0
+}
+
+func (any *objectAny) ToUint32() uint32 {
+ return 0
+}
+
+func (any *objectAny) ToUint64() uint64 {
+ return 0
+}
+
+func (any *objectAny) ToFloat32() float32 {
+ return 0
+}
+
+func (any *objectAny) ToFloat64() float64 {
+ return 0
+}
+
+func (any *objectAny) ToString() string {
+ str, err := MarshalToString(any.val.Interface())
+ any.err = err
+ return str
+}
+
+func (any *objectAny) Get(path ...interface{}) Any {
+ if len(path) == 0 {
+ return any
+ }
+ switch firstPath := path[0].(type) {
+ case string:
+ field := any.val.FieldByName(firstPath)
+ if !field.IsValid() {
+ return newInvalidAny(path)
+ }
+ return Wrap(field.Interface())
+ case int32:
+ if '*' == firstPath {
+ mappedAll := map[string]Any{}
+ for i := 0; i < any.val.NumField(); i++ {
+ field := any.val.Field(i)
+ if field.CanInterface() {
+ mapped := Wrap(field.Interface()).Get(path[1:]...)
+ if mapped.ValueType() != InvalidValue {
+ mappedAll[any.val.Type().Field(i).Name] = mapped
+ }
+ }
+ }
+ return wrapMap(mappedAll)
+ }
+ return newInvalidAny(path)
+ default:
+ return newInvalidAny(path)
+ }
+}
+
+func (any *objectAny) Keys() []string {
+ keys := make([]string, 0, any.val.NumField())
+ for i := 0; i < any.val.NumField(); i++ {
+ keys = append(keys, any.val.Type().Field(i).Name)
+ }
+ return keys
+}
+
+func (any *objectAny) Size() int {
+ return any.val.NumField()
+}
+
+func (any *objectAny) WriteTo(stream *Stream) {
+ stream.WriteVal(any.val)
+}
+
+func (any *objectAny) GetInterface() interface{} {
+ return any.val.Interface()
+}
+
+type mapAny struct {
+ baseAny
+ err error
+ val reflect.Value
+}
+
+func wrapMap(val interface{}) *mapAny {
+ return &mapAny{baseAny{}, nil, reflect.ValueOf(val)}
+}
+
+func (any *mapAny) ValueType() ValueType {
+ return ObjectValue
+}
+
+func (any *mapAny) MustBeValid() Any {
+ return any
+}
+
+func (any *mapAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *mapAny) LastError() error {
+ return any.err
+}
+
+func (any *mapAny) ToBool() bool {
+ return true
+}
+
+func (any *mapAny) ToInt() int {
+ return 0
+}
+
+func (any *mapAny) ToInt32() int32 {
+ return 0
+}
+
+func (any *mapAny) ToInt64() int64 {
+ return 0
+}
+
+func (any *mapAny) ToUint() uint {
+ return 0
+}
+
+func (any *mapAny) ToUint32() uint32 {
+ return 0
+}
+
+func (any *mapAny) ToUint64() uint64 {
+ return 0
+}
+
+func (any *mapAny) ToFloat32() float32 {
+ return 0
+}
+
+func (any *mapAny) ToFloat64() float64 {
+ return 0
+}
+
+func (any *mapAny) ToString() string {
+ str, err := MarshalToString(any.val.Interface())
+ any.err = err
+ return str
+}
+
+func (any *mapAny) Get(path ...interface{}) Any {
+ if len(path) == 0 {
+ return any
+ }
+ switch firstPath := path[0].(type) {
+ case int32:
+ if '*' == firstPath {
+ mappedAll := map[string]Any{}
+ for _, key := range any.val.MapKeys() {
+ keyAsStr := key.String()
+ element := Wrap(any.val.MapIndex(key).Interface())
+ mapped := element.Get(path[1:]...)
+ if mapped.ValueType() != InvalidValue {
+ mappedAll[keyAsStr] = mapped
+ }
+ }
+ return wrapMap(mappedAll)
+ }
+ return newInvalidAny(path)
+ default:
+ value := any.val.MapIndex(reflect.ValueOf(firstPath))
+ if !value.IsValid() {
+ return newInvalidAny(path)
+ }
+ return Wrap(value.Interface())
+ }
+}
+
+func (any *mapAny) Keys() []string {
+ keys := make([]string, 0, any.val.Len())
+ for _, key := range any.val.MapKeys() {
+ keys = append(keys, key.String())
+ }
+ return keys
+}
+
+func (any *mapAny) Size() int {
+ return any.val.Len()
+}
+
+func (any *mapAny) WriteTo(stream *Stream) {
+ stream.WriteVal(any.val)
+}
+
+func (any *mapAny) GetInterface() interface{} {
+ return any.val.Interface()
+}
diff --git a/vendor/github.com/json-iterator/go/any_str.go b/vendor/github.com/json-iterator/go/any_str.go
new file mode 100644
index 00000000..1f12f661
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_str.go
@@ -0,0 +1,166 @@
+package jsoniter
+
+import (
+ "fmt"
+ "strconv"
+)
+
+type stringAny struct {
+ baseAny
+ val string
+}
+
+func (any *stringAny) Get(path ...interface{}) Any {
+ if len(path) == 0 {
+ return any
+ }
+ return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)}
+}
+
+func (any *stringAny) Parse() *Iterator {
+ return nil
+}
+
+func (any *stringAny) ValueType() ValueType {
+ return StringValue
+}
+
+func (any *stringAny) MustBeValid() Any {
+ return any
+}
+
+func (any *stringAny) LastError() error {
+ return nil
+}
+
+func (any *stringAny) ToBool() bool {
+ str := any.ToString()
+ if str == "0" {
+ return false
+ }
+ for _, c := range str {
+ switch c {
+ case ' ', '\n', '\r', '\t':
+ default:
+ return true
+ }
+ }
+ return false
+}
+
+func (any *stringAny) ToInt() int {
+ return int(any.ToInt64())
+
+}
+
+func (any *stringAny) ToInt32() int32 {
+ return int32(any.ToInt64())
+}
+
+func (any *stringAny) ToInt64() int64 {
+ if any.val == "" {
+ return 0
+ }
+
+ flag := 1
+ startPos := 0
+ if any.val[0] == '+' || any.val[0] == '-' {
+ startPos = 1
+ }
+
+ if any.val[0] == '-' {
+ flag = -1
+ }
+
+ endPos := startPos
+ for i := startPos; i < len(any.val); i++ {
+ if any.val[i] >= '0' && any.val[i] <= '9' {
+ endPos = i + 1
+ } else {
+ break
+ }
+ }
+ parsed, _ := strconv.ParseInt(any.val[startPos:endPos], 10, 64)
+ return int64(flag) * parsed
+}
+
+func (any *stringAny) ToUint() uint {
+ return uint(any.ToUint64())
+}
+
+func (any *stringAny) ToUint32() uint32 {
+ return uint32(any.ToUint64())
+}
+
+func (any *stringAny) ToUint64() uint64 {
+ if any.val == "" {
+ return 0
+ }
+
+ startPos := 0
+
+ if any.val[0] == '-' {
+ return 0
+ }
+ if any.val[0] == '+' {
+ startPos = 1
+ }
+
+ endPos := startPos
+ for i := startPos; i < len(any.val); i++ {
+ if any.val[i] >= '0' && any.val[i] <= '9' {
+ endPos = i + 1
+ } else {
+ break
+ }
+ }
+ parsed, _ := strconv.ParseUint(any.val[startPos:endPos], 10, 64)
+ return parsed
+}
+
+func (any *stringAny) ToFloat32() float32 {
+ return float32(any.ToFloat64())
+}
+
+func (any *stringAny) ToFloat64() float64 {
+ if len(any.val) == 0 {
+ return 0
+ }
+
+ // first char invalid
+ if any.val[0] != '+' && any.val[0] != '-' && (any.val[0] > '9' || any.val[0] < '0') {
+ return 0
+ }
+
+ // extract valid num expression from string
+ // eg 123true => 123, -12.12xxa => -12.12
+ endPos := 1
+ for i := 1; i < len(any.val); i++ {
+ if any.val[i] == '.' || any.val[i] == 'e' || any.val[i] == 'E' || any.val[i] == '+' || any.val[i] == '-' {
+ endPos = i + 1
+ continue
+ }
+
+ // end position is the first char which is not digit
+ if any.val[i] >= '0' && any.val[i] <= '9' {
+ endPos = i + 1
+ } else {
+ endPos = i
+ break
+ }
+ }
+ parsed, _ := strconv.ParseFloat(any.val[:endPos], 64)
+ return parsed
+}
+
+func (any *stringAny) ToString() string {
+ return any.val
+}
+
+func (any *stringAny) WriteTo(stream *Stream) {
+ stream.WriteString(any.val)
+}
+
+func (any *stringAny) GetInterface() interface{} {
+ return any.val
+}
diff --git a/vendor/github.com/json-iterator/go/any_uint32.go b/vendor/github.com/json-iterator/go/any_uint32.go
new file mode 100644
index 00000000..656bbd33
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_uint32.go
@@ -0,0 +1,74 @@
+package jsoniter
+
+import (
+ "strconv"
+)
+
+type uint32Any struct {
+ baseAny
+ val uint32
+}
+
+func (any *uint32Any) LastError() error {
+ return nil
+}
+
+func (any *uint32Any) ValueType() ValueType {
+ return NumberValue
+}
+
+func (any *uint32Any) MustBeValid() Any {
+ return any
+}
+
+func (any *uint32Any) ToBool() bool {
+ return any.val != 0
+}
+
+func (any *uint32Any) ToInt() int {
+ return int(any.val)
+}
+
+func (any *uint32Any) ToInt32() int32 {
+ return int32(any.val)
+}
+
+func (any *uint32Any) ToInt64() int64 {
+ return int64(any.val)
+}
+
+func (any *uint32Any) ToUint() uint {
+ return uint(any.val)
+}
+
+func (any *uint32Any) ToUint32() uint32 {
+ return any.val
+}
+
+func (any *uint32Any) ToUint64() uint64 {
+ return uint64(any.val)
+}
+
+func (any *uint32Any) ToFloat32() float32 {
+ return float32(any.val)
+}
+
+func (any *uint32Any) ToFloat64() float64 {
+ return float64(any.val)
+}
+
+func (any *uint32Any) ToString() string {
+ return strconv.FormatInt(int64(any.val), 10)
+}
+
+func (any *uint32Any) WriteTo(stream *Stream) {
+ stream.WriteUint32(any.val)
+}
+
+func (any *uint32Any) Parse() *Iterator {
+ return nil
+}
+
+func (any *uint32Any) GetInterface() interface{} {
+ return any.val
+}
diff --git a/vendor/github.com/json-iterator/go/any_uint64.go b/vendor/github.com/json-iterator/go/any_uint64.go
new file mode 100644
index 00000000..7df2fce3
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/any_uint64.go
@@ -0,0 +1,74 @@
+package jsoniter
+
+import (
+ "strconv"
+)
+
+type uint64Any struct {
+ baseAny
+ val uint64
+}
+
+func (any *uint64Any) LastError() error {
+ return nil
+}
+
+func (any *uint64Any) ValueType() ValueType {
+ return NumberValue
+}
+
+func (any *uint64Any) MustBeValid() Any {
+ return any
+}
+
+func (any *uint64Any) ToBool() bool {
+ return any.val != 0
+}
+
+func (any *uint64Any) ToInt() int {
+ return int(any.val)
+}
+
+func (any *uint64Any) ToInt32() int32 {
+ return int32(any.val)
+}
+
+func (any *uint64Any) ToInt64() int64 {
+ return int64(any.val)
+}
+
+func (any *uint64Any) ToUint() uint {
+ return uint(any.val)
+}
+
+func (any *uint64Any) ToUint32() uint32 {
+ return uint32(any.val)
+}
+
+func (any *uint64Any) ToUint64() uint64 {
+ return any.val
+}
+
+func (any *uint64Any) ToFloat32() float32 {
+ return float32(any.val)
+}
+
+func (any *uint64Any) ToFloat64() float64 {
+ return float64(any.val)
+}
+
+func (any *uint64Any) ToString() string {
+ return strconv.FormatUint(any.val, 10)
+}
+
+func (any *uint64Any) WriteTo(stream *Stream) {
+ stream.WriteUint64(any.val)
+}
+
+func (any *uint64Any) Parse() *Iterator {
+ return nil
+}
+
+func (any *uint64Any) GetInterface() interface{} {
+ return any.val
+}
diff --git a/vendor/github.com/json-iterator/go/build.sh b/vendor/github.com/json-iterator/go/build.sh
new file mode 100644
index 00000000..b45ef688
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+set -x
+
+if [ ! -d /tmp/build-golang/src/github.com/json-iterator ]; then
+ mkdir -p /tmp/build-golang/src/github.com/json-iterator
+ ln -s $PWD /tmp/build-golang/src/github.com/json-iterator/go
+fi
+export GOPATH=/tmp/build-golang
+go get -u github.com/golang/dep/cmd/dep
+cd /tmp/build-golang/src/github.com/json-iterator/go
+exec $GOPATH/bin/dep ensure -update
diff --git a/vendor/github.com/json-iterator/go/config.go b/vendor/github.com/json-iterator/go/config.go
new file mode 100644
index 00000000..2adcdc3b
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/config.go
@@ -0,0 +1,375 @@
+package jsoniter
+
+import (
+ "encoding/json"
+ "io"
+ "reflect"
+ "sync"
+ "unsafe"
+
+ "github.com/modern-go/concurrent"
+ "github.com/modern-go/reflect2"
+)
+
+// Config customize how the API should behave.
+// The API is created from Config by Froze.
+type Config struct {
+ IndentionStep int
+ MarshalFloatWith6Digits bool
+ EscapeHTML bool
+ SortMapKeys bool
+ UseNumber bool
+ DisallowUnknownFields bool
+ TagKey string
+ OnlyTaggedField bool
+ ValidateJsonRawMessage bool
+ ObjectFieldMustBeSimpleString bool
+ CaseSensitive bool
+}
+
+// API the public interface of this package.
+// Primary Marshal and Unmarshal.
+type API interface {
+ IteratorPool
+ StreamPool
+ MarshalToString(v interface{}) (string, error)
+ Marshal(v interface{}) ([]byte, error)
+ MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
+ UnmarshalFromString(str string, v interface{}) error
+ Unmarshal(data []byte, v interface{}) error
+ Get(data []byte, path ...interface{}) Any
+ NewEncoder(writer io.Writer) *Encoder
+ NewDecoder(reader io.Reader) *Decoder
+ Valid(data []byte) bool
+ RegisterExtension(extension Extension)
+ DecoderOf(typ reflect2.Type) ValDecoder
+ EncoderOf(typ reflect2.Type) ValEncoder
+}
+
+// ConfigDefault the default API
+var ConfigDefault = Config{
+ EscapeHTML: true,
+}.Froze()
+
+// ConfigCompatibleWithStandardLibrary tries to be 100% compatible with standard library behavior
+var ConfigCompatibleWithStandardLibrary = Config{
+ EscapeHTML: true,
+ SortMapKeys: true,
+ ValidateJsonRawMessage: true,
+}.Froze()
+
+// ConfigFastest marshals float with only 6 digits precision
+var ConfigFastest = Config{
+ EscapeHTML: false,
+ MarshalFloatWith6Digits: true, // will lose precession
+ ObjectFieldMustBeSimpleString: true, // do not unescape object field
+}.Froze()
+
+type frozenConfig struct {
+ configBeforeFrozen Config
+ sortMapKeys bool
+ indentionStep int
+ objectFieldMustBeSimpleString bool
+ onlyTaggedField bool
+ disallowUnknownFields bool
+ decoderCache *concurrent.Map
+ encoderCache *concurrent.Map
+ encoderExtension Extension
+ decoderExtension Extension
+ extraExtensions []Extension
+ streamPool *sync.Pool
+ iteratorPool *sync.Pool
+ caseSensitive bool
+}
+
+func (cfg *frozenConfig) initCache() {
+ cfg.decoderCache = concurrent.NewMap()
+ cfg.encoderCache = concurrent.NewMap()
+}
+
+func (cfg *frozenConfig) addDecoderToCache(cacheKey uintptr, decoder ValDecoder) {
+ cfg.decoderCache.Store(cacheKey, decoder)
+}
+
+func (cfg *frozenConfig) addEncoderToCache(cacheKey uintptr, encoder ValEncoder) {
+ cfg.encoderCache.Store(cacheKey, encoder)
+}
+
+func (cfg *frozenConfig) getDecoderFromCache(cacheKey uintptr) ValDecoder {
+ decoder, found := cfg.decoderCache.Load(cacheKey)
+ if found {
+ return decoder.(ValDecoder)
+ }
+ return nil
+}
+
+func (cfg *frozenConfig) getEncoderFromCache(cacheKey uintptr) ValEncoder {
+ encoder, found := cfg.encoderCache.Load(cacheKey)
+ if found {
+ return encoder.(ValEncoder)
+ }
+ return nil
+}
+
+var cfgCache = concurrent.NewMap()
+
+func getFrozenConfigFromCache(cfg Config) *frozenConfig {
+ obj, found := cfgCache.Load(cfg)
+ if found {
+ return obj.(*frozenConfig)
+ }
+ return nil
+}
+
+func addFrozenConfigToCache(cfg Config, frozenConfig *frozenConfig) {
+ cfgCache.Store(cfg, frozenConfig)
+}
+
+// Froze forge API from config
+func (cfg Config) Froze() API {
+ api := &frozenConfig{
+ sortMapKeys: cfg.SortMapKeys,
+ indentionStep: cfg.IndentionStep,
+ objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString,
+ onlyTaggedField: cfg.OnlyTaggedField,
+ disallowUnknownFields: cfg.DisallowUnknownFields,
+ caseSensitive: cfg.CaseSensitive,
+ }
+ api.streamPool = &sync.Pool{
+ New: func() interface{} {
+ return NewStream(api, nil, 512)
+ },
+ }
+ api.iteratorPool = &sync.Pool{
+ New: func() interface{} {
+ return NewIterator(api)
+ },
+ }
+ api.initCache()
+ encoderExtension := EncoderExtension{}
+ decoderExtension := DecoderExtension{}
+ if cfg.MarshalFloatWith6Digits {
+ api.marshalFloatWith6Digits(encoderExtension)
+ }
+ if cfg.EscapeHTML {
+ api.escapeHTML(encoderExtension)
+ }
+ if cfg.UseNumber {
+ api.useNumber(decoderExtension)
+ }
+ if cfg.ValidateJsonRawMessage {
+ api.validateJsonRawMessage(encoderExtension)
+ }
+ api.encoderExtension = encoderExtension
+ api.decoderExtension = decoderExtension
+ api.configBeforeFrozen = cfg
+ return api
+}
+
+func (cfg Config) frozeWithCacheReuse(extraExtensions []Extension) *frozenConfig {
+ api := getFrozenConfigFromCache(cfg)
+ if api != nil {
+ return api
+ }
+ api = cfg.Froze().(*frozenConfig)
+ for _, extension := range extraExtensions {
+ api.RegisterExtension(extension)
+ }
+ addFrozenConfigToCache(cfg, api)
+ return api
+}
+
+func (cfg *frozenConfig) validateJsonRawMessage(extension EncoderExtension) {
+ encoder := &funcEncoder{func(ptr unsafe.Pointer, stream *Stream) {
+ rawMessage := *(*json.RawMessage)(ptr)
+ iter := cfg.BorrowIterator([]byte(rawMessage))
+ defer cfg.ReturnIterator(iter)
+ iter.Read()
+ if iter.Error != nil && iter.Error != io.EOF {
+ stream.WriteRaw("null")
+ } else {
+ stream.WriteRaw(string(rawMessage))
+ }
+ }, func(ptr unsafe.Pointer) bool {
+ return len(*((*json.RawMessage)(ptr))) == 0
+ }}
+ extension[reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem()] = encoder
+ extension[reflect2.TypeOfPtr((*RawMessage)(nil)).Elem()] = encoder
+}
+
+func (cfg *frozenConfig) useNumber(extension DecoderExtension) {
+ extension[reflect2.TypeOfPtr((*interface{})(nil)).Elem()] = &funcDecoder{func(ptr unsafe.Pointer, iter *Iterator) {
+ exitingValue := *((*interface{})(ptr))
+ if exitingValue != nil && reflect.TypeOf(exitingValue).Kind() == reflect.Ptr {
+ iter.ReadVal(exitingValue)
+ return
+ }
+ if iter.WhatIsNext() == NumberValue {
+ *((*interface{})(ptr)) = json.Number(iter.readNumberAsString())
+ } else {
+ *((*interface{})(ptr)) = iter.Read()
+ }
+ }}
+}
+func (cfg *frozenConfig) getTagKey() string {
+ tagKey := cfg.configBeforeFrozen.TagKey
+ if tagKey == "" {
+ return "json"
+ }
+ return tagKey
+}
+
+func (cfg *frozenConfig) RegisterExtension(extension Extension) {
+ cfg.extraExtensions = append(cfg.extraExtensions, extension)
+ copied := cfg.configBeforeFrozen
+ cfg.configBeforeFrozen = copied
+}
+
+type lossyFloat32Encoder struct {
+}
+
+func (encoder *lossyFloat32Encoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteFloat32Lossy(*((*float32)(ptr)))
+}
+
+func (encoder *lossyFloat32Encoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*float32)(ptr)) == 0
+}
+
+type lossyFloat64Encoder struct {
+}
+
+func (encoder *lossyFloat64Encoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteFloat64Lossy(*((*float64)(ptr)))
+}
+
+func (encoder *lossyFloat64Encoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*float64)(ptr)) == 0
+}
+
+// EnableLossyFloatMarshalling keeps 10**(-6) precision
+// for float variables for better performance.
+func (cfg *frozenConfig) marshalFloatWith6Digits(extension EncoderExtension) {
+ // for better performance
+ extension[reflect2.TypeOfPtr((*float32)(nil)).Elem()] = &lossyFloat32Encoder{}
+ extension[reflect2.TypeOfPtr((*float64)(nil)).Elem()] = &lossyFloat64Encoder{}
+}
+
+type htmlEscapedStringEncoder struct {
+}
+
+func (encoder *htmlEscapedStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ str := *((*string)(ptr))
+ stream.WriteStringWithHTMLEscaped(str)
+}
+
+func (encoder *htmlEscapedStringEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*string)(ptr)) == ""
+}
+
+func (cfg *frozenConfig) escapeHTML(encoderExtension EncoderExtension) {
+ encoderExtension[reflect2.TypeOfPtr((*string)(nil)).Elem()] = &htmlEscapedStringEncoder{}
+}
+
+func (cfg *frozenConfig) cleanDecoders() {
+ typeDecoders = map[string]ValDecoder{}
+ fieldDecoders = map[string]ValDecoder{}
+ *cfg = *(cfg.configBeforeFrozen.Froze().(*frozenConfig))
+}
+
+func (cfg *frozenConfig) cleanEncoders() {
+ typeEncoders = map[string]ValEncoder{}
+ fieldEncoders = map[string]ValEncoder{}
+ *cfg = *(cfg.configBeforeFrozen.Froze().(*frozenConfig))
+}
+
+func (cfg *frozenConfig) MarshalToString(v interface{}) (string, error) {
+ stream := cfg.BorrowStream(nil)
+ defer cfg.ReturnStream(stream)
+ stream.WriteVal(v)
+ if stream.Error != nil {
+ return "", stream.Error
+ }
+ return string(stream.Buffer()), nil
+}
+
+func (cfg *frozenConfig) Marshal(v interface{}) ([]byte, error) {
+ stream := cfg.BorrowStream(nil)
+ defer cfg.ReturnStream(stream)
+ stream.WriteVal(v)
+ if stream.Error != nil {
+ return nil, stream.Error
+ }
+ result := stream.Buffer()
+ copied := make([]byte, len(result))
+ copy(copied, result)
+ return copied, nil
+}
+
+func (cfg *frozenConfig) MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
+ if prefix != "" {
+ panic("prefix is not supported")
+ }
+ for _, r := range indent {
+ if r != ' ' {
+ panic("indent can only be space")
+ }
+ }
+ newCfg := cfg.configBeforeFrozen
+ newCfg.IndentionStep = len(indent)
+ return newCfg.frozeWithCacheReuse(cfg.extraExtensions).Marshal(v)
+}
+
+func (cfg *frozenConfig) UnmarshalFromString(str string, v interface{}) error {
+ data := []byte(str)
+ iter := cfg.BorrowIterator(data)
+ defer cfg.ReturnIterator(iter)
+ iter.ReadVal(v)
+ c := iter.nextToken()
+ if c == 0 {
+ if iter.Error == io.EOF {
+ return nil
+ }
+ return iter.Error
+ }
+ iter.ReportError("Unmarshal", "there are bytes left after unmarshal")
+ return iter.Error
+}
+
+func (cfg *frozenConfig) Get(data []byte, path ...interface{}) Any {
+ iter := cfg.BorrowIterator(data)
+ defer cfg.ReturnIterator(iter)
+ return locatePath(iter, path)
+}
+
+func (cfg *frozenConfig) Unmarshal(data []byte, v interface{}) error {
+ iter := cfg.BorrowIterator(data)
+ defer cfg.ReturnIterator(iter)
+ iter.ReadVal(v)
+ c := iter.nextToken()
+ if c == 0 {
+ if iter.Error == io.EOF {
+ return nil
+ }
+ return iter.Error
+ }
+ iter.ReportError("Unmarshal", "there are bytes left after unmarshal")
+ return iter.Error
+}
+
+func (cfg *frozenConfig) NewEncoder(writer io.Writer) *Encoder {
+ stream := NewStream(cfg, writer, 512)
+ return &Encoder{stream}
+}
+
+func (cfg *frozenConfig) NewDecoder(reader io.Reader) *Decoder {
+ iter := Parse(cfg, reader, 512)
+ return &Decoder{iter}
+}
+
+func (cfg *frozenConfig) Valid(data []byte) bool {
+ iter := cfg.BorrowIterator(data)
+ defer cfg.ReturnIterator(iter)
+ iter.Skip()
+ return iter.Error == nil
+}
diff --git a/vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md b/vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md
new file mode 100644
index 00000000..3095662b
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md
@@ -0,0 +1,7 @@
+| json type \ dest type | bool | int | uint | float |string|
+| --- | --- | --- | --- |--|--|
+| number | positive => true <br/> negative => true <br/> zero => false| 23.2 => 23 <br/> -32.1 => -32| 12.1 => 12 <br/> -12.1 => 0|as normal|same as origin|
+| string | empty string => false <br/> string "0" => false <br/> other strings => true | "123.32" => 123 <br/> "-123.4" => -123 <br/> "123.23xxxw" => 123 <br/> "abcde12" => 0 <br/> "-32.1" => -32| 13.2 => 13 <br/> -1.1 => 0 |12.1 => 12.1 <br/> -12.3 => -12.3<br/> 12.4xxa => 12.4 <br/> +1.1e2 =>110 |same as origin|
+| bool | true => true <br/> false => false| true => 1 <br/> false => 0 | true => 1 <br/> false => 0 |true => 1 <br/>false => 0|true => "true" <br/> false => "false"|
+| object | true | 0 | 0 |0|originnal json|
+| array | empty array => false <br/> nonempty array => true| [] => 0 <br/> [1,2] => 1 | [] => 0 <br/> [1,2] => 1 |[] => 0<br/>[1,2] => 1|original json| \ No newline at end of file
diff --git a/vendor/github.com/json-iterator/go/go.mod b/vendor/github.com/json-iterator/go/go.mod
new file mode 100644
index 00000000..e05c42ff
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/go.mod
@@ -0,0 +1,11 @@
+module github.com/json-iterator/go
+
+go 1.12
+
+require (
+ github.com/davecgh/go-spew v1.1.1
+ github.com/google/gofuzz v1.0.0
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
+ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
+ github.com/stretchr/testify v1.3.0
+)
diff --git a/vendor/github.com/json-iterator/go/go.sum b/vendor/github.com/json-iterator/go/go.sum
new file mode 100644
index 00000000..be00a6df
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/go.sum
@@ -0,0 +1,15 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+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/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
diff --git a/vendor/github.com/json-iterator/go/iter.go b/vendor/github.com/json-iterator/go/iter.go
new file mode 100644
index 00000000..29b31cf7
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter.go
@@ -0,0 +1,349 @@
+package jsoniter
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+)
+
+// ValueType the type for JSON element
+type ValueType int
+
+const (
+ // InvalidValue invalid JSON element
+ InvalidValue ValueType = iota
+ // StringValue JSON element "string"
+ StringValue
+ // NumberValue JSON element 100 or 0.10
+ NumberValue
+ // NilValue JSON element null
+ NilValue
+ // BoolValue JSON element true or false
+ BoolValue
+ // ArrayValue JSON element []
+ ArrayValue
+ // ObjectValue JSON element {}
+ ObjectValue
+)
+
+var hexDigits []byte
+var valueTypes []ValueType
+
+func init() {
+ hexDigits = make([]byte, 256)
+ for i := 0; i < len(hexDigits); i++ {
+ hexDigits[i] = 255
+ }
+ for i := '0'; i <= '9'; i++ {
+ hexDigits[i] = byte(i - '0')
+ }
+ for i := 'a'; i <= 'f'; i++ {
+ hexDigits[i] = byte((i - 'a') + 10)
+ }
+ for i := 'A'; i <= 'F'; i++ {
+ hexDigits[i] = byte((i - 'A') + 10)
+ }
+ valueTypes = make([]ValueType, 256)
+ for i := 0; i < len(valueTypes); i++ {
+ valueTypes[i] = InvalidValue
+ }
+ valueTypes['"'] = StringValue
+ valueTypes['-'] = NumberValue
+ valueTypes['0'] = NumberValue
+ valueTypes['1'] = NumberValue
+ valueTypes['2'] = NumberValue
+ valueTypes['3'] = NumberValue
+ valueTypes['4'] = NumberValue
+ valueTypes['5'] = NumberValue
+ valueTypes['6'] = NumberValue
+ valueTypes['7'] = NumberValue
+ valueTypes['8'] = NumberValue
+ valueTypes['9'] = NumberValue
+ valueTypes['t'] = BoolValue
+ valueTypes['f'] = BoolValue
+ valueTypes['n'] = NilValue
+ valueTypes['['] = ArrayValue
+ valueTypes['{'] = ObjectValue
+}
+
+// Iterator is a io.Reader like object, with JSON specific read functions.
+// Error is not returned as return value, but stored as Error member on this iterator instance.
+type Iterator struct {
+ cfg *frozenConfig
+ reader io.Reader
+ buf []byte
+ head int
+ tail int
+ depth int
+ captureStartedAt int
+ captured []byte
+ Error error
+ Attachment interface{} // open for customized decoder
+}
+
+// NewIterator creates an empty Iterator instance
+func NewIterator(cfg API) *Iterator {
+ return &Iterator{
+ cfg: cfg.(*frozenConfig),
+ reader: nil,
+ buf: nil,
+ head: 0,
+ tail: 0,
+ depth: 0,
+ }
+}
+
+// Parse creates an Iterator instance from io.Reader
+func Parse(cfg API, reader io.Reader, bufSize int) *Iterator {
+ return &Iterator{
+ cfg: cfg.(*frozenConfig),
+ reader: reader,
+ buf: make([]byte, bufSize),
+ head: 0,
+ tail: 0,
+ depth: 0,
+ }
+}
+
+// ParseBytes creates an Iterator instance from byte array
+func ParseBytes(cfg API, input []byte) *Iterator {
+ return &Iterator{
+ cfg: cfg.(*frozenConfig),
+ reader: nil,
+ buf: input,
+ head: 0,
+ tail: len(input),
+ depth: 0,
+ }
+}
+
+// ParseString creates an Iterator instance from string
+func ParseString(cfg API, input string) *Iterator {
+ return ParseBytes(cfg, []byte(input))
+}
+
+// Pool returns a pool can provide more iterator with same configuration
+func (iter *Iterator) Pool() IteratorPool {
+ return iter.cfg
+}
+
+// Reset reuse iterator instance by specifying another reader
+func (iter *Iterator) Reset(reader io.Reader) *Iterator {
+ iter.reader = reader
+ iter.head = 0
+ iter.tail = 0
+ iter.depth = 0
+ return iter
+}
+
+// ResetBytes reuse iterator instance by specifying another byte array as input
+func (iter *Iterator) ResetBytes(input []byte) *Iterator {
+ iter.reader = nil
+ iter.buf = input
+ iter.head = 0
+ iter.tail = len(input)
+ iter.depth = 0
+ return iter
+}
+
+// WhatIsNext gets ValueType of relatively next json element
+func (iter *Iterator) WhatIsNext() ValueType {
+ valueType := valueTypes[iter.nextToken()]
+ iter.unreadByte()
+ return valueType
+}
+
+func (iter *Iterator) skipWhitespacesWithoutLoadMore() bool {
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ switch c {
+ case ' ', '\n', '\t', '\r':
+ continue
+ }
+ iter.head = i
+ return false
+ }
+ return true
+}
+
+func (iter *Iterator) isObjectEnd() bool {
+ c := iter.nextToken()
+ if c == ',' {
+ return false
+ }
+ if c == '}' {
+ return true
+ }
+ iter.ReportError("isObjectEnd", "object ended prematurely, unexpected char "+string([]byte{c}))
+ return true
+}
+
+func (iter *Iterator) nextToken() byte {
+ // a variation of skip whitespaces, returning the next non-whitespace token
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ switch c {
+ case ' ', '\n', '\t', '\r':
+ continue
+ }
+ iter.head = i + 1
+ return c
+ }
+ if !iter.loadMore() {
+ return 0
+ }
+ }
+}
+
+// ReportError record a error in iterator instance with current position.
+func (iter *Iterator) ReportError(operation string, msg string) {
+ if iter.Error != nil {
+ if iter.Error != io.EOF {
+ return
+ }
+ }
+ peekStart := iter.head - 10
+ if peekStart < 0 {
+ peekStart = 0
+ }
+ peekEnd := iter.head + 10
+ if peekEnd > iter.tail {
+ peekEnd = iter.tail
+ }
+ parsing := string(iter.buf[peekStart:peekEnd])
+ contextStart := iter.head - 50
+ if contextStart < 0 {
+ contextStart = 0
+ }
+ contextEnd := iter.head + 50
+ if contextEnd > iter.tail {
+ contextEnd = iter.tail
+ }
+ context := string(iter.buf[contextStart:contextEnd])
+ iter.Error = fmt.Errorf("%s: %s, error found in #%v byte of ...|%s|..., bigger context ...|%s|...",
+ operation, msg, iter.head-peekStart, parsing, context)
+}
+
+// CurrentBuffer gets current buffer as string for debugging purpose
+func (iter *Iterator) CurrentBuffer() string {
+ peekStart := iter.head - 10
+ if peekStart < 0 {
+ peekStart = 0
+ }
+ return fmt.Sprintf("parsing #%v byte, around ...|%s|..., whole buffer ...|%s|...", iter.head,
+ string(iter.buf[peekStart:iter.head]), string(iter.buf[0:iter.tail]))
+}
+
+func (iter *Iterator) readByte() (ret byte) {
+ if iter.head == iter.tail {
+ if iter.loadMore() {
+ ret = iter.buf[iter.head]
+ iter.head++
+ return ret
+ }
+ return 0
+ }
+ ret = iter.buf[iter.head]
+ iter.head++
+ return ret
+}
+
+func (iter *Iterator) loadMore() bool {
+ if iter.reader == nil {
+ if iter.Error == nil {
+ iter.head = iter.tail
+ iter.Error = io.EOF
+ }
+ return false
+ }
+ if iter.captured != nil {
+ iter.captured = append(iter.captured,
+ iter.buf[iter.captureStartedAt:iter.tail]...)
+ iter.captureStartedAt = 0
+ }
+ for {
+ n, err := iter.reader.Read(iter.buf)
+ if n == 0 {
+ if err != nil {
+ if iter.Error == nil {
+ iter.Error = err
+ }
+ return false
+ }
+ } else {
+ iter.head = 0
+ iter.tail = n
+ return true
+ }
+ }
+}
+
+func (iter *Iterator) unreadByte() {
+ if iter.Error != nil {
+ return
+ }
+ iter.head--
+ return
+}
+
+// Read read the next JSON element as generic interface{}.
+func (iter *Iterator) Read() interface{} {
+ valueType := iter.WhatIsNext()
+ switch valueType {
+ case StringValue:
+ return iter.ReadString()
+ case NumberValue:
+ if iter.cfg.configBeforeFrozen.UseNumber {
+ return json.Number(iter.readNumberAsString())
+ }
+ return iter.ReadFloat64()
+ case NilValue:
+ iter.skipFourBytes('n', 'u', 'l', 'l')
+ return nil
+ case BoolValue:
+ return iter.ReadBool()
+ case ArrayValue:
+ arr := []interface{}{}
+ iter.ReadArrayCB(func(iter *Iterator) bool {
+ var elem interface{}
+ iter.ReadVal(&elem)
+ arr = append(arr, elem)
+ return true
+ })
+ return arr
+ case ObjectValue:
+ obj := map[string]interface{}{}
+ iter.ReadMapCB(func(Iter *Iterator, field string) bool {
+ var elem interface{}
+ iter.ReadVal(&elem)
+ obj[field] = elem
+ return true
+ })
+ return obj
+ default:
+ iter.ReportError("Read", fmt.Sprintf("unexpected value type: %v", valueType))
+ return nil
+ }
+}
+
+// limit maximum depth of nesting, as allowed by https://tools.ietf.org/html/rfc7159#section-9
+const maxDepth = 10000
+
+func (iter *Iterator) incrementDepth() (success bool) {
+ iter.depth++
+ if iter.depth <= maxDepth {
+ return true
+ }
+ iter.ReportError("incrementDepth", "exceeded max depth")
+ return false
+}
+
+func (iter *Iterator) decrementDepth() (success bool) {
+ iter.depth--
+ if iter.depth >= 0 {
+ return true
+ }
+ iter.ReportError("decrementDepth", "unexpected negative nesting")
+ return false
+}
diff --git a/vendor/github.com/json-iterator/go/iter_array.go b/vendor/github.com/json-iterator/go/iter_array.go
new file mode 100644
index 00000000..204fe0e0
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_array.go
@@ -0,0 +1,64 @@
+package jsoniter
+
+// ReadArray read array element, tells if the array has more element to read.
+func (iter *Iterator) ReadArray() (ret bool) {
+ c := iter.nextToken()
+ switch c {
+ case 'n':
+ iter.skipThreeBytes('u', 'l', 'l')
+ return false // null
+ case '[':
+ c = iter.nextToken()
+ if c != ']' {
+ iter.unreadByte()
+ return true
+ }
+ return false
+ case ']':
+ return false
+ case ',':
+ return true
+ default:
+ iter.ReportError("ReadArray", "expect [ or , or ] or n, but found "+string([]byte{c}))
+ return
+ }
+}
+
+// ReadArrayCB read array with callback
+func (iter *Iterator) ReadArrayCB(callback func(*Iterator) bool) (ret bool) {
+ c := iter.nextToken()
+ if c == '[' {
+ if !iter.incrementDepth() {
+ return false
+ }
+ c = iter.nextToken()
+ if c != ']' {
+ iter.unreadByte()
+ if !callback(iter) {
+ iter.decrementDepth()
+ return false
+ }
+ c = iter.nextToken()
+ for c == ',' {
+ if !callback(iter) {
+ iter.decrementDepth()
+ return false
+ }
+ c = iter.nextToken()
+ }
+ if c != ']' {
+ iter.ReportError("ReadArrayCB", "expect ] in the end, but found "+string([]byte{c}))
+ iter.decrementDepth()
+ return false
+ }
+ return iter.decrementDepth()
+ }
+ return iter.decrementDepth()
+ }
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ return true // null
+ }
+ iter.ReportError("ReadArrayCB", "expect [ or n, but found "+string([]byte{c}))
+ return false
+}
diff --git a/vendor/github.com/json-iterator/go/iter_float.go b/vendor/github.com/json-iterator/go/iter_float.go
new file mode 100644
index 00000000..8a3d8b6f
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_float.go
@@ -0,0 +1,342 @@
+package jsoniter
+
+import (
+ "encoding/json"
+ "io"
+ "math/big"
+ "strconv"
+ "strings"
+ "unsafe"
+)
+
+var floatDigits []int8
+
+const invalidCharForNumber = int8(-1)
+const endOfNumber = int8(-2)
+const dotInNumber = int8(-3)
+
+func init() {
+ floatDigits = make([]int8, 256)
+ for i := 0; i < len(floatDigits); i++ {
+ floatDigits[i] = invalidCharForNumber
+ }
+ for i := int8('0'); i <= int8('9'); i++ {
+ floatDigits[i] = i - int8('0')
+ }
+ floatDigits[','] = endOfNumber
+ floatDigits[']'] = endOfNumber
+ floatDigits['}'] = endOfNumber
+ floatDigits[' '] = endOfNumber
+ floatDigits['\t'] = endOfNumber
+ floatDigits['\n'] = endOfNumber
+ floatDigits['.'] = dotInNumber
+}
+
+// ReadBigFloat read big.Float
+func (iter *Iterator) ReadBigFloat() (ret *big.Float) {
+ str := iter.readNumberAsString()
+ if iter.Error != nil && iter.Error != io.EOF {
+ return nil
+ }
+ prec := 64
+ if len(str) > prec {
+ prec = len(str)
+ }
+ val, _, err := big.ParseFloat(str, 10, uint(prec), big.ToZero)
+ if err != nil {
+ iter.Error = err
+ return nil
+ }
+ return val
+}
+
+// ReadBigInt read big.Int
+func (iter *Iterator) ReadBigInt() (ret *big.Int) {
+ str := iter.readNumberAsString()
+ if iter.Error != nil && iter.Error != io.EOF {
+ return nil
+ }
+ ret = big.NewInt(0)
+ var success bool
+ ret, success = ret.SetString(str, 10)
+ if !success {
+ iter.ReportError("ReadBigInt", "invalid big int")
+ return nil
+ }
+ return ret
+}
+
+//ReadFloat32 read float32
+func (iter *Iterator) ReadFloat32() (ret float32) {
+ c := iter.nextToken()
+ if c == '-' {
+ return -iter.readPositiveFloat32()
+ }
+ iter.unreadByte()
+ return iter.readPositiveFloat32()
+}
+
+func (iter *Iterator) readPositiveFloat32() (ret float32) {
+ i := iter.head
+ // first char
+ if i == iter.tail {
+ return iter.readFloat32SlowPath()
+ }
+ c := iter.buf[i]
+ i++
+ ind := floatDigits[c]
+ switch ind {
+ case invalidCharForNumber:
+ return iter.readFloat32SlowPath()
+ case endOfNumber:
+ iter.ReportError("readFloat32", "empty number")
+ return
+ case dotInNumber:
+ iter.ReportError("readFloat32", "leading dot is invalid")
+ return
+ case 0:
+ if i == iter.tail {
+ return iter.readFloat32SlowPath()
+ }
+ c = iter.buf[i]
+ switch c {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ iter.ReportError("readFloat32", "leading zero is invalid")
+ return
+ }
+ }
+ value := uint64(ind)
+ // chars before dot
+non_decimal_loop:
+ for ; i < iter.tail; i++ {
+ c = iter.buf[i]
+ ind := floatDigits[c]
+ switch ind {
+ case invalidCharForNumber:
+ return iter.readFloat32SlowPath()
+ case endOfNumber:
+ iter.head = i
+ return float32(value)
+ case dotInNumber:
+ break non_decimal_loop
+ }
+ if value > uint64SafeToMultiple10 {
+ return iter.readFloat32SlowPath()
+ }
+ value = (value << 3) + (value << 1) + uint64(ind) // value = value * 10 + ind;
+ }
+ // chars after dot
+ if c == '.' {
+ i++
+ decimalPlaces := 0
+ if i == iter.tail {
+ return iter.readFloat32SlowPath()
+ }
+ for ; i < iter.tail; i++ {
+ c = iter.buf[i]
+ ind := floatDigits[c]
+ switch ind {
+ case endOfNumber:
+ if decimalPlaces > 0 && decimalPlaces < len(pow10) {
+ iter.head = i
+ return float32(float64(value) / float64(pow10[decimalPlaces]))
+ }
+ // too many decimal places
+ return iter.readFloat32SlowPath()
+ case invalidCharForNumber, dotInNumber:
+ return iter.readFloat32SlowPath()
+ }
+ decimalPlaces++
+ if value > uint64SafeToMultiple10 {
+ return iter.readFloat32SlowPath()
+ }
+ value = (value << 3) + (value << 1) + uint64(ind)
+ }
+ }
+ return iter.readFloat32SlowPath()
+}
+
+func (iter *Iterator) readNumberAsString() (ret string) {
+ strBuf := [16]byte{}
+ str := strBuf[0:0]
+load_loop:
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ switch c {
+ case '+', '-', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ str = append(str, c)
+ continue
+ default:
+ iter.head = i
+ break load_loop
+ }
+ }
+ if !iter.loadMore() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF {
+ return
+ }
+ if len(str) == 0 {
+ iter.ReportError("readNumberAsString", "invalid number")
+ }
+ return *(*string)(unsafe.Pointer(&str))
+}
+
+func (iter *Iterator) readFloat32SlowPath() (ret float32) {
+ str := iter.readNumberAsString()
+ if iter.Error != nil && iter.Error != io.EOF {
+ return
+ }
+ errMsg := validateFloat(str)
+ if errMsg != "" {
+ iter.ReportError("readFloat32SlowPath", errMsg)
+ return
+ }
+ val, err := strconv.ParseFloat(str, 32)
+ if err != nil {
+ iter.Error = err
+ return
+ }
+ return float32(val)
+}
+
+// ReadFloat64 read float64
+func (iter *Iterator) ReadFloat64() (ret float64) {
+ c := iter.nextToken()
+ if c == '-' {
+ return -iter.readPositiveFloat64()
+ }
+ iter.unreadByte()
+ return iter.readPositiveFloat64()
+}
+
+func (iter *Iterator) readPositiveFloat64() (ret float64) {
+ i := iter.head
+ // first char
+ if i == iter.tail {
+ return iter.readFloat64SlowPath()
+ }
+ c := iter.buf[i]
+ i++
+ ind := floatDigits[c]
+ switch ind {
+ case invalidCharForNumber:
+ return iter.readFloat64SlowPath()
+ case endOfNumber:
+ iter.ReportError("readFloat64", "empty number")
+ return
+ case dotInNumber:
+ iter.ReportError("readFloat64", "leading dot is invalid")
+ return
+ case 0:
+ if i == iter.tail {
+ return iter.readFloat64SlowPath()
+ }
+ c = iter.buf[i]
+ switch c {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ iter.ReportError("readFloat64", "leading zero is invalid")
+ return
+ }
+ }
+ value := uint64(ind)
+ // chars before dot
+non_decimal_loop:
+ for ; i < iter.tail; i++ {
+ c = iter.buf[i]
+ ind := floatDigits[c]
+ switch ind {
+ case invalidCharForNumber:
+ return iter.readFloat64SlowPath()
+ case endOfNumber:
+ iter.head = i
+ return float64(value)
+ case dotInNumber:
+ break non_decimal_loop
+ }
+ if value > uint64SafeToMultiple10 {
+ return iter.readFloat64SlowPath()
+ }
+ value = (value << 3) + (value << 1) + uint64(ind) // value = value * 10 + ind;
+ }
+ // chars after dot
+ if c == '.' {
+ i++
+ decimalPlaces := 0
+ if i == iter.tail {
+ return iter.readFloat64SlowPath()
+ }
+ for ; i < iter.tail; i++ {
+ c = iter.buf[i]
+ ind := floatDigits[c]
+ switch ind {
+ case endOfNumber:
+ if decimalPlaces > 0 && decimalPlaces < len(pow10) {
+ iter.head = i
+ return float64(value) / float64(pow10[decimalPlaces])
+ }
+ // too many decimal places
+ return iter.readFloat64SlowPath()
+ case invalidCharForNumber, dotInNumber:
+ return iter.readFloat64SlowPath()
+ }
+ decimalPlaces++
+ if value > uint64SafeToMultiple10 {
+ return iter.readFloat64SlowPath()
+ }
+ value = (value << 3) + (value << 1) + uint64(ind)
+ if value > maxFloat64 {
+ return iter.readFloat64SlowPath()
+ }
+ }
+ }
+ return iter.readFloat64SlowPath()
+}
+
+func (iter *Iterator) readFloat64SlowPath() (ret float64) {
+ str := iter.readNumberAsString()
+ if iter.Error != nil && iter.Error != io.EOF {
+ return
+ }
+ errMsg := validateFloat(str)
+ if errMsg != "" {
+ iter.ReportError("readFloat64SlowPath", errMsg)
+ return
+ }
+ val, err := strconv.ParseFloat(str, 64)
+ if err != nil {
+ iter.Error = err
+ return
+ }
+ return val
+}
+
+func validateFloat(str string) string {
+ // strconv.ParseFloat is not validating `1.` or `1.e1`
+ if len(str) == 0 {
+ return "empty number"
+ }
+ if str[0] == '-' {
+ return "-- is not valid"
+ }
+ dotPos := strings.IndexByte(str, '.')
+ if dotPos != -1 {
+ if dotPos == len(str)-1 {
+ return "dot can not be last character"
+ }
+ switch str[dotPos+1] {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ default:
+ return "missing digit after dot"
+ }
+ }
+ return ""
+}
+
+// ReadNumber read json.Number
+func (iter *Iterator) ReadNumber() (ret json.Number) {
+ return json.Number(iter.readNumberAsString())
+}
diff --git a/vendor/github.com/json-iterator/go/iter_int.go b/vendor/github.com/json-iterator/go/iter_int.go
new file mode 100644
index 00000000..d786a89f
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_int.go
@@ -0,0 +1,346 @@
+package jsoniter
+
+import (
+ "math"
+ "strconv"
+)
+
+var intDigits []int8
+
+const uint32SafeToMultiply10 = uint32(0xffffffff)/10 - 1
+const uint64SafeToMultiple10 = uint64(0xffffffffffffffff)/10 - 1
+const maxFloat64 = 1<<53 - 1
+
+func init() {
+ intDigits = make([]int8, 256)
+ for i := 0; i < len(intDigits); i++ {
+ intDigits[i] = invalidCharForNumber
+ }
+ for i := int8('0'); i <= int8('9'); i++ {
+ intDigits[i] = i - int8('0')
+ }
+}
+
+// ReadUint read uint
+func (iter *Iterator) ReadUint() uint {
+ if strconv.IntSize == 32 {
+ return uint(iter.ReadUint32())
+ }
+ return uint(iter.ReadUint64())
+}
+
+// ReadInt read int
+func (iter *Iterator) ReadInt() int {
+ if strconv.IntSize == 32 {
+ return int(iter.ReadInt32())
+ }
+ return int(iter.ReadInt64())
+}
+
+// ReadInt8 read int8
+func (iter *Iterator) ReadInt8() (ret int8) {
+ c := iter.nextToken()
+ if c == '-' {
+ val := iter.readUint32(iter.readByte())
+ if val > math.MaxInt8+1 {
+ iter.ReportError("ReadInt8", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return -int8(val)
+ }
+ val := iter.readUint32(c)
+ if val > math.MaxInt8 {
+ iter.ReportError("ReadInt8", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return int8(val)
+}
+
+// ReadUint8 read uint8
+func (iter *Iterator) ReadUint8() (ret uint8) {
+ val := iter.readUint32(iter.nextToken())
+ if val > math.MaxUint8 {
+ iter.ReportError("ReadUint8", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return uint8(val)
+}
+
+// ReadInt16 read int16
+func (iter *Iterator) ReadInt16() (ret int16) {
+ c := iter.nextToken()
+ if c == '-' {
+ val := iter.readUint32(iter.readByte())
+ if val > math.MaxInt16+1 {
+ iter.ReportError("ReadInt16", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return -int16(val)
+ }
+ val := iter.readUint32(c)
+ if val > math.MaxInt16 {
+ iter.ReportError("ReadInt16", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return int16(val)
+}
+
+// ReadUint16 read uint16
+func (iter *Iterator) ReadUint16() (ret uint16) {
+ val := iter.readUint32(iter.nextToken())
+ if val > math.MaxUint16 {
+ iter.ReportError("ReadUint16", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return uint16(val)
+}
+
+// ReadInt32 read int32
+func (iter *Iterator) ReadInt32() (ret int32) {
+ c := iter.nextToken()
+ if c == '-' {
+ val := iter.readUint32(iter.readByte())
+ if val > math.MaxInt32+1 {
+ iter.ReportError("ReadInt32", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return -int32(val)
+ }
+ val := iter.readUint32(c)
+ if val > math.MaxInt32 {
+ iter.ReportError("ReadInt32", "overflow: "+strconv.FormatInt(int64(val), 10))
+ return
+ }
+ return int32(val)
+}
+
+// ReadUint32 read uint32
+func (iter *Iterator) ReadUint32() (ret uint32) {
+ return iter.readUint32(iter.nextToken())
+}
+
+func (iter *Iterator) readUint32(c byte) (ret uint32) {
+ ind := intDigits[c]
+ if ind == 0 {
+ iter.assertInteger()
+ return 0 // single zero
+ }
+ if ind == invalidCharForNumber {
+ iter.ReportError("readUint32", "unexpected character: "+string([]byte{byte(ind)}))
+ return
+ }
+ value := uint32(ind)
+ if iter.tail-iter.head > 10 {
+ i := iter.head
+ ind2 := intDigits[iter.buf[i]]
+ if ind2 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value
+ }
+ i++
+ ind3 := intDigits[iter.buf[i]]
+ if ind3 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*10 + uint32(ind2)
+ }
+ //iter.head = i + 1
+ //value = value * 100 + uint32(ind2) * 10 + uint32(ind3)
+ i++
+ ind4 := intDigits[iter.buf[i]]
+ if ind4 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*100 + uint32(ind2)*10 + uint32(ind3)
+ }
+ i++
+ ind5 := intDigits[iter.buf[i]]
+ if ind5 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*1000 + uint32(ind2)*100 + uint32(ind3)*10 + uint32(ind4)
+ }
+ i++
+ ind6 := intDigits[iter.buf[i]]
+ if ind6 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*10000 + uint32(ind2)*1000 + uint32(ind3)*100 + uint32(ind4)*10 + uint32(ind5)
+ }
+ i++
+ ind7 := intDigits[iter.buf[i]]
+ if ind7 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*100000 + uint32(ind2)*10000 + uint32(ind3)*1000 + uint32(ind4)*100 + uint32(ind5)*10 + uint32(ind6)
+ }
+ i++
+ ind8 := intDigits[iter.buf[i]]
+ if ind8 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*1000000 + uint32(ind2)*100000 + uint32(ind3)*10000 + uint32(ind4)*1000 + uint32(ind5)*100 + uint32(ind6)*10 + uint32(ind7)
+ }
+ i++
+ ind9 := intDigits[iter.buf[i]]
+ value = value*10000000 + uint32(ind2)*1000000 + uint32(ind3)*100000 + uint32(ind4)*10000 + uint32(ind5)*1000 + uint32(ind6)*100 + uint32(ind7)*10 + uint32(ind8)
+ iter.head = i
+ if ind9 == invalidCharForNumber {
+ iter.assertInteger()
+ return value
+ }
+ }
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ ind = intDigits[iter.buf[i]]
+ if ind == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value
+ }
+ if value > uint32SafeToMultiply10 {
+ value2 := (value << 3) + (value << 1) + uint32(ind)
+ if value2 < value {
+ iter.ReportError("readUint32", "overflow")
+ return
+ }
+ value = value2
+ continue
+ }
+ value = (value << 3) + (value << 1) + uint32(ind)
+ }
+ if !iter.loadMore() {
+ iter.assertInteger()
+ return value
+ }
+ }
+}
+
+// ReadInt64 read int64
+func (iter *Iterator) ReadInt64() (ret int64) {
+ c := iter.nextToken()
+ if c == '-' {
+ val := iter.readUint64(iter.readByte())
+ if val > math.MaxInt64+1 {
+ iter.ReportError("ReadInt64", "overflow: "+strconv.FormatUint(uint64(val), 10))
+ return
+ }
+ return -int64(val)
+ }
+ val := iter.readUint64(c)
+ if val > math.MaxInt64 {
+ iter.ReportError("ReadInt64", "overflow: "+strconv.FormatUint(uint64(val), 10))
+ return
+ }
+ return int64(val)
+}
+
+// ReadUint64 read uint64
+func (iter *Iterator) ReadUint64() uint64 {
+ return iter.readUint64(iter.nextToken())
+}
+
+func (iter *Iterator) readUint64(c byte) (ret uint64) {
+ ind := intDigits[c]
+ if ind == 0 {
+ iter.assertInteger()
+ return 0 // single zero
+ }
+ if ind == invalidCharForNumber {
+ iter.ReportError("readUint64", "unexpected character: "+string([]byte{byte(ind)}))
+ return
+ }
+ value := uint64(ind)
+ if iter.tail-iter.head > 10 {
+ i := iter.head
+ ind2 := intDigits[iter.buf[i]]
+ if ind2 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value
+ }
+ i++
+ ind3 := intDigits[iter.buf[i]]
+ if ind3 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*10 + uint64(ind2)
+ }
+ //iter.head = i + 1
+ //value = value * 100 + uint32(ind2) * 10 + uint32(ind3)
+ i++
+ ind4 := intDigits[iter.buf[i]]
+ if ind4 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*100 + uint64(ind2)*10 + uint64(ind3)
+ }
+ i++
+ ind5 := intDigits[iter.buf[i]]
+ if ind5 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*1000 + uint64(ind2)*100 + uint64(ind3)*10 + uint64(ind4)
+ }
+ i++
+ ind6 := intDigits[iter.buf[i]]
+ if ind6 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*10000 + uint64(ind2)*1000 + uint64(ind3)*100 + uint64(ind4)*10 + uint64(ind5)
+ }
+ i++
+ ind7 := intDigits[iter.buf[i]]
+ if ind7 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*100000 + uint64(ind2)*10000 + uint64(ind3)*1000 + uint64(ind4)*100 + uint64(ind5)*10 + uint64(ind6)
+ }
+ i++
+ ind8 := intDigits[iter.buf[i]]
+ if ind8 == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value*1000000 + uint64(ind2)*100000 + uint64(ind3)*10000 + uint64(ind4)*1000 + uint64(ind5)*100 + uint64(ind6)*10 + uint64(ind7)
+ }
+ i++
+ ind9 := intDigits[iter.buf[i]]
+ value = value*10000000 + uint64(ind2)*1000000 + uint64(ind3)*100000 + uint64(ind4)*10000 + uint64(ind5)*1000 + uint64(ind6)*100 + uint64(ind7)*10 + uint64(ind8)
+ iter.head = i
+ if ind9 == invalidCharForNumber {
+ iter.assertInteger()
+ return value
+ }
+ }
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ ind = intDigits[iter.buf[i]]
+ if ind == invalidCharForNumber {
+ iter.head = i
+ iter.assertInteger()
+ return value
+ }
+ if value > uint64SafeToMultiple10 {
+ value2 := (value << 3) + (value << 1) + uint64(ind)
+ if value2 < value {
+ iter.ReportError("readUint64", "overflow")
+ return
+ }
+ value = value2
+ continue
+ }
+ value = (value << 3) + (value << 1) + uint64(ind)
+ }
+ if !iter.loadMore() {
+ iter.assertInteger()
+ return value
+ }
+ }
+}
+
+func (iter *Iterator) assertInteger() {
+ if iter.head < iter.tail && iter.buf[iter.head] == '.' {
+ iter.ReportError("assertInteger", "can not decode float as int")
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/iter_object.go b/vendor/github.com/json-iterator/go/iter_object.go
new file mode 100644
index 00000000..58ee89c8
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_object.go
@@ -0,0 +1,267 @@
+package jsoniter
+
+import (
+ "fmt"
+ "strings"
+)
+
+// ReadObject read one field from object.
+// If object ended, returns empty string.
+// Otherwise, returns the field name.
+func (iter *Iterator) ReadObject() (ret string) {
+ c := iter.nextToken()
+ switch c {
+ case 'n':
+ iter.skipThreeBytes('u', 'l', 'l')
+ return "" // null
+ case '{':
+ c = iter.nextToken()
+ if c == '"' {
+ iter.unreadByte()
+ field := iter.ReadString()
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
+ }
+ return field
+ }
+ if c == '}' {
+ return "" // end of object
+ }
+ iter.ReportError("ReadObject", `expect " after {, but found `+string([]byte{c}))
+ return
+ case ',':
+ field := iter.ReadString()
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
+ }
+ return field
+ case '}':
+ return "" // end of object
+ default:
+ iter.ReportError("ReadObject", fmt.Sprintf(`expect { or , or } or n, but found %s`, string([]byte{c})))
+ return
+ }
+}
+
+// CaseInsensitive
+func (iter *Iterator) readFieldHash() int64 {
+ hash := int64(0x811c9dc5)
+ c := iter.nextToken()
+ if c != '"' {
+ iter.ReportError("readFieldHash", `expect ", but found `+string([]byte{c}))
+ return 0
+ }
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ // require ascii string and no escape
+ b := iter.buf[i]
+ if b == '\\' {
+ iter.head = i
+ for _, b := range iter.readStringSlowPath() {
+ if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
+ b += 'a' - 'A'
+ }
+ hash ^= int64(b)
+ hash *= 0x1000193
+ }
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
+ return 0
+ }
+ return hash
+ }
+ if b == '"' {
+ iter.head = i + 1
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
+ return 0
+ }
+ return hash
+ }
+ if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
+ b += 'a' - 'A'
+ }
+ hash ^= int64(b)
+ hash *= 0x1000193
+ }
+ if !iter.loadMore() {
+ iter.ReportError("readFieldHash", `incomplete field name`)
+ return 0
+ }
+ }
+}
+
+func calcHash(str string, caseSensitive bool) int64 {
+ if !caseSensitive {
+ str = strings.ToLower(str)
+ }
+ hash := int64(0x811c9dc5)
+ for _, b := range []byte(str) {
+ hash ^= int64(b)
+ hash *= 0x1000193
+ }
+ return int64(hash)
+}
+
+// ReadObjectCB read object with callback, the key is ascii only and field name not copied
+func (iter *Iterator) ReadObjectCB(callback func(*Iterator, string) bool) bool {
+ c := iter.nextToken()
+ var field string
+ if c == '{' {
+ if !iter.incrementDepth() {
+ return false
+ }
+ c = iter.nextToken()
+ if c == '"' {
+ iter.unreadByte()
+ field = iter.ReadString()
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
+ }
+ if !callback(iter, field) {
+ iter.decrementDepth()
+ return false
+ }
+ c = iter.nextToken()
+ for c == ',' {
+ field = iter.ReadString()
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
+ }
+ if !callback(iter, field) {
+ iter.decrementDepth()
+ return false
+ }
+ c = iter.nextToken()
+ }
+ if c != '}' {
+ iter.ReportError("ReadObjectCB", `object not ended with }`)
+ iter.decrementDepth()
+ return false
+ }
+ return iter.decrementDepth()
+ }
+ if c == '}' {
+ return iter.decrementDepth()
+ }
+ iter.ReportError("ReadObjectCB", `expect " after {, but found `+string([]byte{c}))
+ iter.decrementDepth()
+ return false
+ }
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ return true // null
+ }
+ iter.ReportError("ReadObjectCB", `expect { or n, but found `+string([]byte{c}))
+ return false
+}
+
+// ReadMapCB read map with callback, the key can be any string
+func (iter *Iterator) ReadMapCB(callback func(*Iterator, string) bool) bool {
+ c := iter.nextToken()
+ if c == '{' {
+ if !iter.incrementDepth() {
+ return false
+ }
+ c = iter.nextToken()
+ if c == '"' {
+ iter.unreadByte()
+ field := iter.ReadString()
+ if iter.nextToken() != ':' {
+ iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
+ iter.decrementDepth()
+ return false
+ }
+ if !callback(iter, field) {
+ iter.decrementDepth()
+ return false
+ }
+ c = iter.nextToken()
+ for c == ',' {
+ field = iter.ReadString()
+ if iter.nextToken() != ':' {
+ iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
+ iter.decrementDepth()
+ return false
+ }
+ if !callback(iter, field) {
+ iter.decrementDepth()
+ return false
+ }
+ c = iter.nextToken()
+ }
+ if c != '}' {
+ iter.ReportError("ReadMapCB", `object not ended with }`)
+ iter.decrementDepth()
+ return false
+ }
+ return iter.decrementDepth()
+ }
+ if c == '}' {
+ return iter.decrementDepth()
+ }
+ iter.ReportError("ReadMapCB", `expect " after {, but found `+string([]byte{c}))
+ iter.decrementDepth()
+ return false
+ }
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ return true // null
+ }
+ iter.ReportError("ReadMapCB", `expect { or n, but found `+string([]byte{c}))
+ return false
+}
+
+func (iter *Iterator) readObjectStart() bool {
+ c := iter.nextToken()
+ if c == '{' {
+ c = iter.nextToken()
+ if c == '}' {
+ return false
+ }
+ iter.unreadByte()
+ return true
+ } else if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ return false
+ }
+ iter.ReportError("readObjectStart", "expect { or n, but found "+string([]byte{c}))
+ return false
+}
+
+func (iter *Iterator) readObjectFieldAsBytes() (ret []byte) {
+ str := iter.ReadStringAsSlice()
+ if iter.skipWhitespacesWithoutLoadMore() {
+ if ret == nil {
+ ret = make([]byte, len(str))
+ copy(ret, str)
+ }
+ if !iter.loadMore() {
+ return
+ }
+ }
+ if iter.buf[iter.head] != ':' {
+ iter.ReportError("readObjectFieldAsBytes", "expect : after object field, but found "+string([]byte{iter.buf[iter.head]}))
+ return
+ }
+ iter.head++
+ if iter.skipWhitespacesWithoutLoadMore() {
+ if ret == nil {
+ ret = make([]byte, len(str))
+ copy(ret, str)
+ }
+ if !iter.loadMore() {
+ return
+ }
+ }
+ if ret == nil {
+ return str
+ }
+ return ret
+}
diff --git a/vendor/github.com/json-iterator/go/iter_skip.go b/vendor/github.com/json-iterator/go/iter_skip.go
new file mode 100644
index 00000000..e91eefb1
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_skip.go
@@ -0,0 +1,130 @@
+package jsoniter
+
+import "fmt"
+
+// ReadNil reads a json object as nil and
+// returns whether it's a nil or not
+func (iter *Iterator) ReadNil() (ret bool) {
+ c := iter.nextToken()
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l') // null
+ return true
+ }
+ iter.unreadByte()
+ return false
+}
+
+// ReadBool reads a json object as BoolValue
+func (iter *Iterator) ReadBool() (ret bool) {
+ c := iter.nextToken()
+ if c == 't' {
+ iter.skipThreeBytes('r', 'u', 'e')
+ return true
+ }
+ if c == 'f' {
+ iter.skipFourBytes('a', 'l', 's', 'e')
+ return false
+ }
+ iter.ReportError("ReadBool", "expect t or f, but found "+string([]byte{c}))
+ return
+}
+
+// SkipAndReturnBytes skip next JSON element, and return its content as []byte.
+// The []byte can be kept, it is a copy of data.
+func (iter *Iterator) SkipAndReturnBytes() []byte {
+ iter.startCapture(iter.head)
+ iter.Skip()
+ return iter.stopCapture()
+}
+
+// SkipAndAppendBytes skips next JSON element and appends its content to
+// buffer, returning the result.
+func (iter *Iterator) SkipAndAppendBytes(buf []byte) []byte {
+ iter.startCaptureTo(buf, iter.head)
+ iter.Skip()
+ return iter.stopCapture()
+}
+
+func (iter *Iterator) startCaptureTo(buf []byte, captureStartedAt int) {
+ if iter.captured != nil {
+ panic("already in capture mode")
+ }
+ iter.captureStartedAt = captureStartedAt
+ iter.captured = buf
+}
+
+func (iter *Iterator) startCapture(captureStartedAt int) {
+ iter.startCaptureTo(make([]byte, 0, 32), captureStartedAt)
+}
+
+func (iter *Iterator) stopCapture() []byte {
+ if iter.captured == nil {
+ panic("not in capture mode")
+ }
+ captured := iter.captured
+ remaining := iter.buf[iter.captureStartedAt:iter.head]
+ iter.captureStartedAt = -1
+ iter.captured = nil
+ return append(captured, remaining...)
+}
+
+// Skip skips a json object and positions to relatively the next json object
+func (iter *Iterator) Skip() {
+ c := iter.nextToken()
+ switch c {
+ case '"':
+ iter.skipString()
+ case 'n':
+ iter.skipThreeBytes('u', 'l', 'l') // null
+ case 't':
+ iter.skipThreeBytes('r', 'u', 'e') // true
+ case 'f':
+ iter.skipFourBytes('a', 'l', 's', 'e') // false
+ case '0':
+ iter.unreadByte()
+ iter.ReadFloat32()
+ case '-', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ iter.skipNumber()
+ case '[':
+ iter.skipArray()
+ case '{':
+ iter.skipObject()
+ default:
+ iter.ReportError("Skip", fmt.Sprintf("do not know how to skip: %v", c))
+ return
+ }
+}
+
+func (iter *Iterator) skipFourBytes(b1, b2, b3, b4 byte) {
+ if iter.readByte() != b1 {
+ iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
+ return
+ }
+ if iter.readByte() != b2 {
+ iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
+ return
+ }
+ if iter.readByte() != b3 {
+ iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
+ return
+ }
+ if iter.readByte() != b4 {
+ iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
+ return
+ }
+}
+
+func (iter *Iterator) skipThreeBytes(b1, b2, b3 byte) {
+ if iter.readByte() != b1 {
+ iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3})))
+ return
+ }
+ if iter.readByte() != b2 {
+ iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3})))
+ return
+ }
+ if iter.readByte() != b3 {
+ iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3})))
+ return
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/iter_skip_sloppy.go b/vendor/github.com/json-iterator/go/iter_skip_sloppy.go
new file mode 100644
index 00000000..9303de41
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_skip_sloppy.go
@@ -0,0 +1,163 @@
+//+build jsoniter_sloppy
+
+package jsoniter
+
+// sloppy but faster implementation, do not validate the input json
+
+func (iter *Iterator) skipNumber() {
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ switch c {
+ case ' ', '\n', '\r', '\t', ',', '}', ']':
+ iter.head = i
+ return
+ }
+ }
+ if !iter.loadMore() {
+ return
+ }
+ }
+}
+
+func (iter *Iterator) skipArray() {
+ level := 1
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ switch iter.buf[i] {
+ case '"': // If inside string, skip it
+ iter.head = i + 1
+ iter.skipString()
+ i = iter.head - 1 // it will be i++ soon
+ case '[': // If open symbol, increase level
+ level++
+ if !iter.incrementDepth() {
+ return
+ }
+ case ']': // If close symbol, increase level
+ level--
+ if !iter.decrementDepth() {
+ return
+ }
+
+ // If we have returned to the original level, we're done
+ if level == 0 {
+ iter.head = i + 1
+ return
+ }
+ }
+ }
+ if !iter.loadMore() {
+ iter.ReportError("skipObject", "incomplete array")
+ return
+ }
+ }
+}
+
+func (iter *Iterator) skipObject() {
+ level := 1
+ if !iter.incrementDepth() {
+ return
+ }
+
+ for {
+ for i := iter.head; i < iter.tail; i++ {
+ switch iter.buf[i] {
+ case '"': // If inside string, skip it
+ iter.head = i + 1
+ iter.skipString()
+ i = iter.head - 1 // it will be i++ soon
+ case '{': // If open symbol, increase level
+ level++
+ if !iter.incrementDepth() {
+ return
+ }
+ case '}': // If close symbol, increase level
+ level--
+ if !iter.decrementDepth() {
+ return
+ }
+
+ // If we have returned to the original level, we're done
+ if level == 0 {
+ iter.head = i + 1
+ return
+ }
+ }
+ }
+ if !iter.loadMore() {
+ iter.ReportError("skipObject", "incomplete object")
+ return
+ }
+ }
+}
+
+func (iter *Iterator) skipString() {
+ for {
+ end, escaped := iter.findStringEnd()
+ if end == -1 {
+ if !iter.loadMore() {
+ iter.ReportError("skipString", "incomplete string")
+ return
+ }
+ if escaped {
+ iter.head = 1 // skip the first char as last char read is \
+ }
+ } else {
+ iter.head = end
+ return
+ }
+ }
+}
+
+// adapted from: https://github.com/buger/jsonparser/blob/master/parser.go
+// Tries to find the end of string
+// Support if string contains escaped quote symbols.
+func (iter *Iterator) findStringEnd() (int, bool) {
+ escaped := false
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ if c == '"' {
+ if !escaped {
+ return i + 1, false
+ }
+ j := i - 1
+ for {
+ if j < iter.head || iter.buf[j] != '\\' {
+ // even number of backslashes
+ // either end of buffer, or " found
+ return i + 1, true
+ }
+ j--
+ if j < iter.head || iter.buf[j] != '\\' {
+ // odd number of backslashes
+ // it is \" or \\\"
+ break
+ }
+ j--
+ }
+ } else if c == '\\' {
+ escaped = true
+ }
+ }
+ j := iter.tail - 1
+ for {
+ if j < iter.head || iter.buf[j] != '\\' {
+ // even number of backslashes
+ // either end of buffer, or " found
+ return -1, false // do not end with \
+ }
+ j--
+ if j < iter.head || iter.buf[j] != '\\' {
+ // odd number of backslashes
+ // it is \" or \\\"
+ break
+ }
+ j--
+
+ }
+ return -1, true // end with \
+}
diff --git a/vendor/github.com/json-iterator/go/iter_skip_strict.go b/vendor/github.com/json-iterator/go/iter_skip_strict.go
new file mode 100644
index 00000000..6cf66d04
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_skip_strict.go
@@ -0,0 +1,99 @@
+//+build !jsoniter_sloppy
+
+package jsoniter
+
+import (
+ "fmt"
+ "io"
+)
+
+func (iter *Iterator) skipNumber() {
+ if !iter.trySkipNumber() {
+ iter.unreadByte()
+ if iter.Error != nil && iter.Error != io.EOF {
+ return
+ }
+ iter.ReadFloat64()
+ if iter.Error != nil && iter.Error != io.EOF {
+ iter.Error = nil
+ iter.ReadBigFloat()
+ }
+ }
+}
+
+func (iter *Iterator) trySkipNumber() bool {
+ dotFound := false
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ switch c {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ case '.':
+ if dotFound {
+ iter.ReportError("validateNumber", `more than one dot found in number`)
+ return true // already failed
+ }
+ if i+1 == iter.tail {
+ return false
+ }
+ c = iter.buf[i+1]
+ switch c {
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ default:
+ iter.ReportError("validateNumber", `missing digit after dot`)
+ return true // already failed
+ }
+ dotFound = true
+ default:
+ switch c {
+ case ',', ']', '}', ' ', '\t', '\n', '\r':
+ if iter.head == i {
+ return false // if - without following digits
+ }
+ iter.head = i
+ return true // must be valid
+ }
+ return false // may be invalid
+ }
+ }
+ return false
+}
+
+func (iter *Iterator) skipString() {
+ if !iter.trySkipString() {
+ iter.unreadByte()
+ iter.ReadString()
+ }
+}
+
+func (iter *Iterator) trySkipString() bool {
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ if c == '"' {
+ iter.head = i + 1
+ return true // valid
+ } else if c == '\\' {
+ return false
+ } else if c < ' ' {
+ iter.ReportError("trySkipString",
+ fmt.Sprintf(`invalid control character found: %d`, c))
+ return true // already failed
+ }
+ }
+ return false
+}
+
+func (iter *Iterator) skipObject() {
+ iter.unreadByte()
+ iter.ReadObjectCB(func(iter *Iterator, field string) bool {
+ iter.Skip()
+ return true
+ })
+}
+
+func (iter *Iterator) skipArray() {
+ iter.unreadByte()
+ iter.ReadArrayCB(func(iter *Iterator) bool {
+ iter.Skip()
+ return true
+ })
+}
diff --git a/vendor/github.com/json-iterator/go/iter_str.go b/vendor/github.com/json-iterator/go/iter_str.go
new file mode 100644
index 00000000..adc487ea
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/iter_str.go
@@ -0,0 +1,215 @@
+package jsoniter
+
+import (
+ "fmt"
+ "unicode/utf16"
+)
+
+// ReadString read string from iterator
+func (iter *Iterator) ReadString() (ret string) {
+ c := iter.nextToken()
+ if c == '"' {
+ for i := iter.head; i < iter.tail; i++ {
+ c := iter.buf[i]
+ if c == '"' {
+ ret = string(iter.buf[iter.head:i])
+ iter.head = i + 1
+ return ret
+ } else if c == '\\' {
+ break
+ } else if c < ' ' {
+ iter.ReportError("ReadString",
+ fmt.Sprintf(`invalid control character found: %d`, c))
+ return
+ }
+ }
+ return iter.readStringSlowPath()
+ } else if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ return ""
+ }
+ iter.ReportError("ReadString", `expects " or n, but found `+string([]byte{c}))
+ return
+}
+
+func (iter *Iterator) readStringSlowPath() (ret string) {
+ var str []byte
+ var c byte
+ for iter.Error == nil {
+ c = iter.readByte()
+ if c == '"' {
+ return string(str)
+ }
+ if c == '\\' {
+ c = iter.readByte()
+ str = iter.readEscapedChar(c, str)
+ } else {
+ str = append(str, c)
+ }
+ }
+ iter.ReportError("readStringSlowPath", "unexpected end of input")
+ return
+}
+
+func (iter *Iterator) readEscapedChar(c byte, str []byte) []byte {
+ switch c {
+ case 'u':
+ r := iter.readU4()
+ if utf16.IsSurrogate(r) {
+ c = iter.readByte()
+ if iter.Error != nil {
+ return nil
+ }
+ if c != '\\' {
+ iter.unreadByte()
+ str = appendRune(str, r)
+ return str
+ }
+ c = iter.readByte()
+ if iter.Error != nil {
+ return nil
+ }
+ if c != 'u' {
+ str = appendRune(str, r)
+ return iter.readEscapedChar(c, str)
+ }
+ r2 := iter.readU4()
+ if iter.Error != nil {
+ return nil
+ }
+ combined := utf16.DecodeRune(r, r2)
+ if combined == '\uFFFD' {
+ str = appendRune(str, r)
+ str = appendRune(str, r2)
+ } else {
+ str = appendRune(str, combined)
+ }
+ } else {
+ str = appendRune(str, r)
+ }
+ case '"':
+ str = append(str, '"')
+ case '\\':
+ str = append(str, '\\')
+ case '/':
+ str = append(str, '/')
+ case 'b':
+ str = append(str, '\b')
+ case 'f':
+ str = append(str, '\f')
+ case 'n':
+ str = append(str, '\n')
+ case 'r':
+ str = append(str, '\r')
+ case 't':
+ str = append(str, '\t')
+ default:
+ iter.ReportError("readEscapedChar",
+ `invalid escape char after \`)
+ return nil
+ }
+ return str
+}
+
+// ReadStringAsSlice read string from iterator without copying into string form.
+// The []byte can not be kept, as it will change after next iterator call.
+func (iter *Iterator) ReadStringAsSlice() (ret []byte) {
+ c := iter.nextToken()
+ if c == '"' {
+ for i := iter.head; i < iter.tail; i++ {
+ // require ascii string and no escape
+ // for: field name, base64, number
+ if iter.buf[i] == '"' {
+ // fast path: reuse the underlying buffer
+ ret = iter.buf[iter.head:i]
+ iter.head = i + 1
+ return ret
+ }
+ }
+ readLen := iter.tail - iter.head
+ copied := make([]byte, readLen, readLen*2)
+ copy(copied, iter.buf[iter.head:iter.tail])
+ iter.head = iter.tail
+ for iter.Error == nil {
+ c := iter.readByte()
+ if c == '"' {
+ return copied
+ }
+ copied = append(copied, c)
+ }
+ return copied
+ }
+ iter.ReportError("ReadStringAsSlice", `expects " or n, but found `+string([]byte{c}))
+ return
+}
+
+func (iter *Iterator) readU4() (ret rune) {
+ for i := 0; i < 4; i++ {
+ c := iter.readByte()
+ if iter.Error != nil {
+ return
+ }
+ if c >= '0' && c <= '9' {
+ ret = ret*16 + rune(c-'0')
+ } else if c >= 'a' && c <= 'f' {
+ ret = ret*16 + rune(c-'a'+10)
+ } else if c >= 'A' && c <= 'F' {
+ ret = ret*16 + rune(c-'A'+10)
+ } else {
+ iter.ReportError("readU4", "expects 0~9 or a~f, but found "+string([]byte{c}))
+ return
+ }
+ }
+ return ret
+}
+
+const (
+ t1 = 0x00 // 0000 0000
+ tx = 0x80 // 1000 0000
+ t2 = 0xC0 // 1100 0000
+ t3 = 0xE0 // 1110 0000
+ t4 = 0xF0 // 1111 0000
+ t5 = 0xF8 // 1111 1000
+
+ maskx = 0x3F // 0011 1111
+ mask2 = 0x1F // 0001 1111
+ mask3 = 0x0F // 0000 1111
+ mask4 = 0x07 // 0000 0111
+
+ rune1Max = 1<<7 - 1
+ rune2Max = 1<<11 - 1
+ rune3Max = 1<<16 - 1
+
+ surrogateMin = 0xD800
+ surrogateMax = 0xDFFF
+
+ maxRune = '\U0010FFFF' // Maximum valid Unicode code point.
+ runeError = '\uFFFD' // the "error" Rune or "Unicode replacement character"
+)
+
+func appendRune(p []byte, r rune) []byte {
+ // Negative values are erroneous. Making it unsigned addresses the problem.
+ switch i := uint32(r); {
+ case i <= rune1Max:
+ p = append(p, byte(r))
+ return p
+ case i <= rune2Max:
+ p = append(p, t2|byte(r>>6))
+ p = append(p, tx|byte(r)&maskx)
+ return p
+ case i > maxRune, surrogateMin <= i && i <= surrogateMax:
+ r = runeError
+ fallthrough
+ case i <= rune3Max:
+ p = append(p, t3|byte(r>>12))
+ p = append(p, tx|byte(r>>6)&maskx)
+ p = append(p, tx|byte(r)&maskx)
+ return p
+ default:
+ p = append(p, t4|byte(r>>18))
+ p = append(p, tx|byte(r>>12)&maskx)
+ p = append(p, tx|byte(r>>6)&maskx)
+ p = append(p, tx|byte(r)&maskx)
+ return p
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/jsoniter.go b/vendor/github.com/json-iterator/go/jsoniter.go
new file mode 100644
index 00000000..c2934f91
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/jsoniter.go
@@ -0,0 +1,18 @@
+// Package jsoniter implements encoding and decoding of JSON as defined in
+// RFC 4627 and provides interfaces with identical syntax of standard lib encoding/json.
+// Converting from encoding/json to jsoniter is no more than replacing the package with jsoniter
+// and variable type declarations (if any).
+// jsoniter interfaces gives 100% compatibility with code using standard lib.
+//
+// "JSON and Go"
+// (https://golang.org/doc/articles/json_and_go.html)
+// gives a description of how Marshal/Unmarshal operate
+// between arbitrary or predefined json objects and bytes,
+// and it applies to jsoniter.Marshal/Unmarshal as well.
+//
+// Besides, jsoniter.Iterator provides a different set of interfaces
+// iterating given bytes/string/reader
+// and yielding parsed elements one by one.
+// This set of interfaces reads input as required and gives
+// better performance.
+package jsoniter
diff --git a/vendor/github.com/json-iterator/go/pool.go b/vendor/github.com/json-iterator/go/pool.go
new file mode 100644
index 00000000..e2389b56
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/pool.go
@@ -0,0 +1,42 @@
+package jsoniter
+
+import (
+ "io"
+)
+
+// IteratorPool a thread safe pool of iterators with same configuration
+type IteratorPool interface {
+ BorrowIterator(data []byte) *Iterator
+ ReturnIterator(iter *Iterator)
+}
+
+// StreamPool a thread safe pool of streams with same configuration
+type StreamPool interface {
+ BorrowStream(writer io.Writer) *Stream
+ ReturnStream(stream *Stream)
+}
+
+func (cfg *frozenConfig) BorrowStream(writer io.Writer) *Stream {
+ stream := cfg.streamPool.Get().(*Stream)
+ stream.Reset(writer)
+ return stream
+}
+
+func (cfg *frozenConfig) ReturnStream(stream *Stream) {
+ stream.out = nil
+ stream.Error = nil
+ stream.Attachment = nil
+ cfg.streamPool.Put(stream)
+}
+
+func (cfg *frozenConfig) BorrowIterator(data []byte) *Iterator {
+ iter := cfg.iteratorPool.Get().(*Iterator)
+ iter.ResetBytes(data)
+ return iter
+}
+
+func (cfg *frozenConfig) ReturnIterator(iter *Iterator) {
+ iter.Error = nil
+ iter.Attachment = nil
+ cfg.iteratorPool.Put(iter)
+}
diff --git a/vendor/github.com/json-iterator/go/reflect.go b/vendor/github.com/json-iterator/go/reflect.go
new file mode 100644
index 00000000..39acb320
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect.go
@@ -0,0 +1,337 @@
+package jsoniter
+
+import (
+ "fmt"
+ "reflect"
+ "unsafe"
+
+ "github.com/modern-go/reflect2"
+)
+
+// ValDecoder is an internal type registered to cache as needed.
+// Don't confuse jsoniter.ValDecoder with json.Decoder.
+// For json.Decoder's adapter, refer to jsoniter.AdapterDecoder(todo link).
+//
+// Reflection on type to create decoders, which is then cached
+// Reflection on value is avoided as we can, as the reflect.Value itself will allocate, with following exceptions
+// 1. create instance of new value, for example *int will need a int to be allocated
+// 2. append to slice, if the existing cap is not enough, allocate will be done using Reflect.New
+// 3. assignment to map, both key and value will be reflect.Value
+// For a simple struct binding, it will be reflect.Value free and allocation free
+type ValDecoder interface {
+ Decode(ptr unsafe.Pointer, iter *Iterator)
+}
+
+// ValEncoder is an internal type registered to cache as needed.
+// Don't confuse jsoniter.ValEncoder with json.Encoder.
+// For json.Encoder's adapter, refer to jsoniter.AdapterEncoder(todo godoc link).
+type ValEncoder interface {
+ IsEmpty(ptr unsafe.Pointer) bool
+ Encode(ptr unsafe.Pointer, stream *Stream)
+}
+
+type checkIsEmpty interface {
+ IsEmpty(ptr unsafe.Pointer) bool
+}
+
+type ctx struct {
+ *frozenConfig
+ prefix string
+ encoders map[reflect2.Type]ValEncoder
+ decoders map[reflect2.Type]ValDecoder
+}
+
+func (b *ctx) caseSensitive() bool {
+ if b.frozenConfig == nil {
+ // default is case-insensitive
+ return false
+ }
+ return b.frozenConfig.caseSensitive
+}
+
+func (b *ctx) append(prefix string) *ctx {
+ return &ctx{
+ frozenConfig: b.frozenConfig,
+ prefix: b.prefix + " " + prefix,
+ encoders: b.encoders,
+ decoders: b.decoders,
+ }
+}
+
+// ReadVal copy the underlying JSON into go interface, same as json.Unmarshal
+func (iter *Iterator) ReadVal(obj interface{}) {
+ depth := iter.depth
+ cacheKey := reflect2.RTypeOf(obj)
+ decoder := iter.cfg.getDecoderFromCache(cacheKey)
+ if decoder == nil {
+ typ := reflect2.TypeOf(obj)
+ if typ == nil || typ.Kind() != reflect.Ptr {
+ iter.ReportError("ReadVal", "can only unmarshal into pointer")
+ return
+ }
+ decoder = iter.cfg.DecoderOf(typ)
+ }
+ ptr := reflect2.PtrOf(obj)
+ if ptr == nil {
+ iter.ReportError("ReadVal", "can not read into nil pointer")
+ return
+ }
+ decoder.Decode(ptr, iter)
+ if iter.depth != depth {
+ iter.ReportError("ReadVal", "unexpected mismatched nesting")
+ return
+ }
+}
+
+// WriteVal copy the go interface into underlying JSON, same as json.Marshal
+func (stream *Stream) WriteVal(val interface{}) {
+ if nil == val {
+ stream.WriteNil()
+ return
+ }
+ cacheKey := reflect2.RTypeOf(val)
+ encoder := stream.cfg.getEncoderFromCache(cacheKey)
+ if encoder == nil {
+ typ := reflect2.TypeOf(val)
+ encoder = stream.cfg.EncoderOf(typ)
+ }
+ encoder.Encode(reflect2.PtrOf(val), stream)
+}
+
+func (cfg *frozenConfig) DecoderOf(typ reflect2.Type) ValDecoder {
+ cacheKey := typ.RType()
+ decoder := cfg.getDecoderFromCache(cacheKey)
+ if decoder != nil {
+ return decoder
+ }
+ ctx := &ctx{
+ frozenConfig: cfg,
+ prefix: "",
+ decoders: map[reflect2.Type]ValDecoder{},
+ encoders: map[reflect2.Type]ValEncoder{},
+ }
+ ptrType := typ.(*reflect2.UnsafePtrType)
+ decoder = decoderOfType(ctx, ptrType.Elem())
+ cfg.addDecoderToCache(cacheKey, decoder)
+ return decoder
+}
+
+func decoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder {
+ decoder := getTypeDecoderFromExtension(ctx, typ)
+ if decoder != nil {
+ return decoder
+ }
+ decoder = createDecoderOfType(ctx, typ)
+ for _, extension := range extensions {
+ decoder = extension.DecorateDecoder(typ, decoder)
+ }
+ decoder = ctx.decoderExtension.DecorateDecoder(typ, decoder)
+ for _, extension := range ctx.extraExtensions {
+ decoder = extension.DecorateDecoder(typ, decoder)
+ }
+ return decoder
+}
+
+func createDecoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder {
+ decoder := ctx.decoders[typ]
+ if decoder != nil {
+ return decoder
+ }
+ placeholder := &placeholderDecoder{}
+ ctx.decoders[typ] = placeholder
+ decoder = _createDecoderOfType(ctx, typ)
+ placeholder.decoder = decoder
+ return decoder
+}
+
+func _createDecoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder {
+ decoder := createDecoderOfJsonRawMessage(ctx, typ)
+ if decoder != nil {
+ return decoder
+ }
+ decoder = createDecoderOfJsonNumber(ctx, typ)
+ if decoder != nil {
+ return decoder
+ }
+ decoder = createDecoderOfMarshaler(ctx, typ)
+ if decoder != nil {
+ return decoder
+ }
+ decoder = createDecoderOfAny(ctx, typ)
+ if decoder != nil {
+ return decoder
+ }
+ decoder = createDecoderOfNative(ctx, typ)
+ if decoder != nil {
+ return decoder
+ }
+ switch typ.Kind() {
+ case reflect.Interface:
+ ifaceType, isIFace := typ.(*reflect2.UnsafeIFaceType)
+ if isIFace {
+ return &ifaceDecoder{valType: ifaceType}
+ }
+ return &efaceDecoder{}
+ case reflect.Struct:
+ return decoderOfStruct(ctx, typ)
+ case reflect.Array:
+ return decoderOfArray(ctx, typ)
+ case reflect.Slice:
+ return decoderOfSlice(ctx, typ)
+ case reflect.Map:
+ return decoderOfMap(ctx, typ)
+ case reflect.Ptr:
+ return decoderOfOptional(ctx, typ)
+ default:
+ return &lazyErrorDecoder{err: fmt.Errorf("%s%s is unsupported type", ctx.prefix, typ.String())}
+ }
+}
+
+func (cfg *frozenConfig) EncoderOf(typ reflect2.Type) ValEncoder {
+ cacheKey := typ.RType()
+ encoder := cfg.getEncoderFromCache(cacheKey)
+ if encoder != nil {
+ return encoder
+ }
+ ctx := &ctx{
+ frozenConfig: cfg,
+ prefix: "",
+ decoders: map[reflect2.Type]ValDecoder{},
+ encoders: map[reflect2.Type]ValEncoder{},
+ }
+ encoder = encoderOfType(ctx, typ)
+ if typ.LikePtr() {
+ encoder = &onePtrEncoder{encoder}
+ }
+ cfg.addEncoderToCache(cacheKey, encoder)
+ return encoder
+}
+
+type onePtrEncoder struct {
+ encoder ValEncoder
+}
+
+func (encoder *onePtrEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.encoder.IsEmpty(unsafe.Pointer(&ptr))
+}
+
+func (encoder *onePtrEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ encoder.encoder.Encode(unsafe.Pointer(&ptr), stream)
+}
+
+func encoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder {
+ encoder := getTypeEncoderFromExtension(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ encoder = createEncoderOfType(ctx, typ)
+ for _, extension := range extensions {
+ encoder = extension.DecorateEncoder(typ, encoder)
+ }
+ encoder = ctx.encoderExtension.DecorateEncoder(typ, encoder)
+ for _, extension := range ctx.extraExtensions {
+ encoder = extension.DecorateEncoder(typ, encoder)
+ }
+ return encoder
+}
+
+func createEncoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder {
+ encoder := ctx.encoders[typ]
+ if encoder != nil {
+ return encoder
+ }
+ placeholder := &placeholderEncoder{}
+ ctx.encoders[typ] = placeholder
+ encoder = _createEncoderOfType(ctx, typ)
+ placeholder.encoder = encoder
+ return encoder
+}
+func _createEncoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder {
+ encoder := createEncoderOfJsonRawMessage(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ encoder = createEncoderOfJsonNumber(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ encoder = createEncoderOfMarshaler(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ encoder = createEncoderOfAny(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ encoder = createEncoderOfNative(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ kind := typ.Kind()
+ switch kind {
+ case reflect.Interface:
+ return &dynamicEncoder{typ}
+ case reflect.Struct:
+ return encoderOfStruct(ctx, typ)
+ case reflect.Array:
+ return encoderOfArray(ctx, typ)
+ case reflect.Slice:
+ return encoderOfSlice(ctx, typ)
+ case reflect.Map:
+ return encoderOfMap(ctx, typ)
+ case reflect.Ptr:
+ return encoderOfOptional(ctx, typ)
+ default:
+ return &lazyErrorEncoder{err: fmt.Errorf("%s%s is unsupported type", ctx.prefix, typ.String())}
+ }
+}
+
+type lazyErrorDecoder struct {
+ err error
+}
+
+func (decoder *lazyErrorDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.WhatIsNext() != NilValue {
+ if iter.Error == nil {
+ iter.Error = decoder.err
+ }
+ } else {
+ iter.Skip()
+ }
+}
+
+type lazyErrorEncoder struct {
+ err error
+}
+
+func (encoder *lazyErrorEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if ptr == nil {
+ stream.WriteNil()
+ } else if stream.Error == nil {
+ stream.Error = encoder.err
+ }
+}
+
+func (encoder *lazyErrorEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return false
+}
+
+type placeholderDecoder struct {
+ decoder ValDecoder
+}
+
+func (decoder *placeholderDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ decoder.decoder.Decode(ptr, iter)
+}
+
+type placeholderEncoder struct {
+ encoder ValEncoder
+}
+
+func (encoder *placeholderEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ encoder.encoder.Encode(ptr, stream)
+}
+
+func (encoder *placeholderEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.encoder.IsEmpty(ptr)
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_array.go b/vendor/github.com/json-iterator/go/reflect_array.go
new file mode 100644
index 00000000..13a0b7b0
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_array.go
@@ -0,0 +1,104 @@
+package jsoniter
+
+import (
+ "fmt"
+ "github.com/modern-go/reflect2"
+ "io"
+ "unsafe"
+)
+
+func decoderOfArray(ctx *ctx, typ reflect2.Type) ValDecoder {
+ arrayType := typ.(*reflect2.UnsafeArrayType)
+ decoder := decoderOfType(ctx.append("[arrayElem]"), arrayType.Elem())
+ return &arrayDecoder{arrayType, decoder}
+}
+
+func encoderOfArray(ctx *ctx, typ reflect2.Type) ValEncoder {
+ arrayType := typ.(*reflect2.UnsafeArrayType)
+ if arrayType.Len() == 0 {
+ return emptyArrayEncoder{}
+ }
+ encoder := encoderOfType(ctx.append("[arrayElem]"), arrayType.Elem())
+ return &arrayEncoder{arrayType, encoder}
+}
+
+type emptyArrayEncoder struct{}
+
+func (encoder emptyArrayEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteEmptyArray()
+}
+
+func (encoder emptyArrayEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return true
+}
+
+type arrayEncoder struct {
+ arrayType *reflect2.UnsafeArrayType
+ elemEncoder ValEncoder
+}
+
+func (encoder *arrayEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteArrayStart()
+ elemPtr := unsafe.Pointer(ptr)
+ encoder.elemEncoder.Encode(elemPtr, stream)
+ for i := 1; i < encoder.arrayType.Len(); i++ {
+ stream.WriteMore()
+ elemPtr = encoder.arrayType.UnsafeGetIndex(ptr, i)
+ encoder.elemEncoder.Encode(elemPtr, stream)
+ }
+ stream.WriteArrayEnd()
+ if stream.Error != nil && stream.Error != io.EOF {
+ stream.Error = fmt.Errorf("%v: %s", encoder.arrayType, stream.Error.Error())
+ }
+}
+
+func (encoder *arrayEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return false
+}
+
+type arrayDecoder struct {
+ arrayType *reflect2.UnsafeArrayType
+ elemDecoder ValDecoder
+}
+
+func (decoder *arrayDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ decoder.doDecode(ptr, iter)
+ if iter.Error != nil && iter.Error != io.EOF {
+ iter.Error = fmt.Errorf("%v: %s", decoder.arrayType, iter.Error.Error())
+ }
+}
+
+func (decoder *arrayDecoder) doDecode(ptr unsafe.Pointer, iter *Iterator) {
+ c := iter.nextToken()
+ arrayType := decoder.arrayType
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ return
+ }
+ if c != '[' {
+ iter.ReportError("decode array", "expect [ or n, but found "+string([]byte{c}))
+ return
+ }
+ c = iter.nextToken()
+ if c == ']' {
+ return
+ }
+ iter.unreadByte()
+ elemPtr := arrayType.UnsafeGetIndex(ptr, 0)
+ decoder.elemDecoder.Decode(elemPtr, iter)
+ length := 1
+ for c = iter.nextToken(); c == ','; c = iter.nextToken() {
+ if length >= arrayType.Len() {
+ iter.Skip()
+ continue
+ }
+ idx := length
+ length += 1
+ elemPtr = arrayType.UnsafeGetIndex(ptr, idx)
+ decoder.elemDecoder.Decode(elemPtr, iter)
+ }
+ if c != ']' {
+ iter.ReportError("decode array", "expect ], but found "+string([]byte{c}))
+ return
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_dynamic.go b/vendor/github.com/json-iterator/go/reflect_dynamic.go
new file mode 100644
index 00000000..8b6bc8b4
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_dynamic.go
@@ -0,0 +1,70 @@
+package jsoniter
+
+import (
+ "github.com/modern-go/reflect2"
+ "reflect"
+ "unsafe"
+)
+
+type dynamicEncoder struct {
+ valType reflect2.Type
+}
+
+func (encoder *dynamicEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ obj := encoder.valType.UnsafeIndirect(ptr)
+ stream.WriteVal(obj)
+}
+
+func (encoder *dynamicEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.valType.UnsafeIndirect(ptr) == nil
+}
+
+type efaceDecoder struct {
+}
+
+func (decoder *efaceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ pObj := (*interface{})(ptr)
+ obj := *pObj
+ if obj == nil {
+ *pObj = iter.Read()
+ return
+ }
+ typ := reflect2.TypeOf(obj)
+ if typ.Kind() != reflect.Ptr {
+ *pObj = iter.Read()
+ return
+ }
+ ptrType := typ.(*reflect2.UnsafePtrType)
+ ptrElemType := ptrType.Elem()
+ if iter.WhatIsNext() == NilValue {
+ if ptrElemType.Kind() != reflect.Ptr {
+ iter.skipFourBytes('n', 'u', 'l', 'l')
+ *pObj = nil
+ return
+ }
+ }
+ if reflect2.IsNil(obj) {
+ obj := ptrElemType.New()
+ iter.ReadVal(obj)
+ *pObj = obj
+ return
+ }
+ iter.ReadVal(obj)
+}
+
+type ifaceDecoder struct {
+ valType *reflect2.UnsafeIFaceType
+}
+
+func (decoder *ifaceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.ReadNil() {
+ decoder.valType.UnsafeSet(ptr, decoder.valType.UnsafeNew())
+ return
+ }
+ obj := decoder.valType.UnsafeIndirect(ptr)
+ if reflect2.IsNil(obj) {
+ iter.ReportError("decode non empty interface", "can not unmarshal into nil")
+ return
+ }
+ iter.ReadVal(obj)
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_extension.go b/vendor/github.com/json-iterator/go/reflect_extension.go
new file mode 100644
index 00000000..74a97bfe
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_extension.go
@@ -0,0 +1,483 @@
+package jsoniter
+
+import (
+ "fmt"
+ "github.com/modern-go/reflect2"
+ "reflect"
+ "sort"
+ "strings"
+ "unicode"
+ "unsafe"
+)
+
+var typeDecoders = map[string]ValDecoder{}
+var fieldDecoders = map[string]ValDecoder{}
+var typeEncoders = map[string]ValEncoder{}
+var fieldEncoders = map[string]ValEncoder{}
+var extensions = []Extension{}
+
+// StructDescriptor describe how should we encode/decode the struct
+type StructDescriptor struct {
+ Type reflect2.Type
+ Fields []*Binding
+}
+
+// GetField get one field from the descriptor by its name.
+// Can not use map here to keep field orders.
+func (structDescriptor *StructDescriptor) GetField(fieldName string) *Binding {
+ for _, binding := range structDescriptor.Fields {
+ if binding.Field.Name() == fieldName {
+ return binding
+ }
+ }
+ return nil
+}
+
+// Binding describe how should we encode/decode the struct field
+type Binding struct {
+ levels []int
+ Field reflect2.StructField
+ FromNames []string
+ ToNames []string
+ Encoder ValEncoder
+ Decoder ValDecoder
+}
+
+// Extension the one for all SPI. Customize encoding/decoding by specifying alternate encoder/decoder.
+// Can also rename fields by UpdateStructDescriptor.
+type Extension interface {
+ UpdateStructDescriptor(structDescriptor *StructDescriptor)
+ CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
+ CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
+ CreateDecoder(typ reflect2.Type) ValDecoder
+ CreateEncoder(typ reflect2.Type) ValEncoder
+ DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
+ DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
+}
+
+// DummyExtension embed this type get dummy implementation for all methods of Extension
+type DummyExtension struct {
+}
+
+// UpdateStructDescriptor No-op
+func (extension *DummyExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) {
+}
+
+// CreateMapKeyDecoder No-op
+func (extension *DummyExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder {
+ return nil
+}
+
+// CreateMapKeyEncoder No-op
+func (extension *DummyExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder {
+ return nil
+}
+
+// CreateDecoder No-op
+func (extension *DummyExtension) CreateDecoder(typ reflect2.Type) ValDecoder {
+ return nil
+}
+
+// CreateEncoder No-op
+func (extension *DummyExtension) CreateEncoder(typ reflect2.Type) ValEncoder {
+ return nil
+}
+
+// DecorateDecoder No-op
+func (extension *DummyExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder {
+ return decoder
+}
+
+// DecorateEncoder No-op
+func (extension *DummyExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder {
+ return encoder
+}
+
+type EncoderExtension map[reflect2.Type]ValEncoder
+
+// UpdateStructDescriptor No-op
+func (extension EncoderExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) {
+}
+
+// CreateDecoder No-op
+func (extension EncoderExtension) CreateDecoder(typ reflect2.Type) ValDecoder {
+ return nil
+}
+
+// CreateEncoder get encoder from map
+func (extension EncoderExtension) CreateEncoder(typ reflect2.Type) ValEncoder {
+ return extension[typ]
+}
+
+// CreateMapKeyDecoder No-op
+func (extension EncoderExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder {
+ return nil
+}
+
+// CreateMapKeyEncoder No-op
+func (extension EncoderExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder {
+ return nil
+}
+
+// DecorateDecoder No-op
+func (extension EncoderExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder {
+ return decoder
+}
+
+// DecorateEncoder No-op
+func (extension EncoderExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder {
+ return encoder
+}
+
+type DecoderExtension map[reflect2.Type]ValDecoder
+
+// UpdateStructDescriptor No-op
+func (extension DecoderExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) {
+}
+
+// CreateMapKeyDecoder No-op
+func (extension DecoderExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder {
+ return nil
+}
+
+// CreateMapKeyEncoder No-op
+func (extension DecoderExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder {
+ return nil
+}
+
+// CreateDecoder get decoder from map
+func (extension DecoderExtension) CreateDecoder(typ reflect2.Type) ValDecoder {
+ return extension[typ]
+}
+
+// CreateEncoder No-op
+func (extension DecoderExtension) CreateEncoder(typ reflect2.Type) ValEncoder {
+ return nil
+}
+
+// DecorateDecoder No-op
+func (extension DecoderExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder {
+ return decoder
+}
+
+// DecorateEncoder No-op
+func (extension DecoderExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder {
+ return encoder
+}
+
+type funcDecoder struct {
+ fun DecoderFunc
+}
+
+func (decoder *funcDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ decoder.fun(ptr, iter)
+}
+
+type funcEncoder struct {
+ fun EncoderFunc
+ isEmptyFunc func(ptr unsafe.Pointer) bool
+}
+
+func (encoder *funcEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ encoder.fun(ptr, stream)
+}
+
+func (encoder *funcEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ if encoder.isEmptyFunc == nil {
+ return false
+ }
+ return encoder.isEmptyFunc(ptr)
+}
+
+// DecoderFunc the function form of TypeDecoder
+type DecoderFunc func(ptr unsafe.Pointer, iter *Iterator)
+
+// EncoderFunc the function form of TypeEncoder
+type EncoderFunc func(ptr unsafe.Pointer, stream *Stream)
+
+// RegisterTypeDecoderFunc register TypeDecoder for a type with function
+func RegisterTypeDecoderFunc(typ string, fun DecoderFunc) {
+ typeDecoders[typ] = &funcDecoder{fun}
+}
+
+// RegisterTypeDecoder register TypeDecoder for a typ
+func RegisterTypeDecoder(typ string, decoder ValDecoder) {
+ typeDecoders[typ] = decoder
+}
+
+// RegisterFieldDecoderFunc register TypeDecoder for a struct field with function
+func RegisterFieldDecoderFunc(typ string, field string, fun DecoderFunc) {
+ RegisterFieldDecoder(typ, field, &funcDecoder{fun})
+}
+
+// RegisterFieldDecoder register TypeDecoder for a struct field
+func RegisterFieldDecoder(typ string, field string, decoder ValDecoder) {
+ fieldDecoders[fmt.Sprintf("%s/%s", typ, field)] = decoder
+}
+
+// RegisterTypeEncoderFunc register TypeEncoder for a type with encode/isEmpty function
+func RegisterTypeEncoderFunc(typ string, fun EncoderFunc, isEmptyFunc func(unsafe.Pointer) bool) {
+ typeEncoders[typ] = &funcEncoder{fun, isEmptyFunc}
+}
+
+// RegisterTypeEncoder register TypeEncoder for a type
+func RegisterTypeEncoder(typ string, encoder ValEncoder) {
+ typeEncoders[typ] = encoder
+}
+
+// RegisterFieldEncoderFunc register TypeEncoder for a struct field with encode/isEmpty function
+func RegisterFieldEncoderFunc(typ string, field string, fun EncoderFunc, isEmptyFunc func(unsafe.Pointer) bool) {
+ RegisterFieldEncoder(typ, field, &funcEncoder{fun, isEmptyFunc})
+}
+
+// RegisterFieldEncoder register TypeEncoder for a struct field
+func RegisterFieldEncoder(typ string, field string, encoder ValEncoder) {
+ fieldEncoders[fmt.Sprintf("%s/%s", typ, field)] = encoder
+}
+
+// RegisterExtension register extension
+func RegisterExtension(extension Extension) {
+ extensions = append(extensions, extension)
+}
+
+func getTypeDecoderFromExtension(ctx *ctx, typ reflect2.Type) ValDecoder {
+ decoder := _getTypeDecoderFromExtension(ctx, typ)
+ if decoder != nil {
+ for _, extension := range extensions {
+ decoder = extension.DecorateDecoder(typ, decoder)
+ }
+ decoder = ctx.decoderExtension.DecorateDecoder(typ, decoder)
+ for _, extension := range ctx.extraExtensions {
+ decoder = extension.DecorateDecoder(typ, decoder)
+ }
+ }
+ return decoder
+}
+func _getTypeDecoderFromExtension(ctx *ctx, typ reflect2.Type) ValDecoder {
+ for _, extension := range extensions {
+ decoder := extension.CreateDecoder(typ)
+ if decoder != nil {
+ return decoder
+ }
+ }
+ decoder := ctx.decoderExtension.CreateDecoder(typ)
+ if decoder != nil {
+ return decoder
+ }
+ for _, extension := range ctx.extraExtensions {
+ decoder := extension.CreateDecoder(typ)
+ if decoder != nil {
+ return decoder
+ }
+ }
+ typeName := typ.String()
+ decoder = typeDecoders[typeName]
+ if decoder != nil {
+ return decoder
+ }
+ if typ.Kind() == reflect.Ptr {
+ ptrType := typ.(*reflect2.UnsafePtrType)
+ decoder := typeDecoders[ptrType.Elem().String()]
+ if decoder != nil {
+ return &OptionalDecoder{ptrType.Elem(), decoder}
+ }
+ }
+ return nil
+}
+
+func getTypeEncoderFromExtension(ctx *ctx, typ reflect2.Type) ValEncoder {
+ encoder := _getTypeEncoderFromExtension(ctx, typ)
+ if encoder != nil {
+ for _, extension := range extensions {
+ encoder = extension.DecorateEncoder(typ, encoder)
+ }
+ encoder = ctx.encoderExtension.DecorateEncoder(typ, encoder)
+ for _, extension := range ctx.extraExtensions {
+ encoder = extension.DecorateEncoder(typ, encoder)
+ }
+ }
+ return encoder
+}
+
+func _getTypeEncoderFromExtension(ctx *ctx, typ reflect2.Type) ValEncoder {
+ for _, extension := range extensions {
+ encoder := extension.CreateEncoder(typ)
+ if encoder != nil {
+ return encoder
+ }
+ }
+ encoder := ctx.encoderExtension.CreateEncoder(typ)
+ if encoder != nil {
+ return encoder
+ }
+ for _, extension := range ctx.extraExtensions {
+ encoder := extension.CreateEncoder(typ)
+ if encoder != nil {
+ return encoder
+ }
+ }
+ typeName := typ.String()
+ encoder = typeEncoders[typeName]
+ if encoder != nil {
+ return encoder
+ }
+ if typ.Kind() == reflect.Ptr {
+ typePtr := typ.(*reflect2.UnsafePtrType)
+ encoder := typeEncoders[typePtr.Elem().String()]
+ if encoder != nil {
+ return &OptionalEncoder{encoder}
+ }
+ }
+ return nil
+}
+
+func describeStruct(ctx *ctx, typ reflect2.Type) *StructDescriptor {
+ structType := typ.(*reflect2.UnsafeStructType)
+ embeddedBindings := []*Binding{}
+ bindings := []*Binding{}
+ for i := 0; i < structType.NumField(); i++ {
+ field := structType.Field(i)
+ tag, hastag := field.Tag().Lookup(ctx.getTagKey())
+ if ctx.onlyTaggedField && !hastag && !field.Anonymous() {
+ continue
+ }
+ if tag == "-" || field.Name() == "_" {
+ continue
+ }
+ tagParts := strings.Split(tag, ",")
+ if field.Anonymous() && (tag == "" || tagParts[0] == "") {
+ if field.Type().Kind() == reflect.Struct {
+ structDescriptor := describeStruct(ctx, field.Type())
+ for _, binding := range structDescriptor.Fields {
+ binding.levels = append([]int{i}, binding.levels...)
+ omitempty := binding.Encoder.(*structFieldEncoder).omitempty
+ binding.Encoder = &structFieldEncoder{field, binding.Encoder, omitempty}
+ binding.Decoder = &structFieldDecoder{field, binding.Decoder}
+ embeddedBindings = append(embeddedBindings, binding)
+ }
+ continue
+ } else if field.Type().Kind() == reflect.Ptr {
+ ptrType := field.Type().(*reflect2.UnsafePtrType)
+ if ptrType.Elem().Kind() == reflect.Struct {
+ structDescriptor := describeStruct(ctx, ptrType.Elem())
+ for _, binding := range structDescriptor.Fields {
+ binding.levels = append([]int{i}, binding.levels...)
+ omitempty := binding.Encoder.(*structFieldEncoder).omitempty
+ binding.Encoder = &dereferenceEncoder{binding.Encoder}
+ binding.Encoder = &structFieldEncoder{field, binding.Encoder, omitempty}
+ binding.Decoder = &dereferenceDecoder{ptrType.Elem(), binding.Decoder}
+ binding.Decoder = &structFieldDecoder{field, binding.Decoder}
+ embeddedBindings = append(embeddedBindings, binding)
+ }
+ continue
+ }
+ }
+ }
+ fieldNames := calcFieldNames(field.Name(), tagParts[0], tag)
+ fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name())
+ decoder := fieldDecoders[fieldCacheKey]
+ if decoder == nil {
+ decoder = decoderOfType(ctx.append(field.Name()), field.Type())
+ }
+ encoder := fieldEncoders[fieldCacheKey]
+ if encoder == nil {
+ encoder = encoderOfType(ctx.append(field.Name()), field.Type())
+ }
+ binding := &Binding{
+ Field: field,
+ FromNames: fieldNames,
+ ToNames: fieldNames,
+ Decoder: decoder,
+ Encoder: encoder,
+ }
+ binding.levels = []int{i}
+ bindings = append(bindings, binding)
+ }
+ return createStructDescriptor(ctx, typ, bindings, embeddedBindings)
+}
+func createStructDescriptor(ctx *ctx, typ reflect2.Type, bindings []*Binding, embeddedBindings []*Binding) *StructDescriptor {
+ structDescriptor := &StructDescriptor{
+ Type: typ,
+ Fields: bindings,
+ }
+ for _, extension := range extensions {
+ extension.UpdateStructDescriptor(structDescriptor)
+ }
+ ctx.encoderExtension.UpdateStructDescriptor(structDescriptor)
+ ctx.decoderExtension.UpdateStructDescriptor(structDescriptor)
+ for _, extension := range ctx.extraExtensions {
+ extension.UpdateStructDescriptor(structDescriptor)
+ }
+ processTags(structDescriptor, ctx.frozenConfig)
+ // merge normal & embedded bindings & sort with original order
+ allBindings := sortableBindings(append(embeddedBindings, structDescriptor.Fields...))
+ sort.Sort(allBindings)
+ structDescriptor.Fields = allBindings
+ return structDescriptor
+}
+
+type sortableBindings []*Binding
+
+func (bindings sortableBindings) Len() int {
+ return len(bindings)
+}
+
+func (bindings sortableBindings) Less(i, j int) bool {
+ left := bindings[i].levels
+ right := bindings[j].levels
+ k := 0
+ for {
+ if left[k] < right[k] {
+ return true
+ } else if left[k] > right[k] {
+ return false
+ }
+ k++
+ }
+}
+
+func (bindings sortableBindings) Swap(i, j int) {
+ bindings[i], bindings[j] = bindings[j], bindings[i]
+}
+
+func processTags(structDescriptor *StructDescriptor, cfg *frozenConfig) {
+ for _, binding := range structDescriptor.Fields {
+ shouldOmitEmpty := false
+ tagParts := strings.Split(binding.Field.Tag().Get(cfg.getTagKey()), ",")
+ for _, tagPart := range tagParts[1:] {
+ if tagPart == "omitempty" {
+ shouldOmitEmpty = true
+ } else if tagPart == "string" {
+ if binding.Field.Type().Kind() == reflect.String {
+ binding.Decoder = &stringModeStringDecoder{binding.Decoder, cfg}
+ binding.Encoder = &stringModeStringEncoder{binding.Encoder, cfg}
+ } else {
+ binding.Decoder = &stringModeNumberDecoder{binding.Decoder}
+ binding.Encoder = &stringModeNumberEncoder{binding.Encoder}
+ }
+ }
+ }
+ binding.Decoder = &structFieldDecoder{binding.Field, binding.Decoder}
+ binding.Encoder = &structFieldEncoder{binding.Field, binding.Encoder, shouldOmitEmpty}
+ }
+}
+
+func calcFieldNames(originalFieldName string, tagProvidedFieldName string, wholeTag string) []string {
+ // ignore?
+ if wholeTag == "-" {
+ return []string{}
+ }
+ // rename?
+ var fieldNames []string
+ if tagProvidedFieldName == "" {
+ fieldNames = []string{originalFieldName}
+ } else {
+ fieldNames = []string{tagProvidedFieldName}
+ }
+ // private?
+ isNotExported := unicode.IsLower(rune(originalFieldName[0])) || originalFieldName[0] == '_'
+ if isNotExported {
+ fieldNames = []string{}
+ }
+ return fieldNames
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_json_number.go b/vendor/github.com/json-iterator/go/reflect_json_number.go
new file mode 100644
index 00000000..98d45c1e
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_json_number.go
@@ -0,0 +1,112 @@
+package jsoniter
+
+import (
+ "encoding/json"
+ "github.com/modern-go/reflect2"
+ "strconv"
+ "unsafe"
+)
+
+type Number string
+
+// String returns the literal text of the number.
+func (n Number) String() string { return string(n) }
+
+// Float64 returns the number as a float64.
+func (n Number) Float64() (float64, error) {
+ return strconv.ParseFloat(string(n), 64)
+}
+
+// Int64 returns the number as an int64.
+func (n Number) Int64() (int64, error) {
+ return strconv.ParseInt(string(n), 10, 64)
+}
+
+func CastJsonNumber(val interface{}) (string, bool) {
+ switch typedVal := val.(type) {
+ case json.Number:
+ return string(typedVal), true
+ case Number:
+ return string(typedVal), true
+ }
+ return "", false
+}
+
+var jsonNumberType = reflect2.TypeOfPtr((*json.Number)(nil)).Elem()
+var jsoniterNumberType = reflect2.TypeOfPtr((*Number)(nil)).Elem()
+
+func createDecoderOfJsonNumber(ctx *ctx, typ reflect2.Type) ValDecoder {
+ if typ.AssignableTo(jsonNumberType) {
+ return &jsonNumberCodec{}
+ }
+ if typ.AssignableTo(jsoniterNumberType) {
+ return &jsoniterNumberCodec{}
+ }
+ return nil
+}
+
+func createEncoderOfJsonNumber(ctx *ctx, typ reflect2.Type) ValEncoder {
+ if typ.AssignableTo(jsonNumberType) {
+ return &jsonNumberCodec{}
+ }
+ if typ.AssignableTo(jsoniterNumberType) {
+ return &jsoniterNumberCodec{}
+ }
+ return nil
+}
+
+type jsonNumberCodec struct {
+}
+
+func (codec *jsonNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ switch iter.WhatIsNext() {
+ case StringValue:
+ *((*json.Number)(ptr)) = json.Number(iter.ReadString())
+ case NilValue:
+ iter.skipFourBytes('n', 'u', 'l', 'l')
+ *((*json.Number)(ptr)) = ""
+ default:
+ *((*json.Number)(ptr)) = json.Number([]byte(iter.readNumberAsString()))
+ }
+}
+
+func (codec *jsonNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ number := *((*json.Number)(ptr))
+ if len(number) == 0 {
+ stream.writeByte('0')
+ } else {
+ stream.WriteRaw(string(number))
+ }
+}
+
+func (codec *jsonNumberCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ return len(*((*json.Number)(ptr))) == 0
+}
+
+type jsoniterNumberCodec struct {
+}
+
+func (codec *jsoniterNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ switch iter.WhatIsNext() {
+ case StringValue:
+ *((*Number)(ptr)) = Number(iter.ReadString())
+ case NilValue:
+ iter.skipFourBytes('n', 'u', 'l', 'l')
+ *((*Number)(ptr)) = ""
+ default:
+ *((*Number)(ptr)) = Number([]byte(iter.readNumberAsString()))
+ }
+}
+
+func (codec *jsoniterNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ number := *((*Number)(ptr))
+ if len(number) == 0 {
+ stream.writeByte('0')
+ } else {
+ stream.WriteRaw(string(number))
+ }
+}
+
+func (codec *jsoniterNumberCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ return len(*((*Number)(ptr))) == 0
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_json_raw_message.go b/vendor/github.com/json-iterator/go/reflect_json_raw_message.go
new file mode 100644
index 00000000..eba434f2
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_json_raw_message.go
@@ -0,0 +1,76 @@
+package jsoniter
+
+import (
+ "encoding/json"
+ "github.com/modern-go/reflect2"
+ "unsafe"
+)
+
+var jsonRawMessageType = reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem()
+var jsoniterRawMessageType = reflect2.TypeOfPtr((*RawMessage)(nil)).Elem()
+
+func createEncoderOfJsonRawMessage(ctx *ctx, typ reflect2.Type) ValEncoder {
+ if typ == jsonRawMessageType {
+ return &jsonRawMessageCodec{}
+ }
+ if typ == jsoniterRawMessageType {
+ return &jsoniterRawMessageCodec{}
+ }
+ return nil
+}
+
+func createDecoderOfJsonRawMessage(ctx *ctx, typ reflect2.Type) ValDecoder {
+ if typ == jsonRawMessageType {
+ return &jsonRawMessageCodec{}
+ }
+ if typ == jsoniterRawMessageType {
+ return &jsoniterRawMessageCodec{}
+ }
+ return nil
+}
+
+type jsonRawMessageCodec struct {
+}
+
+func (codec *jsonRawMessageCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.ReadNil() {
+ *((*json.RawMessage)(ptr)) = nil
+ } else {
+ *((*json.RawMessage)(ptr)) = iter.SkipAndReturnBytes()
+ }
+}
+
+func (codec *jsonRawMessageCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if *((*json.RawMessage)(ptr)) == nil {
+ stream.WriteNil()
+ } else {
+ stream.WriteRaw(string(*((*json.RawMessage)(ptr))))
+ }
+}
+
+func (codec *jsonRawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ return len(*((*json.RawMessage)(ptr))) == 0
+}
+
+type jsoniterRawMessageCodec struct {
+}
+
+func (codec *jsoniterRawMessageCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.ReadNil() {
+ *((*RawMessage)(ptr)) = nil
+ } else {
+ *((*RawMessage)(ptr)) = iter.SkipAndReturnBytes()
+ }
+}
+
+func (codec *jsoniterRawMessageCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if *((*RawMessage)(ptr)) == nil {
+ stream.WriteNil()
+ } else {
+ stream.WriteRaw(string(*((*RawMessage)(ptr))))
+ }
+}
+
+func (codec *jsoniterRawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ return len(*((*RawMessage)(ptr))) == 0
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_map.go b/vendor/github.com/json-iterator/go/reflect_map.go
new file mode 100644
index 00000000..58296713
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_map.go
@@ -0,0 +1,346 @@
+package jsoniter
+
+import (
+ "fmt"
+ "github.com/modern-go/reflect2"
+ "io"
+ "reflect"
+ "sort"
+ "unsafe"
+)
+
+func decoderOfMap(ctx *ctx, typ reflect2.Type) ValDecoder {
+ mapType := typ.(*reflect2.UnsafeMapType)
+ keyDecoder := decoderOfMapKey(ctx.append("[mapKey]"), mapType.Key())
+ elemDecoder := decoderOfType(ctx.append("[mapElem]"), mapType.Elem())
+ return &mapDecoder{
+ mapType: mapType,
+ keyType: mapType.Key(),
+ elemType: mapType.Elem(),
+ keyDecoder: keyDecoder,
+ elemDecoder: elemDecoder,
+ }
+}
+
+func encoderOfMap(ctx *ctx, typ reflect2.Type) ValEncoder {
+ mapType := typ.(*reflect2.UnsafeMapType)
+ if ctx.sortMapKeys {
+ return &sortKeysMapEncoder{
+ mapType: mapType,
+ keyEncoder: encoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()),
+ elemEncoder: encoderOfType(ctx.append("[mapElem]"), mapType.Elem()),
+ }
+ }
+ return &mapEncoder{
+ mapType: mapType,
+ keyEncoder: encoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()),
+ elemEncoder: encoderOfType(ctx.append("[mapElem]"), mapType.Elem()),
+ }
+}
+
+func decoderOfMapKey(ctx *ctx, typ reflect2.Type) ValDecoder {
+ decoder := ctx.decoderExtension.CreateMapKeyDecoder(typ)
+ if decoder != nil {
+ return decoder
+ }
+ for _, extension := range ctx.extraExtensions {
+ decoder := extension.CreateMapKeyDecoder(typ)
+ if decoder != nil {
+ return decoder
+ }
+ }
+
+ ptrType := reflect2.PtrTo(typ)
+ if ptrType.Implements(unmarshalerType) {
+ return &referenceDecoder{
+ &unmarshalerDecoder{
+ valType: ptrType,
+ },
+ }
+ }
+ if typ.Implements(unmarshalerType) {
+ return &unmarshalerDecoder{
+ valType: typ,
+ }
+ }
+ if ptrType.Implements(textUnmarshalerType) {
+ return &referenceDecoder{
+ &textUnmarshalerDecoder{
+ valType: ptrType,
+ },
+ }
+ }
+ if typ.Implements(textUnmarshalerType) {
+ return &textUnmarshalerDecoder{
+ valType: typ,
+ }
+ }
+
+ switch typ.Kind() {
+ case reflect.String:
+ return decoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String))
+ case reflect.Bool,
+ reflect.Uint8, reflect.Int8,
+ reflect.Uint16, reflect.Int16,
+ reflect.Uint32, reflect.Int32,
+ reflect.Uint64, reflect.Int64,
+ reflect.Uint, reflect.Int,
+ reflect.Float32, reflect.Float64,
+ reflect.Uintptr:
+ typ = reflect2.DefaultTypeOfKind(typ.Kind())
+ return &numericMapKeyDecoder{decoderOfType(ctx, typ)}
+ default:
+ return &lazyErrorDecoder{err: fmt.Errorf("unsupported map key type: %v", typ)}
+ }
+}
+
+func encoderOfMapKey(ctx *ctx, typ reflect2.Type) ValEncoder {
+ encoder := ctx.encoderExtension.CreateMapKeyEncoder(typ)
+ if encoder != nil {
+ return encoder
+ }
+ for _, extension := range ctx.extraExtensions {
+ encoder := extension.CreateMapKeyEncoder(typ)
+ if encoder != nil {
+ return encoder
+ }
+ }
+
+ if typ == textMarshalerType {
+ return &directTextMarshalerEncoder{
+ stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
+ }
+ }
+ if typ.Implements(textMarshalerType) {
+ return &textMarshalerEncoder{
+ valType: typ,
+ stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
+ }
+ }
+
+ switch typ.Kind() {
+ case reflect.String:
+ return encoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String))
+ case reflect.Bool,
+ reflect.Uint8, reflect.Int8,
+ reflect.Uint16, reflect.Int16,
+ reflect.Uint32, reflect.Int32,
+ reflect.Uint64, reflect.Int64,
+ reflect.Uint, reflect.Int,
+ reflect.Float32, reflect.Float64,
+ reflect.Uintptr:
+ typ = reflect2.DefaultTypeOfKind(typ.Kind())
+ return &numericMapKeyEncoder{encoderOfType(ctx, typ)}
+ default:
+ if typ.Kind() == reflect.Interface {
+ return &dynamicMapKeyEncoder{ctx, typ}
+ }
+ return &lazyErrorEncoder{err: fmt.Errorf("unsupported map key type: %v", typ)}
+ }
+}
+
+type mapDecoder struct {
+ mapType *reflect2.UnsafeMapType
+ keyType reflect2.Type
+ elemType reflect2.Type
+ keyDecoder ValDecoder
+ elemDecoder ValDecoder
+}
+
+func (decoder *mapDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ mapType := decoder.mapType
+ c := iter.nextToken()
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ *(*unsafe.Pointer)(ptr) = nil
+ mapType.UnsafeSet(ptr, mapType.UnsafeNew())
+ return
+ }
+ if mapType.UnsafeIsNil(ptr) {
+ mapType.UnsafeSet(ptr, mapType.UnsafeMakeMap(0))
+ }
+ if c != '{' {
+ iter.ReportError("ReadMapCB", `expect { or n, but found `+string([]byte{c}))
+ return
+ }
+ c = iter.nextToken()
+ if c == '}' {
+ return
+ }
+ iter.unreadByte()
+ key := decoder.keyType.UnsafeNew()
+ decoder.keyDecoder.Decode(key, iter)
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
+ return
+ }
+ elem := decoder.elemType.UnsafeNew()
+ decoder.elemDecoder.Decode(elem, iter)
+ decoder.mapType.UnsafeSetIndex(ptr, key, elem)
+ for c = iter.nextToken(); c == ','; c = iter.nextToken() {
+ key := decoder.keyType.UnsafeNew()
+ decoder.keyDecoder.Decode(key, iter)
+ c = iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
+ return
+ }
+ elem := decoder.elemType.UnsafeNew()
+ decoder.elemDecoder.Decode(elem, iter)
+ decoder.mapType.UnsafeSetIndex(ptr, key, elem)
+ }
+ if c != '}' {
+ iter.ReportError("ReadMapCB", `expect }, but found `+string([]byte{c}))
+ }
+}
+
+type numericMapKeyDecoder struct {
+ decoder ValDecoder
+}
+
+func (decoder *numericMapKeyDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ c := iter.nextToken()
+ if c != '"' {
+ iter.ReportError("ReadMapCB", `expect ", but found `+string([]byte{c}))
+ return
+ }
+ decoder.decoder.Decode(ptr, iter)
+ c = iter.nextToken()
+ if c != '"' {
+ iter.ReportError("ReadMapCB", `expect ", but found `+string([]byte{c}))
+ return
+ }
+}
+
+type numericMapKeyEncoder struct {
+ encoder ValEncoder
+}
+
+func (encoder *numericMapKeyEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.writeByte('"')
+ encoder.encoder.Encode(ptr, stream)
+ stream.writeByte('"')
+}
+
+func (encoder *numericMapKeyEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return false
+}
+
+type dynamicMapKeyEncoder struct {
+ ctx *ctx
+ valType reflect2.Type
+}
+
+func (encoder *dynamicMapKeyEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ obj := encoder.valType.UnsafeIndirect(ptr)
+ encoderOfMapKey(encoder.ctx, reflect2.TypeOf(obj)).Encode(reflect2.PtrOf(obj), stream)
+}
+
+func (encoder *dynamicMapKeyEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ obj := encoder.valType.UnsafeIndirect(ptr)
+ return encoderOfMapKey(encoder.ctx, reflect2.TypeOf(obj)).IsEmpty(reflect2.PtrOf(obj))
+}
+
+type mapEncoder struct {
+ mapType *reflect2.UnsafeMapType
+ keyEncoder ValEncoder
+ elemEncoder ValEncoder
+}
+
+func (encoder *mapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if *(*unsafe.Pointer)(ptr) == nil {
+ stream.WriteNil()
+ return
+ }
+ stream.WriteObjectStart()
+ iter := encoder.mapType.UnsafeIterate(ptr)
+ for i := 0; iter.HasNext(); i++ {
+ if i != 0 {
+ stream.WriteMore()
+ }
+ key, elem := iter.UnsafeNext()
+ encoder.keyEncoder.Encode(key, stream)
+ if stream.indention > 0 {
+ stream.writeTwoBytes(byte(':'), byte(' '))
+ } else {
+ stream.writeByte(':')
+ }
+ encoder.elemEncoder.Encode(elem, stream)
+ }
+ stream.WriteObjectEnd()
+}
+
+func (encoder *mapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ iter := encoder.mapType.UnsafeIterate(ptr)
+ return !iter.HasNext()
+}
+
+type sortKeysMapEncoder struct {
+ mapType *reflect2.UnsafeMapType
+ keyEncoder ValEncoder
+ elemEncoder ValEncoder
+}
+
+func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if *(*unsafe.Pointer)(ptr) == nil {
+ stream.WriteNil()
+ return
+ }
+ stream.WriteObjectStart()
+ mapIter := encoder.mapType.UnsafeIterate(ptr)
+ subStream := stream.cfg.BorrowStream(nil)
+ subStream.Attachment = stream.Attachment
+ subIter := stream.cfg.BorrowIterator(nil)
+ keyValues := encodedKeyValues{}
+ for mapIter.HasNext() {
+ key, elem := mapIter.UnsafeNext()
+ subStreamIndex := subStream.Buffered()
+ encoder.keyEncoder.Encode(key, subStream)
+ if subStream.Error != nil && subStream.Error != io.EOF && stream.Error == nil {
+ stream.Error = subStream.Error
+ }
+ encodedKey := subStream.Buffer()[subStreamIndex:]
+ subIter.ResetBytes(encodedKey)
+ decodedKey := subIter.ReadString()
+ if stream.indention > 0 {
+ subStream.writeTwoBytes(byte(':'), byte(' '))
+ } else {
+ subStream.writeByte(':')
+ }
+ encoder.elemEncoder.Encode(elem, subStream)
+ keyValues = append(keyValues, encodedKV{
+ key: decodedKey,
+ keyValue: subStream.Buffer()[subStreamIndex:],
+ })
+ }
+ sort.Sort(keyValues)
+ for i, keyValue := range keyValues {
+ if i != 0 {
+ stream.WriteMore()
+ }
+ stream.Write(keyValue.keyValue)
+ }
+ if subStream.Error != nil && stream.Error == nil {
+ stream.Error = subStream.Error
+ }
+ stream.WriteObjectEnd()
+ stream.cfg.ReturnStream(subStream)
+ stream.cfg.ReturnIterator(subIter)
+}
+
+func (encoder *sortKeysMapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ iter := encoder.mapType.UnsafeIterate(ptr)
+ return !iter.HasNext()
+}
+
+type encodedKeyValues []encodedKV
+
+type encodedKV struct {
+ key string
+ keyValue []byte
+}
+
+func (sv encodedKeyValues) Len() int { return len(sv) }
+func (sv encodedKeyValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
+func (sv encodedKeyValues) Less(i, j int) bool { return sv[i].key < sv[j].key }
diff --git a/vendor/github.com/json-iterator/go/reflect_marshaler.go b/vendor/github.com/json-iterator/go/reflect_marshaler.go
new file mode 100644
index 00000000..3e21f375
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_marshaler.go
@@ -0,0 +1,225 @@
+package jsoniter
+
+import (
+ "encoding"
+ "encoding/json"
+ "unsafe"
+
+ "github.com/modern-go/reflect2"
+)
+
+var marshalerType = reflect2.TypeOfPtr((*json.Marshaler)(nil)).Elem()
+var unmarshalerType = reflect2.TypeOfPtr((*json.Unmarshaler)(nil)).Elem()
+var textMarshalerType = reflect2.TypeOfPtr((*encoding.TextMarshaler)(nil)).Elem()
+var textUnmarshalerType = reflect2.TypeOfPtr((*encoding.TextUnmarshaler)(nil)).Elem()
+
+func createDecoderOfMarshaler(ctx *ctx, typ reflect2.Type) ValDecoder {
+ ptrType := reflect2.PtrTo(typ)
+ if ptrType.Implements(unmarshalerType) {
+ return &referenceDecoder{
+ &unmarshalerDecoder{ptrType},
+ }
+ }
+ if ptrType.Implements(textUnmarshalerType) {
+ return &referenceDecoder{
+ &textUnmarshalerDecoder{ptrType},
+ }
+ }
+ return nil
+}
+
+func createEncoderOfMarshaler(ctx *ctx, typ reflect2.Type) ValEncoder {
+ if typ == marshalerType {
+ checkIsEmpty := createCheckIsEmpty(ctx, typ)
+ var encoder ValEncoder = &directMarshalerEncoder{
+ checkIsEmpty: checkIsEmpty,
+ }
+ return encoder
+ }
+ if typ.Implements(marshalerType) {
+ checkIsEmpty := createCheckIsEmpty(ctx, typ)
+ var encoder ValEncoder = &marshalerEncoder{
+ valType: typ,
+ checkIsEmpty: checkIsEmpty,
+ }
+ return encoder
+ }
+ ptrType := reflect2.PtrTo(typ)
+ if ctx.prefix != "" && ptrType.Implements(marshalerType) {
+ checkIsEmpty := createCheckIsEmpty(ctx, ptrType)
+ var encoder ValEncoder = &marshalerEncoder{
+ valType: ptrType,
+ checkIsEmpty: checkIsEmpty,
+ }
+ return &referenceEncoder{encoder}
+ }
+ if typ == textMarshalerType {
+ checkIsEmpty := createCheckIsEmpty(ctx, typ)
+ var encoder ValEncoder = &directTextMarshalerEncoder{
+ checkIsEmpty: checkIsEmpty,
+ stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
+ }
+ return encoder
+ }
+ if typ.Implements(textMarshalerType) {
+ checkIsEmpty := createCheckIsEmpty(ctx, typ)
+ var encoder ValEncoder = &textMarshalerEncoder{
+ valType: typ,
+ stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
+ checkIsEmpty: checkIsEmpty,
+ }
+ return encoder
+ }
+ // if prefix is empty, the type is the root type
+ if ctx.prefix != "" && ptrType.Implements(textMarshalerType) {
+ checkIsEmpty := createCheckIsEmpty(ctx, ptrType)
+ var encoder ValEncoder = &textMarshalerEncoder{
+ valType: ptrType,
+ stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
+ checkIsEmpty: checkIsEmpty,
+ }
+ return &referenceEncoder{encoder}
+ }
+ return nil
+}
+
+type marshalerEncoder struct {
+ checkIsEmpty checkIsEmpty
+ valType reflect2.Type
+}
+
+func (encoder *marshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ obj := encoder.valType.UnsafeIndirect(ptr)
+ if encoder.valType.IsNullable() && reflect2.IsNil(obj) {
+ stream.WriteNil()
+ return
+ }
+ marshaler := obj.(json.Marshaler)
+ bytes, err := marshaler.MarshalJSON()
+ if err != nil {
+ stream.Error = err
+ } else {
+ // html escape was already done by jsoniter
+ // but the extra '\n' should be trimed
+ l := len(bytes)
+ if l > 0 && bytes[l-1] == '\n' {
+ bytes = bytes[:l-1]
+ }
+ stream.Write(bytes)
+ }
+}
+
+func (encoder *marshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.checkIsEmpty.IsEmpty(ptr)
+}
+
+type directMarshalerEncoder struct {
+ checkIsEmpty checkIsEmpty
+}
+
+func (encoder *directMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ marshaler := *(*json.Marshaler)(ptr)
+ if marshaler == nil {
+ stream.WriteNil()
+ return
+ }
+ bytes, err := marshaler.MarshalJSON()
+ if err != nil {
+ stream.Error = err
+ } else {
+ stream.Write(bytes)
+ }
+}
+
+func (encoder *directMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.checkIsEmpty.IsEmpty(ptr)
+}
+
+type textMarshalerEncoder struct {
+ valType reflect2.Type
+ stringEncoder ValEncoder
+ checkIsEmpty checkIsEmpty
+}
+
+func (encoder *textMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ obj := encoder.valType.UnsafeIndirect(ptr)
+ if encoder.valType.IsNullable() && reflect2.IsNil(obj) {
+ stream.WriteNil()
+ return
+ }
+ marshaler := (obj).(encoding.TextMarshaler)
+ bytes, err := marshaler.MarshalText()
+ if err != nil {
+ stream.Error = err
+ } else {
+ str := string(bytes)
+ encoder.stringEncoder.Encode(unsafe.Pointer(&str), stream)
+ }
+}
+
+func (encoder *textMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.checkIsEmpty.IsEmpty(ptr)
+}
+
+type directTextMarshalerEncoder struct {
+ stringEncoder ValEncoder
+ checkIsEmpty checkIsEmpty
+}
+
+func (encoder *directTextMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ marshaler := *(*encoding.TextMarshaler)(ptr)
+ if marshaler == nil {
+ stream.WriteNil()
+ return
+ }
+ bytes, err := marshaler.MarshalText()
+ if err != nil {
+ stream.Error = err
+ } else {
+ str := string(bytes)
+ encoder.stringEncoder.Encode(unsafe.Pointer(&str), stream)
+ }
+}
+
+func (encoder *directTextMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.checkIsEmpty.IsEmpty(ptr)
+}
+
+type unmarshalerDecoder struct {
+ valType reflect2.Type
+}
+
+func (decoder *unmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ valType := decoder.valType
+ obj := valType.UnsafeIndirect(ptr)
+ unmarshaler := obj.(json.Unmarshaler)
+ iter.nextToken()
+ iter.unreadByte() // skip spaces
+ bytes := iter.SkipAndReturnBytes()
+ err := unmarshaler.UnmarshalJSON(bytes)
+ if err != nil {
+ iter.ReportError("unmarshalerDecoder", err.Error())
+ }
+}
+
+type textUnmarshalerDecoder struct {
+ valType reflect2.Type
+}
+
+func (decoder *textUnmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ valType := decoder.valType
+ obj := valType.UnsafeIndirect(ptr)
+ if reflect2.IsNil(obj) {
+ ptrType := valType.(*reflect2.UnsafePtrType)
+ elemType := ptrType.Elem()
+ elem := elemType.UnsafeNew()
+ ptrType.UnsafeSet(ptr, unsafe.Pointer(&elem))
+ obj = valType.UnsafeIndirect(ptr)
+ }
+ unmarshaler := (obj).(encoding.TextUnmarshaler)
+ str := iter.ReadString()
+ err := unmarshaler.UnmarshalText([]byte(str))
+ if err != nil {
+ iter.ReportError("textUnmarshalerDecoder", err.Error())
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_native.go b/vendor/github.com/json-iterator/go/reflect_native.go
new file mode 100644
index 00000000..f88722d1
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_native.go
@@ -0,0 +1,453 @@
+package jsoniter
+
+import (
+ "encoding/base64"
+ "reflect"
+ "strconv"
+ "unsafe"
+
+ "github.com/modern-go/reflect2"
+)
+
+const ptrSize = 32 << uintptr(^uintptr(0)>>63)
+
+func createEncoderOfNative(ctx *ctx, typ reflect2.Type) ValEncoder {
+ if typ.Kind() == reflect.Slice && typ.(reflect2.SliceType).Elem().Kind() == reflect.Uint8 {
+ sliceDecoder := decoderOfSlice(ctx, typ)
+ return &base64Codec{sliceDecoder: sliceDecoder}
+ }
+ typeName := typ.String()
+ kind := typ.Kind()
+ switch kind {
+ case reflect.String:
+ if typeName != "string" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*string)(nil)).Elem())
+ }
+ return &stringCodec{}
+ case reflect.Int:
+ if typeName != "int" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*int)(nil)).Elem())
+ }
+ if strconv.IntSize == 32 {
+ return &int32Codec{}
+ }
+ return &int64Codec{}
+ case reflect.Int8:
+ if typeName != "int8" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*int8)(nil)).Elem())
+ }
+ return &int8Codec{}
+ case reflect.Int16:
+ if typeName != "int16" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*int16)(nil)).Elem())
+ }
+ return &int16Codec{}
+ case reflect.Int32:
+ if typeName != "int32" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*int32)(nil)).Elem())
+ }
+ return &int32Codec{}
+ case reflect.Int64:
+ if typeName != "int64" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*int64)(nil)).Elem())
+ }
+ return &int64Codec{}
+ case reflect.Uint:
+ if typeName != "uint" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*uint)(nil)).Elem())
+ }
+ if strconv.IntSize == 32 {
+ return &uint32Codec{}
+ }
+ return &uint64Codec{}
+ case reflect.Uint8:
+ if typeName != "uint8" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*uint8)(nil)).Elem())
+ }
+ return &uint8Codec{}
+ case reflect.Uint16:
+ if typeName != "uint16" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*uint16)(nil)).Elem())
+ }
+ return &uint16Codec{}
+ case reflect.Uint32:
+ if typeName != "uint32" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*uint32)(nil)).Elem())
+ }
+ return &uint32Codec{}
+ case reflect.Uintptr:
+ if typeName != "uintptr" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*uintptr)(nil)).Elem())
+ }
+ if ptrSize == 32 {
+ return &uint32Codec{}
+ }
+ return &uint64Codec{}
+ case reflect.Uint64:
+ if typeName != "uint64" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*uint64)(nil)).Elem())
+ }
+ return &uint64Codec{}
+ case reflect.Float32:
+ if typeName != "float32" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*float32)(nil)).Elem())
+ }
+ return &float32Codec{}
+ case reflect.Float64:
+ if typeName != "float64" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*float64)(nil)).Elem())
+ }
+ return &float64Codec{}
+ case reflect.Bool:
+ if typeName != "bool" {
+ return encoderOfType(ctx, reflect2.TypeOfPtr((*bool)(nil)).Elem())
+ }
+ return &boolCodec{}
+ }
+ return nil
+}
+
+func createDecoderOfNative(ctx *ctx, typ reflect2.Type) ValDecoder {
+ if typ.Kind() == reflect.Slice && typ.(reflect2.SliceType).Elem().Kind() == reflect.Uint8 {
+ sliceDecoder := decoderOfSlice(ctx, typ)
+ return &base64Codec{sliceDecoder: sliceDecoder}
+ }
+ typeName := typ.String()
+ switch typ.Kind() {
+ case reflect.String:
+ if typeName != "string" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*string)(nil)).Elem())
+ }
+ return &stringCodec{}
+ case reflect.Int:
+ if typeName != "int" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*int)(nil)).Elem())
+ }
+ if strconv.IntSize == 32 {
+ return &int32Codec{}
+ }
+ return &int64Codec{}
+ case reflect.Int8:
+ if typeName != "int8" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*int8)(nil)).Elem())
+ }
+ return &int8Codec{}
+ case reflect.Int16:
+ if typeName != "int16" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*int16)(nil)).Elem())
+ }
+ return &int16Codec{}
+ case reflect.Int32:
+ if typeName != "int32" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*int32)(nil)).Elem())
+ }
+ return &int32Codec{}
+ case reflect.Int64:
+ if typeName != "int64" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*int64)(nil)).Elem())
+ }
+ return &int64Codec{}
+ case reflect.Uint:
+ if typeName != "uint" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*uint)(nil)).Elem())
+ }
+ if strconv.IntSize == 32 {
+ return &uint32Codec{}
+ }
+ return &uint64Codec{}
+ case reflect.Uint8:
+ if typeName != "uint8" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*uint8)(nil)).Elem())
+ }
+ return &uint8Codec{}
+ case reflect.Uint16:
+ if typeName != "uint16" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*uint16)(nil)).Elem())
+ }
+ return &uint16Codec{}
+ case reflect.Uint32:
+ if typeName != "uint32" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*uint32)(nil)).Elem())
+ }
+ return &uint32Codec{}
+ case reflect.Uintptr:
+ if typeName != "uintptr" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*uintptr)(nil)).Elem())
+ }
+ if ptrSize == 32 {
+ return &uint32Codec{}
+ }
+ return &uint64Codec{}
+ case reflect.Uint64:
+ if typeName != "uint64" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*uint64)(nil)).Elem())
+ }
+ return &uint64Codec{}
+ case reflect.Float32:
+ if typeName != "float32" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*float32)(nil)).Elem())
+ }
+ return &float32Codec{}
+ case reflect.Float64:
+ if typeName != "float64" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*float64)(nil)).Elem())
+ }
+ return &float64Codec{}
+ case reflect.Bool:
+ if typeName != "bool" {
+ return decoderOfType(ctx, reflect2.TypeOfPtr((*bool)(nil)).Elem())
+ }
+ return &boolCodec{}
+ }
+ return nil
+}
+
+type stringCodec struct {
+}
+
+func (codec *stringCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ *((*string)(ptr)) = iter.ReadString()
+}
+
+func (codec *stringCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ str := *((*string)(ptr))
+ stream.WriteString(str)
+}
+
+func (codec *stringCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*string)(ptr)) == ""
+}
+
+type int8Codec struct {
+}
+
+func (codec *int8Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*int8)(ptr)) = iter.ReadInt8()
+ }
+}
+
+func (codec *int8Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteInt8(*((*int8)(ptr)))
+}
+
+func (codec *int8Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*int8)(ptr)) == 0
+}
+
+type int16Codec struct {
+}
+
+func (codec *int16Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*int16)(ptr)) = iter.ReadInt16()
+ }
+}
+
+func (codec *int16Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteInt16(*((*int16)(ptr)))
+}
+
+func (codec *int16Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*int16)(ptr)) == 0
+}
+
+type int32Codec struct {
+}
+
+func (codec *int32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*int32)(ptr)) = iter.ReadInt32()
+ }
+}
+
+func (codec *int32Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteInt32(*((*int32)(ptr)))
+}
+
+func (codec *int32Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*int32)(ptr)) == 0
+}
+
+type int64Codec struct {
+}
+
+func (codec *int64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*int64)(ptr)) = iter.ReadInt64()
+ }
+}
+
+func (codec *int64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteInt64(*((*int64)(ptr)))
+}
+
+func (codec *int64Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*int64)(ptr)) == 0
+}
+
+type uint8Codec struct {
+}
+
+func (codec *uint8Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*uint8)(ptr)) = iter.ReadUint8()
+ }
+}
+
+func (codec *uint8Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteUint8(*((*uint8)(ptr)))
+}
+
+func (codec *uint8Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*uint8)(ptr)) == 0
+}
+
+type uint16Codec struct {
+}
+
+func (codec *uint16Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*uint16)(ptr)) = iter.ReadUint16()
+ }
+}
+
+func (codec *uint16Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteUint16(*((*uint16)(ptr)))
+}
+
+func (codec *uint16Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*uint16)(ptr)) == 0
+}
+
+type uint32Codec struct {
+}
+
+func (codec *uint32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*uint32)(ptr)) = iter.ReadUint32()
+ }
+}
+
+func (codec *uint32Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteUint32(*((*uint32)(ptr)))
+}
+
+func (codec *uint32Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*uint32)(ptr)) == 0
+}
+
+type uint64Codec struct {
+}
+
+func (codec *uint64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*uint64)(ptr)) = iter.ReadUint64()
+ }
+}
+
+func (codec *uint64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteUint64(*((*uint64)(ptr)))
+}
+
+func (codec *uint64Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*uint64)(ptr)) == 0
+}
+
+type float32Codec struct {
+}
+
+func (codec *float32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*float32)(ptr)) = iter.ReadFloat32()
+ }
+}
+
+func (codec *float32Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteFloat32(*((*float32)(ptr)))
+}
+
+func (codec *float32Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*float32)(ptr)) == 0
+}
+
+type float64Codec struct {
+}
+
+func (codec *float64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*float64)(ptr)) = iter.ReadFloat64()
+ }
+}
+
+func (codec *float64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteFloat64(*((*float64)(ptr)))
+}
+
+func (codec *float64Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*float64)(ptr)) == 0
+}
+
+type boolCodec struct {
+}
+
+func (codec *boolCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.ReadNil() {
+ *((*bool)(ptr)) = iter.ReadBool()
+ }
+}
+
+func (codec *boolCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteBool(*((*bool)(ptr)))
+}
+
+func (codec *boolCodec) IsEmpty(ptr unsafe.Pointer) bool {
+ return !(*((*bool)(ptr)))
+}
+
+type base64Codec struct {
+ sliceType *reflect2.UnsafeSliceType
+ sliceDecoder ValDecoder
+}
+
+func (codec *base64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.ReadNil() {
+ codec.sliceType.UnsafeSetNil(ptr)
+ return
+ }
+ switch iter.WhatIsNext() {
+ case StringValue:
+ src := iter.ReadString()
+ dst, err := base64.StdEncoding.DecodeString(src)
+ if err != nil {
+ iter.ReportError("decode base64", err.Error())
+ } else {
+ codec.sliceType.UnsafeSet(ptr, unsafe.Pointer(&dst))
+ }
+ case ArrayValue:
+ codec.sliceDecoder.Decode(ptr, iter)
+ default:
+ iter.ReportError("base64Codec", "invalid input")
+ }
+}
+
+func (codec *base64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if codec.sliceType.UnsafeIsNil(ptr) {
+ stream.WriteNil()
+ return
+ }
+ src := *((*[]byte)(ptr))
+ encoding := base64.StdEncoding
+ stream.writeByte('"')
+ if len(src) != 0 {
+ size := encoding.EncodedLen(len(src))
+ buf := make([]byte, size)
+ encoding.Encode(buf, src)
+ stream.buf = append(stream.buf, buf...)
+ }
+ stream.writeByte('"')
+}
+
+func (codec *base64Codec) IsEmpty(ptr unsafe.Pointer) bool {
+ return len(*((*[]byte)(ptr))) == 0
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_optional.go b/vendor/github.com/json-iterator/go/reflect_optional.go
new file mode 100644
index 00000000..fa71f474
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_optional.go
@@ -0,0 +1,129 @@
+package jsoniter
+
+import (
+ "github.com/modern-go/reflect2"
+ "unsafe"
+)
+
+func decoderOfOptional(ctx *ctx, typ reflect2.Type) ValDecoder {
+ ptrType := typ.(*reflect2.UnsafePtrType)
+ elemType := ptrType.Elem()
+ decoder := decoderOfType(ctx, elemType)
+ return &OptionalDecoder{elemType, decoder}
+}
+
+func encoderOfOptional(ctx *ctx, typ reflect2.Type) ValEncoder {
+ ptrType := typ.(*reflect2.UnsafePtrType)
+ elemType := ptrType.Elem()
+ elemEncoder := encoderOfType(ctx, elemType)
+ encoder := &OptionalEncoder{elemEncoder}
+ return encoder
+}
+
+type OptionalDecoder struct {
+ ValueType reflect2.Type
+ ValueDecoder ValDecoder
+}
+
+func (decoder *OptionalDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.ReadNil() {
+ *((*unsafe.Pointer)(ptr)) = nil
+ } else {
+ if *((*unsafe.Pointer)(ptr)) == nil {
+ //pointer to null, we have to allocate memory to hold the value
+ newPtr := decoder.ValueType.UnsafeNew()
+ decoder.ValueDecoder.Decode(newPtr, iter)
+ *((*unsafe.Pointer)(ptr)) = newPtr
+ } else {
+ //reuse existing instance
+ decoder.ValueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter)
+ }
+ }
+}
+
+type dereferenceDecoder struct {
+ // only to deference a pointer
+ valueType reflect2.Type
+ valueDecoder ValDecoder
+}
+
+func (decoder *dereferenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if *((*unsafe.Pointer)(ptr)) == nil {
+ //pointer to null, we have to allocate memory to hold the value
+ newPtr := decoder.valueType.UnsafeNew()
+ decoder.valueDecoder.Decode(newPtr, iter)
+ *((*unsafe.Pointer)(ptr)) = newPtr
+ } else {
+ //reuse existing instance
+ decoder.valueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter)
+ }
+}
+
+type OptionalEncoder struct {
+ ValueEncoder ValEncoder
+}
+
+func (encoder *OptionalEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if *((*unsafe.Pointer)(ptr)) == nil {
+ stream.WriteNil()
+ } else {
+ encoder.ValueEncoder.Encode(*((*unsafe.Pointer)(ptr)), stream)
+ }
+}
+
+func (encoder *OptionalEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return *((*unsafe.Pointer)(ptr)) == nil
+}
+
+type dereferenceEncoder struct {
+ ValueEncoder ValEncoder
+}
+
+func (encoder *dereferenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if *((*unsafe.Pointer)(ptr)) == nil {
+ stream.WriteNil()
+ } else {
+ encoder.ValueEncoder.Encode(*((*unsafe.Pointer)(ptr)), stream)
+ }
+}
+
+func (encoder *dereferenceEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ dePtr := *((*unsafe.Pointer)(ptr))
+ if dePtr == nil {
+ return true
+ }
+ return encoder.ValueEncoder.IsEmpty(dePtr)
+}
+
+func (encoder *dereferenceEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool {
+ deReferenced := *((*unsafe.Pointer)(ptr))
+ if deReferenced == nil {
+ return true
+ }
+ isEmbeddedPtrNil, converted := encoder.ValueEncoder.(IsEmbeddedPtrNil)
+ if !converted {
+ return false
+ }
+ fieldPtr := unsafe.Pointer(deReferenced)
+ return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr)
+}
+
+type referenceEncoder struct {
+ encoder ValEncoder
+}
+
+func (encoder *referenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ encoder.encoder.Encode(unsafe.Pointer(&ptr), stream)
+}
+
+func (encoder *referenceEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.encoder.IsEmpty(unsafe.Pointer(&ptr))
+}
+
+type referenceDecoder struct {
+ decoder ValDecoder
+}
+
+func (decoder *referenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ decoder.decoder.Decode(unsafe.Pointer(&ptr), iter)
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_slice.go b/vendor/github.com/json-iterator/go/reflect_slice.go
new file mode 100644
index 00000000..9441d79d
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_slice.go
@@ -0,0 +1,99 @@
+package jsoniter
+
+import (
+ "fmt"
+ "github.com/modern-go/reflect2"
+ "io"
+ "unsafe"
+)
+
+func decoderOfSlice(ctx *ctx, typ reflect2.Type) ValDecoder {
+ sliceType := typ.(*reflect2.UnsafeSliceType)
+ decoder := decoderOfType(ctx.append("[sliceElem]"), sliceType.Elem())
+ return &sliceDecoder{sliceType, decoder}
+}
+
+func encoderOfSlice(ctx *ctx, typ reflect2.Type) ValEncoder {
+ sliceType := typ.(*reflect2.UnsafeSliceType)
+ encoder := encoderOfType(ctx.append("[sliceElem]"), sliceType.Elem())
+ return &sliceEncoder{sliceType, encoder}
+}
+
+type sliceEncoder struct {
+ sliceType *reflect2.UnsafeSliceType
+ elemEncoder ValEncoder
+}
+
+func (encoder *sliceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ if encoder.sliceType.UnsafeIsNil(ptr) {
+ stream.WriteNil()
+ return
+ }
+ length := encoder.sliceType.UnsafeLengthOf(ptr)
+ if length == 0 {
+ stream.WriteEmptyArray()
+ return
+ }
+ stream.WriteArrayStart()
+ encoder.elemEncoder.Encode(encoder.sliceType.UnsafeGetIndex(ptr, 0), stream)
+ for i := 1; i < length; i++ {
+ stream.WriteMore()
+ elemPtr := encoder.sliceType.UnsafeGetIndex(ptr, i)
+ encoder.elemEncoder.Encode(elemPtr, stream)
+ }
+ stream.WriteArrayEnd()
+ if stream.Error != nil && stream.Error != io.EOF {
+ stream.Error = fmt.Errorf("%v: %s", encoder.sliceType, stream.Error.Error())
+ }
+}
+
+func (encoder *sliceEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.sliceType.UnsafeLengthOf(ptr) == 0
+}
+
+type sliceDecoder struct {
+ sliceType *reflect2.UnsafeSliceType
+ elemDecoder ValDecoder
+}
+
+func (decoder *sliceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ decoder.doDecode(ptr, iter)
+ if iter.Error != nil && iter.Error != io.EOF {
+ iter.Error = fmt.Errorf("%v: %s", decoder.sliceType, iter.Error.Error())
+ }
+}
+
+func (decoder *sliceDecoder) doDecode(ptr unsafe.Pointer, iter *Iterator) {
+ c := iter.nextToken()
+ sliceType := decoder.sliceType
+ if c == 'n' {
+ iter.skipThreeBytes('u', 'l', 'l')
+ sliceType.UnsafeSetNil(ptr)
+ return
+ }
+ if c != '[' {
+ iter.ReportError("decode slice", "expect [ or n, but found "+string([]byte{c}))
+ return
+ }
+ c = iter.nextToken()
+ if c == ']' {
+ sliceType.UnsafeSet(ptr, sliceType.UnsafeMakeSlice(0, 0))
+ return
+ }
+ iter.unreadByte()
+ sliceType.UnsafeGrow(ptr, 1)
+ elemPtr := sliceType.UnsafeGetIndex(ptr, 0)
+ decoder.elemDecoder.Decode(elemPtr, iter)
+ length := 1
+ for c = iter.nextToken(); c == ','; c = iter.nextToken() {
+ idx := length
+ length += 1
+ sliceType.UnsafeGrow(ptr, length)
+ elemPtr = sliceType.UnsafeGetIndex(ptr, idx)
+ decoder.elemDecoder.Decode(elemPtr, iter)
+ }
+ if c != ']' {
+ iter.ReportError("decode slice", "expect ], but found "+string([]byte{c}))
+ return
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_struct_decoder.go b/vendor/github.com/json-iterator/go/reflect_struct_decoder.go
new file mode 100644
index 00000000..92ae912d
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_struct_decoder.go
@@ -0,0 +1,1097 @@
+package jsoniter
+
+import (
+ "fmt"
+ "io"
+ "strings"
+ "unsafe"
+
+ "github.com/modern-go/reflect2"
+)
+
+func decoderOfStruct(ctx *ctx, typ reflect2.Type) ValDecoder {
+ bindings := map[string]*Binding{}
+ structDescriptor := describeStruct(ctx, typ)
+ for _, binding := range structDescriptor.Fields {
+ for _, fromName := range binding.FromNames {
+ old := bindings[fromName]
+ if old == nil {
+ bindings[fromName] = binding
+ continue
+ }
+ ignoreOld, ignoreNew := resolveConflictBinding(ctx.frozenConfig, old, binding)
+ if ignoreOld {
+ delete(bindings, fromName)
+ }
+ if !ignoreNew {
+ bindings[fromName] = binding
+ }
+ }
+ }
+ fields := map[string]*structFieldDecoder{}
+ for k, binding := range bindings {
+ fields[k] = binding.Decoder.(*structFieldDecoder)
+ }
+
+ if !ctx.caseSensitive() {
+ for k, binding := range bindings {
+ if _, found := fields[strings.ToLower(k)]; !found {
+ fields[strings.ToLower(k)] = binding.Decoder.(*structFieldDecoder)
+ }
+ }
+ }
+
+ return createStructDecoder(ctx, typ, fields)
+}
+
+func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structFieldDecoder) ValDecoder {
+ if ctx.disallowUnknownFields {
+ return &generalStructDecoder{typ: typ, fields: fields, disallowUnknownFields: true}
+ }
+ knownHash := map[int64]struct{}{
+ 0: {},
+ }
+
+ switch len(fields) {
+ case 0:
+ return &skipObjectDecoder{typ}
+ case 1:
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ return &oneFieldStructDecoder{typ, fieldHash, fieldDecoder}
+ }
+ case 2:
+ var fieldHash1 int64
+ var fieldHash2 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldHash1 == 0 {
+ fieldHash1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else {
+ fieldHash2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ }
+ }
+ return &twoFieldsStructDecoder{typ, fieldHash1, fieldDecoder1, fieldHash2, fieldDecoder2}
+ case 3:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ }
+ }
+ return &threeFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3}
+ case 4:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ }
+ }
+ return &fourFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4}
+ case 5:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldName5 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ var fieldDecoder5 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else if fieldName4 == 0 {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ } else {
+ fieldName5 = fieldHash
+ fieldDecoder5 = fieldDecoder
+ }
+ }
+ return &fiveFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4,
+ fieldName5, fieldDecoder5}
+ case 6:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldName5 int64
+ var fieldName6 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ var fieldDecoder5 *structFieldDecoder
+ var fieldDecoder6 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else if fieldName4 == 0 {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ } else if fieldName5 == 0 {
+ fieldName5 = fieldHash
+ fieldDecoder5 = fieldDecoder
+ } else {
+ fieldName6 = fieldHash
+ fieldDecoder6 = fieldDecoder
+ }
+ }
+ return &sixFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4,
+ fieldName5, fieldDecoder5,
+ fieldName6, fieldDecoder6}
+ case 7:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldName5 int64
+ var fieldName6 int64
+ var fieldName7 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ var fieldDecoder5 *structFieldDecoder
+ var fieldDecoder6 *structFieldDecoder
+ var fieldDecoder7 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else if fieldName4 == 0 {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ } else if fieldName5 == 0 {
+ fieldName5 = fieldHash
+ fieldDecoder5 = fieldDecoder
+ } else if fieldName6 == 0 {
+ fieldName6 = fieldHash
+ fieldDecoder6 = fieldDecoder
+ } else {
+ fieldName7 = fieldHash
+ fieldDecoder7 = fieldDecoder
+ }
+ }
+ return &sevenFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4,
+ fieldName5, fieldDecoder5,
+ fieldName6, fieldDecoder6,
+ fieldName7, fieldDecoder7}
+ case 8:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldName5 int64
+ var fieldName6 int64
+ var fieldName7 int64
+ var fieldName8 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ var fieldDecoder5 *structFieldDecoder
+ var fieldDecoder6 *structFieldDecoder
+ var fieldDecoder7 *structFieldDecoder
+ var fieldDecoder8 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else if fieldName4 == 0 {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ } else if fieldName5 == 0 {
+ fieldName5 = fieldHash
+ fieldDecoder5 = fieldDecoder
+ } else if fieldName6 == 0 {
+ fieldName6 = fieldHash
+ fieldDecoder6 = fieldDecoder
+ } else if fieldName7 == 0 {
+ fieldName7 = fieldHash
+ fieldDecoder7 = fieldDecoder
+ } else {
+ fieldName8 = fieldHash
+ fieldDecoder8 = fieldDecoder
+ }
+ }
+ return &eightFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4,
+ fieldName5, fieldDecoder5,
+ fieldName6, fieldDecoder6,
+ fieldName7, fieldDecoder7,
+ fieldName8, fieldDecoder8}
+ case 9:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldName5 int64
+ var fieldName6 int64
+ var fieldName7 int64
+ var fieldName8 int64
+ var fieldName9 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ var fieldDecoder5 *structFieldDecoder
+ var fieldDecoder6 *structFieldDecoder
+ var fieldDecoder7 *structFieldDecoder
+ var fieldDecoder8 *structFieldDecoder
+ var fieldDecoder9 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else if fieldName4 == 0 {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ } else if fieldName5 == 0 {
+ fieldName5 = fieldHash
+ fieldDecoder5 = fieldDecoder
+ } else if fieldName6 == 0 {
+ fieldName6 = fieldHash
+ fieldDecoder6 = fieldDecoder
+ } else if fieldName7 == 0 {
+ fieldName7 = fieldHash
+ fieldDecoder7 = fieldDecoder
+ } else if fieldName8 == 0 {
+ fieldName8 = fieldHash
+ fieldDecoder8 = fieldDecoder
+ } else {
+ fieldName9 = fieldHash
+ fieldDecoder9 = fieldDecoder
+ }
+ }
+ return &nineFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4,
+ fieldName5, fieldDecoder5,
+ fieldName6, fieldDecoder6,
+ fieldName7, fieldDecoder7,
+ fieldName8, fieldDecoder8,
+ fieldName9, fieldDecoder9}
+ case 10:
+ var fieldName1 int64
+ var fieldName2 int64
+ var fieldName3 int64
+ var fieldName4 int64
+ var fieldName5 int64
+ var fieldName6 int64
+ var fieldName7 int64
+ var fieldName8 int64
+ var fieldName9 int64
+ var fieldName10 int64
+ var fieldDecoder1 *structFieldDecoder
+ var fieldDecoder2 *structFieldDecoder
+ var fieldDecoder3 *structFieldDecoder
+ var fieldDecoder4 *structFieldDecoder
+ var fieldDecoder5 *structFieldDecoder
+ var fieldDecoder6 *structFieldDecoder
+ var fieldDecoder7 *structFieldDecoder
+ var fieldDecoder8 *structFieldDecoder
+ var fieldDecoder9 *structFieldDecoder
+ var fieldDecoder10 *structFieldDecoder
+ for fieldName, fieldDecoder := range fields {
+ fieldHash := calcHash(fieldName, ctx.caseSensitive())
+ _, known := knownHash[fieldHash]
+ if known {
+ return &generalStructDecoder{typ, fields, false}
+ }
+ knownHash[fieldHash] = struct{}{}
+ if fieldName1 == 0 {
+ fieldName1 = fieldHash
+ fieldDecoder1 = fieldDecoder
+ } else if fieldName2 == 0 {
+ fieldName2 = fieldHash
+ fieldDecoder2 = fieldDecoder
+ } else if fieldName3 == 0 {
+ fieldName3 = fieldHash
+ fieldDecoder3 = fieldDecoder
+ } else if fieldName4 == 0 {
+ fieldName4 = fieldHash
+ fieldDecoder4 = fieldDecoder
+ } else if fieldName5 == 0 {
+ fieldName5 = fieldHash
+ fieldDecoder5 = fieldDecoder
+ } else if fieldName6 == 0 {
+ fieldName6 = fieldHash
+ fieldDecoder6 = fieldDecoder
+ } else if fieldName7 == 0 {
+ fieldName7 = fieldHash
+ fieldDecoder7 = fieldDecoder
+ } else if fieldName8 == 0 {
+ fieldName8 = fieldHash
+ fieldDecoder8 = fieldDecoder
+ } else if fieldName9 == 0 {
+ fieldName9 = fieldHash
+ fieldDecoder9 = fieldDecoder
+ } else {
+ fieldName10 = fieldHash
+ fieldDecoder10 = fieldDecoder
+ }
+ }
+ return &tenFieldsStructDecoder{typ,
+ fieldName1, fieldDecoder1,
+ fieldName2, fieldDecoder2,
+ fieldName3, fieldDecoder3,
+ fieldName4, fieldDecoder4,
+ fieldName5, fieldDecoder5,
+ fieldName6, fieldDecoder6,
+ fieldName7, fieldDecoder7,
+ fieldName8, fieldDecoder8,
+ fieldName9, fieldDecoder9,
+ fieldName10, fieldDecoder10}
+ }
+ return &generalStructDecoder{typ, fields, false}
+}
+
+type generalStructDecoder struct {
+ typ reflect2.Type
+ fields map[string]*structFieldDecoder
+ disallowUnknownFields bool
+}
+
+func (decoder *generalStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ var c byte
+ for c = ','; c == ','; c = iter.nextToken() {
+ decoder.decodeOneField(ptr, iter)
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ if c != '}' {
+ iter.ReportError("struct Decode", `expect }, but found `+string([]byte{c}))
+ }
+ iter.decrementDepth()
+}
+
+func (decoder *generalStructDecoder) decodeOneField(ptr unsafe.Pointer, iter *Iterator) {
+ var field string
+ var fieldDecoder *structFieldDecoder
+ if iter.cfg.objectFieldMustBeSimpleString {
+ fieldBytes := iter.ReadStringAsSlice()
+ field = *(*string)(unsafe.Pointer(&fieldBytes))
+ fieldDecoder = decoder.fields[field]
+ if fieldDecoder == nil && !iter.cfg.caseSensitive {
+ fieldDecoder = decoder.fields[strings.ToLower(field)]
+ }
+ } else {
+ field = iter.ReadString()
+ fieldDecoder = decoder.fields[field]
+ if fieldDecoder == nil && !iter.cfg.caseSensitive {
+ fieldDecoder = decoder.fields[strings.ToLower(field)]
+ }
+ }
+ if fieldDecoder == nil {
+ if decoder.disallowUnknownFields {
+ msg := "found unknown field: " + field
+ iter.ReportError("ReadObject", msg)
+ }
+ c := iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
+ }
+ iter.Skip()
+ return
+ }
+ c := iter.nextToken()
+ if c != ':' {
+ iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
+ }
+ fieldDecoder.Decode(ptr, iter)
+}
+
+type skipObjectDecoder struct {
+ typ reflect2.Type
+}
+
+func (decoder *skipObjectDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ valueType := iter.WhatIsNext()
+ if valueType != ObjectValue && valueType != NilValue {
+ iter.ReportError("skipObjectDecoder", "expect object or null")
+ return
+ }
+ iter.Skip()
+}
+
+type oneFieldStructDecoder struct {
+ typ reflect2.Type
+ fieldHash int64
+ fieldDecoder *structFieldDecoder
+}
+
+func (decoder *oneFieldStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ if iter.readFieldHash() == decoder.fieldHash {
+ decoder.fieldDecoder.Decode(ptr, iter)
+ } else {
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type twoFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+}
+
+func (decoder *twoFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type threeFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+}
+
+func (decoder *threeFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type fourFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+}
+
+func (decoder *fourFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type fiveFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+ fieldHash5 int64
+ fieldDecoder5 *structFieldDecoder
+}
+
+func (decoder *fiveFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ case decoder.fieldHash5:
+ decoder.fieldDecoder5.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type sixFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+ fieldHash5 int64
+ fieldDecoder5 *structFieldDecoder
+ fieldHash6 int64
+ fieldDecoder6 *structFieldDecoder
+}
+
+func (decoder *sixFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ case decoder.fieldHash5:
+ decoder.fieldDecoder5.Decode(ptr, iter)
+ case decoder.fieldHash6:
+ decoder.fieldDecoder6.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type sevenFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+ fieldHash5 int64
+ fieldDecoder5 *structFieldDecoder
+ fieldHash6 int64
+ fieldDecoder6 *structFieldDecoder
+ fieldHash7 int64
+ fieldDecoder7 *structFieldDecoder
+}
+
+func (decoder *sevenFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ case decoder.fieldHash5:
+ decoder.fieldDecoder5.Decode(ptr, iter)
+ case decoder.fieldHash6:
+ decoder.fieldDecoder6.Decode(ptr, iter)
+ case decoder.fieldHash7:
+ decoder.fieldDecoder7.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type eightFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+ fieldHash5 int64
+ fieldDecoder5 *structFieldDecoder
+ fieldHash6 int64
+ fieldDecoder6 *structFieldDecoder
+ fieldHash7 int64
+ fieldDecoder7 *structFieldDecoder
+ fieldHash8 int64
+ fieldDecoder8 *structFieldDecoder
+}
+
+func (decoder *eightFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ case decoder.fieldHash5:
+ decoder.fieldDecoder5.Decode(ptr, iter)
+ case decoder.fieldHash6:
+ decoder.fieldDecoder6.Decode(ptr, iter)
+ case decoder.fieldHash7:
+ decoder.fieldDecoder7.Decode(ptr, iter)
+ case decoder.fieldHash8:
+ decoder.fieldDecoder8.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type nineFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+ fieldHash5 int64
+ fieldDecoder5 *structFieldDecoder
+ fieldHash6 int64
+ fieldDecoder6 *structFieldDecoder
+ fieldHash7 int64
+ fieldDecoder7 *structFieldDecoder
+ fieldHash8 int64
+ fieldDecoder8 *structFieldDecoder
+ fieldHash9 int64
+ fieldDecoder9 *structFieldDecoder
+}
+
+func (decoder *nineFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ case decoder.fieldHash5:
+ decoder.fieldDecoder5.Decode(ptr, iter)
+ case decoder.fieldHash6:
+ decoder.fieldDecoder6.Decode(ptr, iter)
+ case decoder.fieldHash7:
+ decoder.fieldDecoder7.Decode(ptr, iter)
+ case decoder.fieldHash8:
+ decoder.fieldDecoder8.Decode(ptr, iter)
+ case decoder.fieldHash9:
+ decoder.fieldDecoder9.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type tenFieldsStructDecoder struct {
+ typ reflect2.Type
+ fieldHash1 int64
+ fieldDecoder1 *structFieldDecoder
+ fieldHash2 int64
+ fieldDecoder2 *structFieldDecoder
+ fieldHash3 int64
+ fieldDecoder3 *structFieldDecoder
+ fieldHash4 int64
+ fieldDecoder4 *structFieldDecoder
+ fieldHash5 int64
+ fieldDecoder5 *structFieldDecoder
+ fieldHash6 int64
+ fieldDecoder6 *structFieldDecoder
+ fieldHash7 int64
+ fieldDecoder7 *structFieldDecoder
+ fieldHash8 int64
+ fieldDecoder8 *structFieldDecoder
+ fieldHash9 int64
+ fieldDecoder9 *structFieldDecoder
+ fieldHash10 int64
+ fieldDecoder10 *structFieldDecoder
+}
+
+func (decoder *tenFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if !iter.readObjectStart() {
+ return
+ }
+ if !iter.incrementDepth() {
+ return
+ }
+ for {
+ switch iter.readFieldHash() {
+ case decoder.fieldHash1:
+ decoder.fieldDecoder1.Decode(ptr, iter)
+ case decoder.fieldHash2:
+ decoder.fieldDecoder2.Decode(ptr, iter)
+ case decoder.fieldHash3:
+ decoder.fieldDecoder3.Decode(ptr, iter)
+ case decoder.fieldHash4:
+ decoder.fieldDecoder4.Decode(ptr, iter)
+ case decoder.fieldHash5:
+ decoder.fieldDecoder5.Decode(ptr, iter)
+ case decoder.fieldHash6:
+ decoder.fieldDecoder6.Decode(ptr, iter)
+ case decoder.fieldHash7:
+ decoder.fieldDecoder7.Decode(ptr, iter)
+ case decoder.fieldHash8:
+ decoder.fieldDecoder8.Decode(ptr, iter)
+ case decoder.fieldHash9:
+ decoder.fieldDecoder9.Decode(ptr, iter)
+ case decoder.fieldHash10:
+ decoder.fieldDecoder10.Decode(ptr, iter)
+ default:
+ iter.Skip()
+ }
+ if iter.isObjectEnd() {
+ break
+ }
+ }
+ if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 {
+ iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error())
+ }
+ iter.decrementDepth()
+}
+
+type structFieldDecoder struct {
+ field reflect2.StructField
+ fieldDecoder ValDecoder
+}
+
+func (decoder *structFieldDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ fieldPtr := decoder.field.UnsafeGet(ptr)
+ decoder.fieldDecoder.Decode(fieldPtr, iter)
+ if iter.Error != nil && iter.Error != io.EOF {
+ iter.Error = fmt.Errorf("%s: %s", decoder.field.Name(), iter.Error.Error())
+ }
+}
+
+type stringModeStringDecoder struct {
+ elemDecoder ValDecoder
+ cfg *frozenConfig
+}
+
+func (decoder *stringModeStringDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ decoder.elemDecoder.Decode(ptr, iter)
+ str := *((*string)(ptr))
+ tempIter := decoder.cfg.BorrowIterator([]byte(str))
+ defer decoder.cfg.ReturnIterator(tempIter)
+ *((*string)(ptr)) = tempIter.ReadString()
+}
+
+type stringModeNumberDecoder struct {
+ elemDecoder ValDecoder
+}
+
+func (decoder *stringModeNumberDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
+ if iter.WhatIsNext() == NilValue {
+ decoder.elemDecoder.Decode(ptr, iter)
+ return
+ }
+
+ c := iter.nextToken()
+ if c != '"' {
+ iter.ReportError("stringModeNumberDecoder", `expect ", but found `+string([]byte{c}))
+ return
+ }
+ decoder.elemDecoder.Decode(ptr, iter)
+ if iter.Error != nil {
+ return
+ }
+ c = iter.readByte()
+ if c != '"' {
+ iter.ReportError("stringModeNumberDecoder", `expect ", but found `+string([]byte{c}))
+ return
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/reflect_struct_encoder.go b/vendor/github.com/json-iterator/go/reflect_struct_encoder.go
new file mode 100644
index 00000000..152e3ef5
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/reflect_struct_encoder.go
@@ -0,0 +1,211 @@
+package jsoniter
+
+import (
+ "fmt"
+ "github.com/modern-go/reflect2"
+ "io"
+ "reflect"
+ "unsafe"
+)
+
+func encoderOfStruct(ctx *ctx, typ reflect2.Type) ValEncoder {
+ type bindingTo struct {
+ binding *Binding
+ toName string
+ ignored bool
+ }
+ orderedBindings := []*bindingTo{}
+ structDescriptor := describeStruct(ctx, typ)
+ for _, binding := range structDescriptor.Fields {
+ for _, toName := range binding.ToNames {
+ new := &bindingTo{
+ binding: binding,
+ toName: toName,
+ }
+ for _, old := range orderedBindings {
+ if old.toName != toName {
+ continue
+ }
+ old.ignored, new.ignored = resolveConflictBinding(ctx.frozenConfig, old.binding, new.binding)
+ }
+ orderedBindings = append(orderedBindings, new)
+ }
+ }
+ if len(orderedBindings) == 0 {
+ return &emptyStructEncoder{}
+ }
+ finalOrderedFields := []structFieldTo{}
+ for _, bindingTo := range orderedBindings {
+ if !bindingTo.ignored {
+ finalOrderedFields = append(finalOrderedFields, structFieldTo{
+ encoder: bindingTo.binding.Encoder.(*structFieldEncoder),
+ toName: bindingTo.toName,
+ })
+ }
+ }
+ return &structEncoder{typ, finalOrderedFields}
+}
+
+func createCheckIsEmpty(ctx *ctx, typ reflect2.Type) checkIsEmpty {
+ encoder := createEncoderOfNative(ctx, typ)
+ if encoder != nil {
+ return encoder
+ }
+ kind := typ.Kind()
+ switch kind {
+ case reflect.Interface:
+ return &dynamicEncoder{typ}
+ case reflect.Struct:
+ return &structEncoder{typ: typ}
+ case reflect.Array:
+ return &arrayEncoder{}
+ case reflect.Slice:
+ return &sliceEncoder{}
+ case reflect.Map:
+ return encoderOfMap(ctx, typ)
+ case reflect.Ptr:
+ return &OptionalEncoder{}
+ default:
+ return &lazyErrorEncoder{err: fmt.Errorf("unsupported type: %v", typ)}
+ }
+}
+
+func resolveConflictBinding(cfg *frozenConfig, old, new *Binding) (ignoreOld, ignoreNew bool) {
+ newTagged := new.Field.Tag().Get(cfg.getTagKey()) != ""
+ oldTagged := old.Field.Tag().Get(cfg.getTagKey()) != ""
+ if newTagged {
+ if oldTagged {
+ if len(old.levels) > len(new.levels) {
+ return true, false
+ } else if len(new.levels) > len(old.levels) {
+ return false, true
+ } else {
+ return true, true
+ }
+ } else {
+ return true, false
+ }
+ } else {
+ if oldTagged {
+ return true, false
+ }
+ if len(old.levels) > len(new.levels) {
+ return true, false
+ } else if len(new.levels) > len(old.levels) {
+ return false, true
+ } else {
+ return true, true
+ }
+ }
+}
+
+type structFieldEncoder struct {
+ field reflect2.StructField
+ fieldEncoder ValEncoder
+ omitempty bool
+}
+
+func (encoder *structFieldEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ fieldPtr := encoder.field.UnsafeGet(ptr)
+ encoder.fieldEncoder.Encode(fieldPtr, stream)
+ if stream.Error != nil && stream.Error != io.EOF {
+ stream.Error = fmt.Errorf("%s: %s", encoder.field.Name(), stream.Error.Error())
+ }
+}
+
+func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ fieldPtr := encoder.field.UnsafeGet(ptr)
+ return encoder.fieldEncoder.IsEmpty(fieldPtr)
+}
+
+func (encoder *structFieldEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool {
+ isEmbeddedPtrNil, converted := encoder.fieldEncoder.(IsEmbeddedPtrNil)
+ if !converted {
+ return false
+ }
+ fieldPtr := encoder.field.UnsafeGet(ptr)
+ return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr)
+}
+
+type IsEmbeddedPtrNil interface {
+ IsEmbeddedPtrNil(ptr unsafe.Pointer) bool
+}
+
+type structEncoder struct {
+ typ reflect2.Type
+ fields []structFieldTo
+}
+
+type structFieldTo struct {
+ encoder *structFieldEncoder
+ toName string
+}
+
+func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteObjectStart()
+ isNotFirst := false
+ for _, field := range encoder.fields {
+ if field.encoder.omitempty && field.encoder.IsEmpty(ptr) {
+ continue
+ }
+ if field.encoder.IsEmbeddedPtrNil(ptr) {
+ continue
+ }
+ if isNotFirst {
+ stream.WriteMore()
+ }
+ stream.WriteObjectField(field.toName)
+ field.encoder.Encode(ptr, stream)
+ isNotFirst = true
+ }
+ stream.WriteObjectEnd()
+ if stream.Error != nil && stream.Error != io.EOF {
+ stream.Error = fmt.Errorf("%v.%s", encoder.typ, stream.Error.Error())
+ }
+}
+
+func (encoder *structEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return false
+}
+
+type emptyStructEncoder struct {
+}
+
+func (encoder *emptyStructEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.WriteEmptyObject()
+}
+
+func (encoder *emptyStructEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return false
+}
+
+type stringModeNumberEncoder struct {
+ elemEncoder ValEncoder
+}
+
+func (encoder *stringModeNumberEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ stream.writeByte('"')
+ encoder.elemEncoder.Encode(ptr, stream)
+ stream.writeByte('"')
+}
+
+func (encoder *stringModeNumberEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.elemEncoder.IsEmpty(ptr)
+}
+
+type stringModeStringEncoder struct {
+ elemEncoder ValEncoder
+ cfg *frozenConfig
+}
+
+func (encoder *stringModeStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
+ tempStream := encoder.cfg.BorrowStream(nil)
+ tempStream.Attachment = stream.Attachment
+ defer encoder.cfg.ReturnStream(tempStream)
+ encoder.elemEncoder.Encode(ptr, tempStream)
+ stream.WriteString(string(tempStream.Buffer()))
+}
+
+func (encoder *stringModeStringEncoder) IsEmpty(ptr unsafe.Pointer) bool {
+ return encoder.elemEncoder.IsEmpty(ptr)
+}
diff --git a/vendor/github.com/json-iterator/go/stream.go b/vendor/github.com/json-iterator/go/stream.go
new file mode 100644
index 00000000..23d8a3ad
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/stream.go
@@ -0,0 +1,210 @@
+package jsoniter
+
+import (
+ "io"
+)
+
+// stream is a io.Writer like object, with JSON specific write functions.
+// Error is not returned as return value, but stored as Error member on this stream instance.
+type Stream struct {
+ cfg *frozenConfig
+ out io.Writer
+ buf []byte
+ Error error
+ indention int
+ Attachment interface{} // open for customized encoder
+}
+
+// NewStream create new stream instance.
+// cfg can be jsoniter.ConfigDefault.
+// out can be nil if write to internal buffer.
+// bufSize is the initial size for the internal buffer in bytes.
+func NewStream(cfg API, out io.Writer, bufSize int) *Stream {
+ return &Stream{
+ cfg: cfg.(*frozenConfig),
+ out: out,
+ buf: make([]byte, 0, bufSize),
+ Error: nil,
+ indention: 0,
+ }
+}
+
+// Pool returns a pool can provide more stream with same configuration
+func (stream *Stream) Pool() StreamPool {
+ return stream.cfg
+}
+
+// Reset reuse this stream instance by assign a new writer
+func (stream *Stream) Reset(out io.Writer) {
+ stream.out = out
+ stream.buf = stream.buf[:0]
+}
+
+// Available returns how many bytes are unused in the buffer.
+func (stream *Stream) Available() int {
+ return cap(stream.buf) - len(stream.buf)
+}
+
+// Buffered returns the number of bytes that have been written into the current buffer.
+func (stream *Stream) Buffered() int {
+ return len(stream.buf)
+}
+
+// Buffer if writer is nil, use this method to take the result
+func (stream *Stream) Buffer() []byte {
+ return stream.buf
+}
+
+// SetBuffer allows to append to the internal buffer directly
+func (stream *Stream) SetBuffer(buf []byte) {
+ stream.buf = buf
+}
+
+// Write writes the contents of p into the buffer.
+// It returns the number of bytes written.
+// If nn < len(p), it also returns an error explaining
+// why the write is short.
+func (stream *Stream) Write(p []byte) (nn int, err error) {
+ stream.buf = append(stream.buf, p...)
+ if stream.out != nil {
+ nn, err = stream.out.Write(stream.buf)
+ stream.buf = stream.buf[nn:]
+ return
+ }
+ return len(p), nil
+}
+
+// WriteByte writes a single byte.
+func (stream *Stream) writeByte(c byte) {
+ stream.buf = append(stream.buf, c)
+}
+
+func (stream *Stream) writeTwoBytes(c1 byte, c2 byte) {
+ stream.buf = append(stream.buf, c1, c2)
+}
+
+func (stream *Stream) writeThreeBytes(c1 byte, c2 byte, c3 byte) {
+ stream.buf = append(stream.buf, c1, c2, c3)
+}
+
+func (stream *Stream) writeFourBytes(c1 byte, c2 byte, c3 byte, c4 byte) {
+ stream.buf = append(stream.buf, c1, c2, c3, c4)
+}
+
+func (stream *Stream) writeFiveBytes(c1 byte, c2 byte, c3 byte, c4 byte, c5 byte) {
+ stream.buf = append(stream.buf, c1, c2, c3, c4, c5)
+}
+
+// Flush writes any buffered data to the underlying io.Writer.
+func (stream *Stream) Flush() error {
+ if stream.out == nil {
+ return nil
+ }
+ if stream.Error != nil {
+ return stream.Error
+ }
+ _, err := stream.out.Write(stream.buf)
+ if err != nil {
+ if stream.Error == nil {
+ stream.Error = err
+ }
+ return err
+ }
+ stream.buf = stream.buf[:0]
+ return nil
+}
+
+// WriteRaw write string out without quotes, just like []byte
+func (stream *Stream) WriteRaw(s string) {
+ stream.buf = append(stream.buf, s...)
+}
+
+// WriteNil write null to stream
+func (stream *Stream) WriteNil() {
+ stream.writeFourBytes('n', 'u', 'l', 'l')
+}
+
+// WriteTrue write true to stream
+func (stream *Stream) WriteTrue() {
+ stream.writeFourBytes('t', 'r', 'u', 'e')
+}
+
+// WriteFalse write false to stream
+func (stream *Stream) WriteFalse() {
+ stream.writeFiveBytes('f', 'a', 'l', 's', 'e')
+}
+
+// WriteBool write true or false into stream
+func (stream *Stream) WriteBool(val bool) {
+ if val {
+ stream.WriteTrue()
+ } else {
+ stream.WriteFalse()
+ }
+}
+
+// WriteObjectStart write { with possible indention
+func (stream *Stream) WriteObjectStart() {
+ stream.indention += stream.cfg.indentionStep
+ stream.writeByte('{')
+ stream.writeIndention(0)
+}
+
+// WriteObjectField write "field": with possible indention
+func (stream *Stream) WriteObjectField(field string) {
+ stream.WriteString(field)
+ if stream.indention > 0 {
+ stream.writeTwoBytes(':', ' ')
+ } else {
+ stream.writeByte(':')
+ }
+}
+
+// WriteObjectEnd write } with possible indention
+func (stream *Stream) WriteObjectEnd() {
+ stream.writeIndention(stream.cfg.indentionStep)
+ stream.indention -= stream.cfg.indentionStep
+ stream.writeByte('}')
+}
+
+// WriteEmptyObject write {}
+func (stream *Stream) WriteEmptyObject() {
+ stream.writeByte('{')
+ stream.writeByte('}')
+}
+
+// WriteMore write , with possible indention
+func (stream *Stream) WriteMore() {
+ stream.writeByte(',')
+ stream.writeIndention(0)
+}
+
+// WriteArrayStart write [ with possible indention
+func (stream *Stream) WriteArrayStart() {
+ stream.indention += stream.cfg.indentionStep
+ stream.writeByte('[')
+ stream.writeIndention(0)
+}
+
+// WriteEmptyArray write []
+func (stream *Stream) WriteEmptyArray() {
+ stream.writeTwoBytes('[', ']')
+}
+
+// WriteArrayEnd write ] with possible indention
+func (stream *Stream) WriteArrayEnd() {
+ stream.writeIndention(stream.cfg.indentionStep)
+ stream.indention -= stream.cfg.indentionStep
+ stream.writeByte(']')
+}
+
+func (stream *Stream) writeIndention(delta int) {
+ if stream.indention == 0 {
+ return
+ }
+ stream.writeByte('\n')
+ toWrite := stream.indention - delta
+ for i := 0; i < toWrite; i++ {
+ stream.buf = append(stream.buf, ' ')
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/stream_float.go b/vendor/github.com/json-iterator/go/stream_float.go
new file mode 100644
index 00000000..826aa594
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/stream_float.go
@@ -0,0 +1,111 @@
+package jsoniter
+
+import (
+ "fmt"
+ "math"
+ "strconv"
+)
+
+var pow10 []uint64
+
+func init() {
+ pow10 = []uint64{1, 10, 100, 1000, 10000, 100000, 1000000}
+}
+
+// WriteFloat32 write float32 to stream
+func (stream *Stream) WriteFloat32(val float32) {
+ if math.IsInf(float64(val), 0) || math.IsNaN(float64(val)) {
+ stream.Error = fmt.Errorf("unsupported value: %f", val)
+ return
+ }
+ abs := math.Abs(float64(val))
+ fmt := byte('f')
+ // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
+ if abs != 0 {
+ if float32(abs) < 1e-6 || float32(abs) >= 1e21 {
+ fmt = 'e'
+ }
+ }
+ stream.buf = strconv.AppendFloat(stream.buf, float64(val), fmt, -1, 32)
+}
+
+// WriteFloat32Lossy write float32 to stream with ONLY 6 digits precision although much much faster
+func (stream *Stream) WriteFloat32Lossy(val float32) {
+ if math.IsInf(float64(val), 0) || math.IsNaN(float64(val)) {
+ stream.Error = fmt.Errorf("unsupported value: %f", val)
+ return
+ }
+ if val < 0 {
+ stream.writeByte('-')
+ val = -val
+ }
+ if val > 0x4ffffff {
+ stream.WriteFloat32(val)
+ return
+ }
+ precision := 6
+ exp := uint64(1000000) // 6
+ lval := uint64(float64(val)*float64(exp) + 0.5)
+ stream.WriteUint64(lval / exp)
+ fval := lval % exp
+ if fval == 0 {
+ return
+ }
+ stream.writeByte('.')
+ for p := precision - 1; p > 0 && fval < pow10[p]; p-- {
+ stream.writeByte('0')
+ }
+ stream.WriteUint64(fval)
+ for stream.buf[len(stream.buf)-1] == '0' {
+ stream.buf = stream.buf[:len(stream.buf)-1]
+ }
+}
+
+// WriteFloat64 write float64 to stream
+func (stream *Stream) WriteFloat64(val float64) {
+ if math.IsInf(val, 0) || math.IsNaN(val) {
+ stream.Error = fmt.Errorf("unsupported value: %f", val)
+ return
+ }
+ abs := math.Abs(val)
+ fmt := byte('f')
+ // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
+ if abs != 0 {
+ if abs < 1e-6 || abs >= 1e21 {
+ fmt = 'e'
+ }
+ }
+ stream.buf = strconv.AppendFloat(stream.buf, float64(val), fmt, -1, 64)
+}
+
+// WriteFloat64Lossy write float64 to stream with ONLY 6 digits precision although much much faster
+func (stream *Stream) WriteFloat64Lossy(val float64) {
+ if math.IsInf(val, 0) || math.IsNaN(val) {
+ stream.Error = fmt.Errorf("unsupported value: %f", val)
+ return
+ }
+ if val < 0 {
+ stream.writeByte('-')
+ val = -val
+ }
+ if val > 0x4ffffff {
+ stream.WriteFloat64(val)
+ return
+ }
+ precision := 6
+ exp := uint64(1000000) // 6
+ lval := uint64(val*float64(exp) + 0.5)
+ stream.WriteUint64(lval / exp)
+ fval := lval % exp
+ if fval == 0 {
+ return
+ }
+ stream.writeByte('.')
+ for p := precision - 1; p > 0 && fval < pow10[p]; p-- {
+ stream.writeByte('0')
+ }
+ stream.WriteUint64(fval)
+ for stream.buf[len(stream.buf)-1] == '0' {
+ stream.buf = stream.buf[:len(stream.buf)-1]
+ }
+}
diff --git a/vendor/github.com/json-iterator/go/stream_int.go b/vendor/github.com/json-iterator/go/stream_int.go
new file mode 100644
index 00000000..d1059ee4
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/stream_int.go
@@ -0,0 +1,190 @@
+package jsoniter
+
+var digits []uint32
+
+func init() {
+ digits = make([]uint32, 1000)
+ for i := uint32(0); i < 1000; i++ {
+ digits[i] = (((i / 100) + '0') << 16) + ((((i / 10) % 10) + '0') << 8) + i%10 + '0'
+ if i < 10 {
+ digits[i] += 2 << 24
+ } else if i < 100 {
+ digits[i] += 1 << 24
+ }
+ }
+}
+
+func writeFirstBuf(space []byte, v uint32) []byte {
+ start := v >> 24
+ if start == 0 {
+ space = append(space, byte(v>>16), byte(v>>8))
+ } else if start == 1 {
+ space = append(space, byte(v>>8))
+ }
+ space = append(space, byte(v))
+ return space
+}
+
+func writeBuf(buf []byte, v uint32) []byte {
+ return append(buf, byte(v>>16), byte(v>>8), byte(v))
+}
+
+// WriteUint8 write uint8 to stream
+func (stream *Stream) WriteUint8(val uint8) {
+ stream.buf = writeFirstBuf(stream.buf, digits[val])
+}
+
+// WriteInt8 write int8 to stream
+func (stream *Stream) WriteInt8(nval int8) {
+ var val uint8
+ if nval < 0 {
+ val = uint8(-nval)
+ stream.buf = append(stream.buf, '-')
+ } else {
+ val = uint8(nval)
+ }
+ stream.buf = writeFirstBuf(stream.buf, digits[val])
+}
+
+// WriteUint16 write uint16 to stream
+func (stream *Stream) WriteUint16(val uint16) {
+ q1 := val / 1000
+ if q1 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[val])
+ return
+ }
+ r1 := val - q1*1000
+ stream.buf = writeFirstBuf(stream.buf, digits[q1])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+ return
+}
+
+// WriteInt16 write int16 to stream
+func (stream *Stream) WriteInt16(nval int16) {
+ var val uint16
+ if nval < 0 {
+ val = uint16(-nval)
+ stream.buf = append(stream.buf, '-')
+ } else {
+ val = uint16(nval)
+ }
+ stream.WriteUint16(val)
+}
+
+// WriteUint32 write uint32 to stream
+func (stream *Stream) WriteUint32(val uint32) {
+ q1 := val / 1000
+ if q1 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[val])
+ return
+ }
+ r1 := val - q1*1000
+ q2 := q1 / 1000
+ if q2 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q1])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+ return
+ }
+ r2 := q1 - q2*1000
+ q3 := q2 / 1000
+ if q3 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q2])
+ } else {
+ r3 := q2 - q3*1000
+ stream.buf = append(stream.buf, byte(q3+'0'))
+ stream.buf = writeBuf(stream.buf, digits[r3])
+ }
+ stream.buf = writeBuf(stream.buf, digits[r2])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+}
+
+// WriteInt32 write int32 to stream
+func (stream *Stream) WriteInt32(nval int32) {
+ var val uint32
+ if nval < 0 {
+ val = uint32(-nval)
+ stream.buf = append(stream.buf, '-')
+ } else {
+ val = uint32(nval)
+ }
+ stream.WriteUint32(val)
+}
+
+// WriteUint64 write uint64 to stream
+func (stream *Stream) WriteUint64(val uint64) {
+ q1 := val / 1000
+ if q1 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[val])
+ return
+ }
+ r1 := val - q1*1000
+ q2 := q1 / 1000
+ if q2 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q1])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+ return
+ }
+ r2 := q1 - q2*1000
+ q3 := q2 / 1000
+ if q3 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q2])
+ stream.buf = writeBuf(stream.buf, digits[r2])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+ return
+ }
+ r3 := q2 - q3*1000
+ q4 := q3 / 1000
+ if q4 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q3])
+ stream.buf = writeBuf(stream.buf, digits[r3])
+ stream.buf = writeBuf(stream.buf, digits[r2])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+ return
+ }
+ r4 := q3 - q4*1000
+ q5 := q4 / 1000
+ if q5 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q4])
+ stream.buf = writeBuf(stream.buf, digits[r4])
+ stream.buf = writeBuf(stream.buf, digits[r3])
+ stream.buf = writeBuf(stream.buf, digits[r2])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+ return
+ }
+ r5 := q4 - q5*1000
+ q6 := q5 / 1000
+ if q6 == 0 {
+ stream.buf = writeFirstBuf(stream.buf, digits[q5])
+ } else {
+ stream.buf = writeFirstBuf(stream.buf, digits[q6])
+ r6 := q5 - q6*1000
+ stream.buf = writeBuf(stream.buf, digits[r6])
+ }
+ stream.buf = writeBuf(stream.buf, digits[r5])
+ stream.buf = writeBuf(stream.buf, digits[r4])
+ stream.buf = writeBuf(stream.buf, digits[r3])
+ stream.buf = writeBuf(stream.buf, digits[r2])
+ stream.buf = writeBuf(stream.buf, digits[r1])
+}
+
+// WriteInt64 write int64 to stream
+func (stream *Stream) WriteInt64(nval int64) {
+ var val uint64
+ if nval < 0 {
+ val = uint64(-nval)
+ stream.buf = append(stream.buf, '-')
+ } else {
+ val = uint64(nval)
+ }
+ stream.WriteUint64(val)
+}
+
+// WriteInt write int to stream
+func (stream *Stream) WriteInt(val int) {
+ stream.WriteInt64(int64(val))
+}
+
+// WriteUint write uint to stream
+func (stream *Stream) WriteUint(val uint) {
+ stream.WriteUint64(uint64(val))
+}
diff --git a/vendor/github.com/json-iterator/go/stream_str.go b/vendor/github.com/json-iterator/go/stream_str.go
new file mode 100644
index 00000000..54c2ba0b
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/stream_str.go
@@ -0,0 +1,372 @@
+package jsoniter
+
+import (
+ "unicode/utf8"
+)
+
+// htmlSafeSet holds the value true if the ASCII character with the given
+// array position can be safely represented inside a JSON string, embedded
+// inside of HTML <script> tags, without any additional escaping.
+//
+// All values are true except for the ASCII control characters (0-31), the
+// double quote ("), the backslash character ("\"), HTML opening and closing
+// tags ("<" and ">"), and the ampersand ("&").
+var htmlSafeSet = [utf8.RuneSelf]bool{
+ ' ': true,
+ '!': true,
+ '"': false,
+ '#': true,
+ '$': true,
+ '%': true,
+ '&': false,
+ '\'': true,
+ '(': true,
+ ')': true,
+ '*': true,
+ '+': true,
+ ',': true,
+ '-': true,
+ '.': true,
+ '/': true,
+ '0': true,
+ '1': true,
+ '2': true,
+ '3': true,
+ '4': true,
+ '5': true,
+ '6': true,
+ '7': true,
+ '8': true,
+ '9': true,
+ ':': true,
+ ';': true,
+ '<': false,
+ '=': true,
+ '>': false,
+ '?': true,
+ '@': true,
+ 'A': true,
+ 'B': true,
+ 'C': true,
+ 'D': true,
+ 'E': true,
+ 'F': true,
+ 'G': true,
+ 'H': true,
+ 'I': true,
+ 'J': true,
+ 'K': true,
+ 'L': true,
+ 'M': true,
+ 'N': true,
+ 'O': true,
+ 'P': true,
+ 'Q': true,
+ 'R': true,
+ 'S': true,
+ 'T': true,
+ 'U': true,
+ 'V': true,
+ 'W': true,
+ 'X': true,
+ 'Y': true,
+ 'Z': true,
+ '[': true,
+ '\\': false,
+ ']': true,
+ '^': true,
+ '_': true,
+ '`': true,
+ 'a': true,
+ 'b': true,
+ 'c': true,
+ 'd': true,
+ 'e': true,
+ 'f': true,
+ 'g': true,
+ 'h': true,
+ 'i': true,
+ 'j': true,
+ 'k': true,
+ 'l': true,
+ 'm': true,
+ 'n': true,
+ 'o': true,
+ 'p': true,
+ 'q': true,
+ 'r': true,
+ 's': true,
+ 't': true,
+ 'u': true,
+ 'v': true,
+ 'w': true,
+ 'x': true,
+ 'y': true,
+ 'z': true,
+ '{': true,
+ '|': true,
+ '}': true,
+ '~': true,
+ '\u007f': true,
+}
+
+// safeSet holds the value true if the ASCII character with the given array
+// position can be represented inside a JSON string without any further
+// escaping.
+//
+// All values are true except for the ASCII control characters (0-31), the
+// double quote ("), and the backslash character ("\").
+var safeSet = [utf8.RuneSelf]bool{
+ ' ': true,
+ '!': true,
+ '"': false,
+ '#': true,
+ '$': true,
+ '%': true,
+ '&': true,
+ '\'': true,
+ '(': true,
+ ')': true,
+ '*': true,
+ '+': true,
+ ',': true,
+ '-': true,
+ '.': true,
+ '/': true,
+ '0': true,
+ '1': true,
+ '2': true,
+ '3': true,
+ '4': true,
+ '5': true,
+ '6': true,
+ '7': true,
+ '8': true,
+ '9': true,
+ ':': true,
+ ';': true,
+ '<': true,
+ '=': true,
+ '>': true,
+ '?': true,
+ '@': true,
+ 'A': true,
+ 'B': true,
+ 'C': true,
+ 'D': true,
+ 'E': true,
+ 'F': true,
+ 'G': true,
+ 'H': true,
+ 'I': true,
+ 'J': true,
+ 'K': true,
+ 'L': true,
+ 'M': true,
+ 'N': true,
+ 'O': true,
+ 'P': true,
+ 'Q': true,
+ 'R': true,
+ 'S': true,
+ 'T': true,
+ 'U': true,
+ 'V': true,
+ 'W': true,
+ 'X': true,
+ 'Y': true,
+ 'Z': true,
+ '[': true,
+ '\\': false,
+ ']': true,
+ '^': true,
+ '_': true,
+ '`': true,
+ 'a': true,
+ 'b': true,
+ 'c': true,
+ 'd': true,
+ 'e': true,
+ 'f': true,
+ 'g': true,
+ 'h': true,
+ 'i': true,
+ 'j': true,
+ 'k': true,
+ 'l': true,
+ 'm': true,
+ 'n': true,
+ 'o': true,
+ 'p': true,
+ 'q': true,
+ 'r': true,
+ 's': true,
+ 't': true,
+ 'u': true,
+ 'v': true,
+ 'w': true,
+ 'x': true,
+ 'y': true,
+ 'z': true,
+ '{': true,
+ '|': true,
+ '}': true,
+ '~': true,
+ '\u007f': true,
+}
+
+var hex = "0123456789abcdef"
+
+// WriteStringWithHTMLEscaped write string to stream with html special characters escaped
+func (stream *Stream) WriteStringWithHTMLEscaped(s string) {
+ valLen := len(s)
+ stream.buf = append(stream.buf, '"')
+ // write string, the fast path, without utf8 and escape support
+ i := 0
+ for ; i < valLen; i++ {
+ c := s[i]
+ if c < utf8.RuneSelf && htmlSafeSet[c] {
+ stream.buf = append(stream.buf, c)
+ } else {
+ break
+ }
+ }
+ if i == valLen {
+ stream.buf = append(stream.buf, '"')
+ return
+ }
+ writeStringSlowPathWithHTMLEscaped(stream, i, s, valLen)
+}
+
+func writeStringSlowPathWithHTMLEscaped(stream *Stream, i int, s string, valLen int) {
+ start := i
+ // for the remaining parts, we process them char by char
+ for i < valLen {
+ if b := s[i]; b < utf8.RuneSelf {
+ if htmlSafeSet[b] {
+ i++
+ continue
+ }
+ if start < i {
+ stream.WriteRaw(s[start:i])
+ }
+ switch b {
+ case '\\', '"':
+ stream.writeTwoBytes('\\', b)
+ case '\n':
+ stream.writeTwoBytes('\\', 'n')
+ case '\r':
+ stream.writeTwoBytes('\\', 'r')
+ case '\t':
+ stream.writeTwoBytes('\\', 't')
+ default:
+ // This encodes bytes < 0x20 except for \t, \n and \r.
+ // If escapeHTML is set, it also escapes <, >, and &
+ // because they can lead to security holes when
+ // user-controlled strings are rendered into JSON
+ // and served to some browsers.
+ stream.WriteRaw(`\u00`)
+ stream.writeTwoBytes(hex[b>>4], hex[b&0xF])
+ }
+ i++
+ start = i
+ continue
+ }
+ c, size := utf8.DecodeRuneInString(s[i:])
+ if c == utf8.RuneError && size == 1 {
+ if start < i {
+ stream.WriteRaw(s[start:i])
+ }
+ stream.WriteRaw(`\ufffd`)
+ i++
+ start = i
+ continue
+ }
+ // U+2028 is LINE SEPARATOR.
+ // U+2029 is PARAGRAPH SEPARATOR.
+ // They are both technically valid characters in JSON strings,
+ // but don't work in JSONP, which has to be evaluated as JavaScript,
+ // and can lead to security holes there. It is valid JSON to
+ // escape them, so we do so unconditionally.
+ // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
+ if c == '\u2028' || c == '\u2029' {
+ if start < i {
+ stream.WriteRaw(s[start:i])
+ }
+ stream.WriteRaw(`\u202`)
+ stream.writeByte(hex[c&0xF])
+ i += size
+ start = i
+ continue
+ }
+ i += size
+ }
+ if start < len(s) {
+ stream.WriteRaw(s[start:])
+ }
+ stream.writeByte('"')
+}
+
+// WriteString write string to stream without html escape
+func (stream *Stream) WriteString(s string) {
+ valLen := len(s)
+ stream.buf = append(stream.buf, '"')
+ // write string, the fast path, without utf8 and escape support
+ i := 0
+ for ; i < valLen; i++ {
+ c := s[i]
+ if c > 31 && c != '"' && c != '\\' {
+ stream.buf = append(stream.buf, c)
+ } else {
+ break
+ }
+ }
+ if i == valLen {
+ stream.buf = append(stream.buf, '"')
+ return
+ }
+ writeStringSlowPath(stream, i, s, valLen)
+}
+
+func writeStringSlowPath(stream *Stream, i int, s string, valLen int) {
+ start := i
+ // for the remaining parts, we process them char by char
+ for i < valLen {
+ if b := s[i]; b < utf8.RuneSelf {
+ if safeSet[b] {
+ i++
+ continue
+ }
+ if start < i {
+ stream.WriteRaw(s[start:i])
+ }
+ switch b {
+ case '\\', '"':
+ stream.writeTwoBytes('\\', b)
+ case '\n':
+ stream.writeTwoBytes('\\', 'n')
+ case '\r':
+ stream.writeTwoBytes('\\', 'r')
+ case '\t':
+ stream.writeTwoBytes('\\', 't')
+ default:
+ // This encodes bytes < 0x20 except for \t, \n and \r.
+ // If escapeHTML is set, it also escapes <, >, and &
+ // because they can lead to security holes when
+ // user-controlled strings are rendered into JSON
+ // and served to some browsers.
+ stream.WriteRaw(`\u00`)
+ stream.writeTwoBytes(hex[b>>4], hex[b&0xF])
+ }
+ i++
+ start = i
+ continue
+ }
+ i++
+ continue
+ }
+ if start < len(s) {
+ stream.WriteRaw(s[start:])
+ }
+ stream.writeByte('"')
+}
diff --git a/vendor/github.com/json-iterator/go/test.sh b/vendor/github.com/json-iterator/go/test.sh
new file mode 100644
index 00000000..f4e7c0b2
--- /dev/null
+++ b/vendor/github.com/json-iterator/go/test.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+echo "" > coverage.txt
+
+for d in $(go list ./... | grep -v vendor); do
+ go test -coverprofile=profile.out -coverpkg=github.com/json-iterator/go $d
+ if [ -f profile.out ]; then
+ cat profile.out >> coverage.txt
+ rm profile.out
+ fi
+done
diff --git a/vendor/github.com/klauspost/cpuid/v2/.gitignore b/vendor/github.com/klauspost/cpuid/v2/.gitignore
new file mode 100644
index 00000000..daf913b1
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/klauspost/cpuid/v2/.goreleaser.yml b/vendor/github.com/klauspost/cpuid/v2/.goreleaser.yml
new file mode 100644
index 00000000..944cc000
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/.goreleaser.yml
@@ -0,0 +1,74 @@
+# This is an example goreleaser.yaml file with some sane defaults.
+# Make sure to check the documentation at http://goreleaser.com
+
+builds:
+ -
+ id: "cpuid"
+ binary: cpuid
+ main: ./cmd/cpuid/main.go
+ env:
+ - CGO_ENABLED=0
+ flags:
+ - -ldflags=-s -w
+ goos:
+ - aix
+ - linux
+ - freebsd
+ - netbsd
+ - windows
+ - darwin
+ goarch:
+ - 386
+ - amd64
+ - arm64
+ goarm:
+ - 7
+
+archives:
+ -
+ id: cpuid
+ name_template: "cpuid-{{ .Os }}_{{ .Arch }}_{{ .Version }}"
+ replacements:
+ aix: AIX
+ darwin: OSX
+ linux: Linux
+ windows: Windows
+ 386: i386
+ amd64: x86_64
+ freebsd: FreeBSD
+ netbsd: NetBSD
+ format_overrides:
+ - goos: windows
+ format: zip
+ files:
+ - LICENSE
+checksum:
+ name_template: 'checksums.txt'
+snapshot:
+ name_template: "{{ .Tag }}-next"
+changelog:
+ sort: asc
+ filters:
+ exclude:
+ - '^doc:'
+ - '^docs:'
+ - '^test:'
+ - '^tests:'
+ - '^Update\sREADME.md'
+
+nfpms:
+ -
+ file_name_template: "cpuid_package_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
+ vendor: Klaus Post
+ homepage: https://github.com/klauspost/cpuid
+ maintainer: Klaus Post <klauspost@gmail.com>
+ description: CPUID Tool
+ license: BSD 3-Clause
+ formats:
+ - deb
+ - rpm
+ replacements:
+ darwin: Darwin
+ linux: Linux
+ freebsd: FreeBSD
+ amd64: x86_64
diff --git a/vendor/github.com/klauspost/cpuid/v2/.travis.yml b/vendor/github.com/klauspost/cpuid/v2/.travis.yml
new file mode 100644
index 00000000..aa9bad7e
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/.travis.yml
@@ -0,0 +1,67 @@
+language: go
+
+os:
+ - linux
+ - osx
+ - windows
+
+arch:
+ - amd64
+ - arm64
+
+go:
+ - 1.13.x
+ - 1.14.x
+ - 1.15.x
+ - 1.16.x
+ - master
+
+env:
+ - CGO_ENABLED=0
+
+script:
+ - go vet ./...
+ - go test -test.v -test.run ^TestCPUID$
+ - CGO_ENABLED=1 go test -race ./...
+ - go test -tags=nounsafe -test.v -test.run ^TestCPUID$
+ - go test -tags=noasm ./...
+ - go run ./cmd/cpuid/main.go
+ - go run ./cmd/cpuid/main.go -json
+
+matrix:
+ allow_failures:
+ - go: 'master'
+ fast_finish: true
+ include:
+ - stage: other
+ go: 1.16.x
+ os: linux
+ arch: amd64
+ script:
+ - diff <(gofmt -d .) <(printf "")
+ - diff <(gofmt -d ./private) <(printf "")
+ - curl -sfL https://git.io/goreleaser | VERSION=v0.157.0 sh -s -- check # check goreleaser config for deprecations
+ - curl -sL https://git.io/goreleaser | VERSION=v0.157.0 sh -s -- --snapshot --skip-publish --rm-dist
+ - go get github.com/klauspost/asmfmt&&go install github.com/klauspost/asmfmt/cmd/asmfmt
+ - diff <(asmfmt -d .) <(printf "")
+ - GOOS=linux GOARCH=386 go test .
+ - ./test-architectures.sh
+ - stage: other
+ go: 1.15.x
+ os: linux
+ arch: amd64
+ script:
+ - ./test-architectures.sh
+
+deploy:
+ - provider: script
+ skip_cleanup: true
+ script: curl -sL https://git.io/goreleaser | VERSION=v0.157.0 bash || true
+ on:
+ tags: true
+ condition: ($TRAVIS_OS_NAME = linux) && ($TRAVIS_CPU_ARCH = amd64)
+ go: 1.16.x
+branches:
+ only:
+ - master
+ - /^v\d+\.\d+(\.\d+)?(-\S*)?$/
diff --git a/vendor/github.com/klauspost/cpuid/v2/CONTRIBUTING.txt b/vendor/github.com/klauspost/cpuid/v2/CONTRIBUTING.txt
new file mode 100644
index 00000000..2ef4714f
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/CONTRIBUTING.txt
@@ -0,0 +1,35 @@
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2015- Klaus Post & Contributors.
+Email: klauspost@gmail.com
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+(c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+(d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
diff --git a/vendor/github.com/klauspost/cpuid/v2/LICENSE b/vendor/github.com/klauspost/cpuid/v2/LICENSE
new file mode 100644
index 00000000..5cec7ee9
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Klaus Post
+
+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/klauspost/cpuid/v2/README.md b/vendor/github.com/klauspost/cpuid/v2/README.md
new file mode 100644
index 00000000..465f4b77
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/README.md
@@ -0,0 +1,137 @@
+# cpuid
+Package cpuid provides information about the CPU running the current program.
+
+CPU features are detected on startup, and kept for fast access through the life of the application.
+Currently x86 / x64 (AMD64/i386) and ARM (ARM64) is supported, and no external C (cgo) code is used, which should make the library very easy to use.
+
+You can access the CPU information by accessing the shared CPU variable of the cpuid library.
+
+Package home: https://github.com/klauspost/cpuid
+
+[![PkgGoDev](https://pkg.go.dev/badge/github.com/klauspost/cpuid)](https://pkg.go.dev/github.com/klauspost/cpuid/v2)
+[![Build Status][3]][4]
+
+[3]: https://travis-ci.org/klauspost/cpuid.svg?branch=master
+[4]: https://travis-ci.org/klauspost/cpuid
+
+## installing
+
+`go get -u github.com/klauspost/cpuid/v2` using modules.
+
+Drop `v2` for others.
+
+## example
+
+```Go
+package main
+
+import (
+ "fmt"
+ "strings"
+
+ . "github.com/klauspost/cpuid/v2"
+)
+
+func main() {
+ // Print basic CPU information:
+ fmt.Println("Name:", CPU.BrandName)
+ fmt.Println("PhysicalCores:", CPU.PhysicalCores)
+ fmt.Println("ThreadsPerCore:", CPU.ThreadsPerCore)
+ fmt.Println("LogicalCores:", CPU.LogicalCores)
+ fmt.Println("Family", CPU.Family, "Model:", CPU.Model, "Vendor ID:", CPU.VendorID)
+ fmt.Println("Features:", fmt.Sprintf(strings.Join(CPU.FeatureSet(), ",")))
+ fmt.Println("Cacheline bytes:", CPU.CacheLine)
+ fmt.Println("L1 Data Cache:", CPU.Cache.L1D, "bytes")
+ fmt.Println("L1 Instruction Cache:", CPU.Cache.L1D, "bytes")
+ fmt.Println("L2 Cache:", CPU.Cache.L2, "bytes")
+ fmt.Println("L3 Cache:", CPU.Cache.L3, "bytes")
+ fmt.Println("Frequency", CPU.Hz, "hz")
+
+ // Test if we have these specific features:
+ if CPU.Supports(SSE, SSE2) {
+ fmt.Println("We have Streaming SIMD 2 Extensions")
+ }
+}
+```
+
+Sample output:
+```
+>go run main.go
+Name: AMD Ryzen 9 3950X 16-Core Processor
+PhysicalCores: 16
+ThreadsPerCore: 2
+LogicalCores: 32
+Family 23 Model: 113 Vendor ID: AMD
+Features: ADX,AESNI,AVX,AVX2,BMI1,BMI2,CLMUL,CMOV,CX16,F16C,FMA3,HTT,HYPERVISOR,LZCNT,MMX,MMXEXT,NX,POPCNT,RDRAND,RDSEED,RDTSCP,SHA,SSE,SSE2,SSE3,SSE4,SSE42,SSE4A,SSSE3
+Cacheline bytes: 64
+L1 Data Cache: 32768 bytes
+L1 Instruction Cache: 32768 bytes
+L2 Cache: 524288 bytes
+L3 Cache: 16777216 bytes
+Frequency 0 hz
+We have Streaming SIMD 2 Extensions
+```
+
+# usage
+
+The `cpuid.CPU` provides access to CPU features. Use `cpuid.CPU.Supports()` to check for CPU features.
+A faster `cpuid.CPU.Has()` is provided which will usually be inlined by the gc compiler.
+
+Note that for some cpu/os combinations some features will not be detected.
+`amd64` has rather good support and should work reliably on all platforms.
+
+Note that hypervisors may not pass through all CPU features.
+
+## arm64 feature detection
+
+Not all operating systems provide ARM features directly
+and there is no safe way to do so for the rest.
+
+Currently `arm64/linux` and `arm64/freebsd` should be quite reliable.
+`arm64/darwin` adds features expected from the M1 processor, but a lot remains undetected.
+
+A `DetectARM()` can be used if you are able to control your deployment,
+it will detect CPU features, but may crash if the OS doesn't intercept the calls.
+A `-cpu.arm` flag for detecting unsafe ARM features can be added. See below.
+
+Note that currently only features are detected on ARM,
+no additional information is currently available.
+
+## flags
+
+It is possible to add flags that affects cpu detection.
+
+For this the `Flags()` command is provided.
+
+This must be called *before* `flag.Parse()` AND after the flags have been parsed `Detect()` must be called.
+
+This means that any detection used in `init()` functions will not contain these flags.
+
+Example:
+
+```Go
+package main
+
+import (
+ "flag"
+ "fmt"
+ "strings"
+
+ "github.com/klauspost/cpuid/v2"
+)
+
+func main() {
+ cpuid.Flags()
+ flag.Parse()
+ cpuid.Detect()
+
+ // Test if we have these specific features:
+ if cpuid.CPU.Supports(cpuid.SSE, cpuid.SSE2) {
+ fmt.Println("We have Streaming SIMD 2 Extensions")
+ }
+}
+```
+
+# license
+
+This code is published under an MIT license. See LICENSE file for more information.
diff --git a/vendor/github.com/klauspost/cpuid/v2/cpuid.go b/vendor/github.com/klauspost/cpuid/v2/cpuid.go
new file mode 100644
index 00000000..43e9cc17
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/cpuid.go
@@ -0,0 +1,1023 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+// Package cpuid provides information about the CPU running the current program.
+//
+// CPU features are detected on startup, and kept for fast access through the life of the application.
+// Currently x86 / x64 (AMD64) as well as arm64 is supported.
+//
+// You can access the CPU information by accessing the shared CPU variable of the cpuid library.
+//
+// Package home: https://github.com/klauspost/cpuid
+package cpuid
+
+import (
+ "flag"
+ "fmt"
+ "math"
+ "os"
+ "runtime"
+ "strings"
+)
+
+// AMD refererence: https://www.amd.com/system/files/TechDocs/25481.pdf
+// and Processor Programming Reference (PPR)
+
+// Vendor is a representation of a CPU vendor.
+type Vendor int
+
+const (
+ VendorUnknown Vendor = iota
+ Intel
+ AMD
+ VIA
+ Transmeta
+ NSC
+ KVM // Kernel-based Virtual Machine
+ MSVM // Microsoft Hyper-V or Windows Virtual PC
+ VMware
+ XenHVM
+ Bhyve
+ Hygon
+ SiS
+ RDC
+
+ Ampere
+ ARM
+ Broadcom
+ Cavium
+ DEC
+ Fujitsu
+ Infineon
+ Motorola
+ NVIDIA
+ AMCC
+ Qualcomm
+ Marvell
+
+ lastVendor
+)
+
+//go:generate stringer -type=FeatureID,Vendor
+
+// FeatureID is the ID of a specific cpu feature.
+type FeatureID int
+
+const (
+ // Keep index -1 as unknown
+ UNKNOWN = -1
+
+ // Add features
+ ADX FeatureID = iota // Intel ADX (Multi-Precision Add-Carry Instruction Extensions)
+ AESNI // Advanced Encryption Standard New Instructions
+ AMD3DNOW // AMD 3DNOW
+ AMD3DNOWEXT // AMD 3DNowExt
+ AMXBF16 // Tile computational operations on BFLOAT16 numbers
+ AMXINT8 // Tile computational operations on 8-bit integers
+ AMXTILE // Tile architecture
+ AVX // AVX functions
+ AVX2 // AVX2 functions
+ AVX512BF16 // AVX-512 BFLOAT16 Instructions
+ AVX512BITALG // AVX-512 Bit Algorithms
+ AVX512BW // AVX-512 Byte and Word Instructions
+ AVX512CD // AVX-512 Conflict Detection Instructions
+ AVX512DQ // AVX-512 Doubleword and Quadword Instructions
+ AVX512ER // AVX-512 Exponential and Reciprocal Instructions
+ AVX512F // AVX-512 Foundation
+ AVX512IFMA // AVX-512 Integer Fused Multiply-Add Instructions
+ AVX512PF // AVX-512 Prefetch Instructions
+ AVX512VBMI // AVX-512 Vector Bit Manipulation Instructions
+ AVX512VBMI2 // AVX-512 Vector Bit Manipulation Instructions, Version 2
+ AVX512VL // AVX-512 Vector Length Extensions
+ AVX512VNNI // AVX-512 Vector Neural Network Instructions
+ AVX512VP2INTERSECT // AVX-512 Intersect for D/Q
+ AVX512VPOPCNTDQ // AVX-512 Vector Population Count Doubleword and Quadword
+ AVXSLOW // Indicates the CPU performs 2 128 bit operations instead of one.
+ BMI1 // Bit Manipulation Instruction Set 1
+ BMI2 // Bit Manipulation Instruction Set 2
+ CLDEMOTE // Cache Line Demote
+ CLMUL // Carry-less Multiplication
+ CMOV // i686 CMOV
+ CX16 // CMPXCHG16B Instruction
+ ENQCMD // Enqueue Command
+ ERMS // Enhanced REP MOVSB/STOSB
+ F16C // Half-precision floating-point conversion
+ FMA3 // Intel FMA 3. Does not imply AVX.
+ FMA4 // Bulldozer FMA4 functions
+ GFNI // Galois Field New Instructions
+ HLE // Hardware Lock Elision
+ HTT // Hyperthreading (enabled)
+ HYPERVISOR // This bit has been reserved by Intel & AMD for use by hypervisors
+ IBPB // Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB)
+ IBS // Instruction Based Sampling (AMD)
+ IBSBRNTRGT // Instruction Based Sampling Feature (AMD)
+ IBSFETCHSAM // Instruction Based Sampling Feature (AMD)
+ IBSFFV // Instruction Based Sampling Feature (AMD)
+ IBSOPCNT // Instruction Based Sampling Feature (AMD)
+ IBSOPCNTEXT // Instruction Based Sampling Feature (AMD)
+ IBSOPSAM // Instruction Based Sampling Feature (AMD)
+ IBSRDWROPCNT // Instruction Based Sampling Feature (AMD)
+ IBSRIPINVALIDCHK // Instruction Based Sampling Feature (AMD)
+ LZCNT // LZCNT instruction
+ MMX // standard MMX
+ MMXEXT // SSE integer functions or AMD MMX ext
+ MOVDIR64B // Move 64 Bytes as Direct Store
+ MOVDIRI // Move Doubleword as Direct Store
+ MPX // Intel MPX (Memory Protection Extensions)
+ NX // NX (No-Execute) bit
+ POPCNT // POPCNT instruction
+ RDRAND // RDRAND instruction is available
+ RDSEED // RDSEED instruction is available
+ RDTSCP // RDTSCP Instruction
+ RTM // Restricted Transactional Memory
+ SERIALIZE // Serialize Instruction Execution
+ SGX // Software Guard Extensions
+ SGXLC // Software Guard Extensions Launch Control
+ SHA // Intel SHA Extensions
+ SSE // SSE functions
+ SSE2 // P4 SSE functions
+ SSE3 // Prescott SSE3 functions
+ SSE4 // Penryn SSE4.1 functions
+ SSE42 // Nehalem SSE4.2 functions
+ SSE4A // AMD Barcelona microarchitecture SSE4a instructions
+ SSSE3 // Conroe SSSE3 functions
+ STIBP // Single Thread Indirect Branch Predictors
+ TBM // AMD Trailing Bit Manipulation
+ TSXLDTRK // Intel TSX Suspend Load Address Tracking
+ VAES // Vector AES
+ VMX // Virtual Machine Extensions
+ VPCLMULQDQ // Carry-Less Multiplication Quadword
+ WAITPKG // TPAUSE, UMONITOR, UMWAIT
+ WBNOINVD // Write Back and Do Not Invalidate Cache
+ XOP // Bulldozer XOP functions
+
+ // ARM features:
+ AESARM // AES instructions
+ ARMCPUID // Some CPU ID registers readable at user-level
+ ASIMD // Advanced SIMD
+ ASIMDDP // SIMD Dot Product
+ ASIMDHP // Advanced SIMD half-precision floating point
+ ASIMDRDM // Rounding Double Multiply Accumulate/Subtract (SQRDMLAH/SQRDMLSH)
+ ATOMICS // Large System Extensions (LSE)
+ CRC32 // CRC32/CRC32C instructions
+ DCPOP // Data cache clean to Point of Persistence (DC CVAP)
+ EVTSTRM // Generic timer
+ FCMA // Floatin point complex number addition and multiplication
+ FP // Single-precision and double-precision floating point
+ FPHP // Half-precision floating point
+ GPA // Generic Pointer Authentication
+ JSCVT // Javascript-style double->int convert (FJCVTZS)
+ LRCPC // Weaker release consistency (LDAPR, etc)
+ PMULL // Polynomial Multiply instructions (PMULL/PMULL2)
+ SHA1 // SHA-1 instructions (SHA1C, etc)
+ SHA2 // SHA-2 instructions (SHA256H, etc)
+ SHA3 // SHA-3 instructions (EOR3, RAXI, XAR, BCAX)
+ SHA512 // SHA512 instructions
+ SM3 // SM3 instructions
+ SM4 // SM4 instructions
+ SVE // Scalable Vector Extension
+
+ // Keep it last. It automatically defines the size of []flagSet
+ lastID
+
+ firstID FeatureID = UNKNOWN + 1
+)
+
+// CPUInfo contains information about the detected system CPU.
+type CPUInfo struct {
+ BrandName string // Brand name reported by the CPU
+ VendorID Vendor // Comparable CPU vendor ID
+ VendorString string // Raw vendor string.
+ featureSet flagSet // Features of the CPU
+ PhysicalCores int // Number of physical processor cores in your CPU. Will be 0 if undetectable.
+ ThreadsPerCore int // Number of threads per physical core. Will be 1 if undetectable.
+ LogicalCores int // Number of physical cores times threads that can run on each core through the use of hyperthreading. Will be 0 if undetectable.
+ Family int // CPU family number
+ Model int // CPU model number
+ CacheLine int // Cache line size in bytes. Will be 0 if undetectable.
+ Hz int64 // Clock speed, if known, 0 otherwise
+ Cache struct {
+ L1I int // L1 Instruction Cache (per core or shared). Will be -1 if undetected
+ L1D int // L1 Data Cache (per core or shared). Will be -1 if undetected
+ L2 int // L2 Cache (per core or shared). Will be -1 if undetected
+ L3 int // L3 Cache (per core, per ccx or shared). Will be -1 if undetected
+ }
+ SGX SGXSupport
+ maxFunc uint32
+ maxExFunc uint32
+}
+
+var cpuid func(op uint32) (eax, ebx, ecx, edx uint32)
+var cpuidex func(op, op2 uint32) (eax, ebx, ecx, edx uint32)
+var xgetbv func(index uint32) (eax, edx uint32)
+var rdtscpAsm func() (eax, ebx, ecx, edx uint32)
+var darwinHasAVX512 = func() bool { return false }
+
+// CPU contains information about the CPU as detected on startup,
+// or when Detect last was called.
+//
+// Use this as the primary entry point to you data.
+var CPU CPUInfo
+
+func init() {
+ initCPU()
+ Detect()
+}
+
+// Detect will re-detect current CPU info.
+// This will replace the content of the exported CPU variable.
+//
+// Unless you expect the CPU to change while you are running your program
+// you should not need to call this function.
+// If you call this, you must ensure that no other goroutine is accessing the
+// exported CPU variable.
+func Detect() {
+ // Set defaults
+ CPU.ThreadsPerCore = 1
+ CPU.Cache.L1I = -1
+ CPU.Cache.L1D = -1
+ CPU.Cache.L2 = -1
+ CPU.Cache.L3 = -1
+ safe := true
+ if detectArmFlag != nil {
+ safe = !*detectArmFlag
+ }
+ addInfo(&CPU, safe)
+ if displayFeats != nil && *displayFeats {
+ fmt.Println("cpu features:", strings.Join(CPU.FeatureSet(), ","))
+ // Exit with non-zero so tests will print value.
+ os.Exit(1)
+ }
+ if disableFlag != nil {
+ s := strings.Split(*disableFlag, ",")
+ for _, feat := range s {
+ feat := ParseFeature(strings.TrimSpace(feat))
+ if feat != UNKNOWN {
+ CPU.featureSet.unset(feat)
+ }
+ }
+ }
+}
+
+// DetectARM will detect ARM64 features.
+// This is NOT done automatically since it can potentially crash
+// if the OS does not handle the command.
+// If in the future this can be done safely this function may not
+// do anything.
+func DetectARM() {
+ addInfo(&CPU, false)
+}
+
+var detectArmFlag *bool
+var displayFeats *bool
+var disableFlag *string
+
+// Flags will enable flags.
+// This must be called *before* flag.Parse AND
+// Detect must be called after the flags have been parsed.
+// Note that this means that any detection used in init() functions
+// will not contain these flags.
+func Flags() {
+ disableFlag = flag.String("cpu.disable", "", "disable cpu features; comma separated list")
+ displayFeats = flag.Bool("cpu.features", false, "lists cpu features and exits")
+ detectArmFlag = flag.Bool("cpu.arm", false, "allow ARM features to be detected; can potentially crash")
+}
+
+// Supports returns whether the CPU supports all of the requested features.
+func (c CPUInfo) Supports(ids ...FeatureID) bool {
+ for _, id := range ids {
+ if !c.featureSet.inSet(id) {
+ return false
+ }
+ }
+ return true
+}
+
+// Has allows for checking a single feature.
+// Should be inlined by the compiler.
+func (c CPUInfo) Has(id FeatureID) bool {
+ return c.featureSet.inSet(id)
+}
+
+// Disable will disable one or several features.
+func (c *CPUInfo) Disable(ids ...FeatureID) bool {
+ for _, id := range ids {
+ c.featureSet.unset(id)
+ }
+ return true
+}
+
+// Enable will disable one or several features even if they were undetected.
+// This is of course not recommended for obvious reasons.
+func (c *CPUInfo) Enable(ids ...FeatureID) bool {
+ for _, id := range ids {
+ c.featureSet.set(id)
+ }
+ return true
+}
+
+// IsVendor returns true if vendor is recognized as Intel
+func (c CPUInfo) IsVendor(v Vendor) bool {
+ return c.VendorID == v
+}
+
+func (c CPUInfo) FeatureSet() []string {
+ s := make([]string, 0)
+ for _, f := range c.featureSet.Strings() {
+ s = append(s, f)
+ }
+ return s
+}
+
+// RTCounter returns the 64-bit time-stamp counter
+// Uses the RDTSCP instruction. The value 0 is returned
+// if the CPU does not support the instruction.
+func (c CPUInfo) RTCounter() uint64 {
+ if !c.Supports(RDTSCP) {
+ return 0
+ }
+ a, _, _, d := rdtscpAsm()
+ return uint64(a) | (uint64(d) << 32)
+}
+
+// Ia32TscAux returns the IA32_TSC_AUX part of the RDTSCP.
+// This variable is OS dependent, but on Linux contains information
+// about the current cpu/core the code is running on.
+// If the RDTSCP instruction isn't supported on the CPU, the value 0 is returned.
+func (c CPUInfo) Ia32TscAux() uint32 {
+ if !c.Supports(RDTSCP) {
+ return 0
+ }
+ _, _, ecx, _ := rdtscpAsm()
+ return ecx
+}
+
+// LogicalCPU will return the Logical CPU the code is currently executing on.
+// This is likely to change when the OS re-schedules the running thread
+// to another CPU.
+// If the current core cannot be detected, -1 will be returned.
+func (c CPUInfo) LogicalCPU() int {
+ if c.maxFunc < 1 {
+ return -1
+ }
+ _, ebx, _, _ := cpuid(1)
+ return int(ebx >> 24)
+}
+
+// hertz tries to compute the clock speed of the CPU. If leaf 15 is
+// supported, use it, otherwise parse the brand string. Yes, really.
+func hertz(model string) int64 {
+ mfi := maxFunctionID()
+ if mfi >= 0x15 {
+ eax, ebx, ecx, _ := cpuid(0x15)
+ if eax != 0 && ebx != 0 && ecx != 0 {
+ return int64((int64(ecx) * int64(ebx)) / int64(eax))
+ }
+ }
+ // computeHz determines the official rated speed of a CPU from its brand
+ // string. This insanity is *actually the official documented way to do
+ // this according to Intel*, prior to leaf 0x15 existing. The official
+ // documentation only shows this working for exactly `x.xx` or `xxxx`
+ // cases, e.g., `2.50GHz` or `1300MHz`; this parser will accept other
+ // sizes.
+ hz := strings.LastIndex(model, "Hz")
+ if hz < 3 {
+ return 0
+ }
+ var multiplier int64
+ switch model[hz-1] {
+ case 'M':
+ multiplier = 1000 * 1000
+ case 'G':
+ multiplier = 1000 * 1000 * 1000
+ case 'T':
+ multiplier = 1000 * 1000 * 1000 * 1000
+ }
+ if multiplier == 0 {
+ return 0
+ }
+ freq := int64(0)
+ divisor := int64(0)
+ decimalShift := int64(1)
+ var i int
+ for i = hz - 2; i >= 0 && model[i] != ' '; i-- {
+ if model[i] >= '0' && model[i] <= '9' {
+ freq += int64(model[i]-'0') * decimalShift
+ decimalShift *= 10
+ } else if model[i] == '.' {
+ if divisor != 0 {
+ return 0
+ }
+ divisor = decimalShift
+ } else {
+ return 0
+ }
+ }
+ // we didn't find a space
+ if i < 0 {
+ return 0
+ }
+ if divisor != 0 {
+ return (freq * multiplier) / divisor
+ }
+ return freq * multiplier
+}
+
+// VM Will return true if the cpu id indicates we are in
+// a virtual machine.
+func (c CPUInfo) VM() bool {
+ return CPU.featureSet.inSet(HYPERVISOR)
+}
+
+// flags contains detected cpu features and characteristics
+type flags uint64
+
+// log2(bits_in_uint64)
+const flagBitsLog2 = 6
+const flagBits = 1 << flagBitsLog2
+const flagMask = flagBits - 1
+
+// flagSet contains detected cpu features and characteristics in an array of flags
+type flagSet [(lastID + flagMask) / flagBits]flags
+
+func (s flagSet) inSet(feat FeatureID) bool {
+ return s[feat>>flagBitsLog2]&(1<<(feat&flagMask)) != 0
+}
+
+func (s *flagSet) set(feat FeatureID) {
+ s[feat>>flagBitsLog2] |= 1 << (feat & flagMask)
+}
+
+// setIf will set a feature if boolean is true.
+func (s *flagSet) setIf(cond bool, features ...FeatureID) {
+ if cond {
+ for _, offset := range features {
+ s[offset>>flagBitsLog2] |= 1 << (offset & flagMask)
+ }
+ }
+}
+
+func (s *flagSet) unset(offset FeatureID) {
+ bit := flags(1 << (offset & flagMask))
+ s[offset>>flagBitsLog2] = s[offset>>flagBitsLog2] & ^bit
+}
+
+// or with another flagset.
+func (s *flagSet) or(other flagSet) {
+ for i, v := range other[:] {
+ s[i] |= v
+ }
+}
+
+// ParseFeature will parse the string and return the ID of the matching feature.
+// Will return UNKNOWN if not found.
+func ParseFeature(s string) FeatureID {
+ s = strings.ToUpper(s)
+ for i := firstID; i < lastID; i++ {
+ if i.String() == s {
+ return i
+ }
+ }
+ return UNKNOWN
+}
+
+// Strings returns an array of the detected features for FlagsSet.
+func (s flagSet) Strings() []string {
+ if len(s) == 0 {
+ return []string{""}
+ }
+ r := make([]string, 0)
+ for i := firstID; i < lastID; i++ {
+ if s.inSet(i) {
+ r = append(r, i.String())
+ }
+ }
+ return r
+}
+
+func maxExtendedFunction() uint32 {
+ eax, _, _, _ := cpuid(0x80000000)
+ return eax
+}
+
+func maxFunctionID() uint32 {
+ a, _, _, _ := cpuid(0)
+ return a
+}
+
+func brandName() string {
+ if maxExtendedFunction() >= 0x80000004 {
+ v := make([]uint32, 0, 48)
+ for i := uint32(0); i < 3; i++ {
+ a, b, c, d := cpuid(0x80000002 + i)
+ v = append(v, a, b, c, d)
+ }
+ return strings.Trim(string(valAsString(v...)), " ")
+ }
+ return "unknown"
+}
+
+func threadsPerCore() int {
+ mfi := maxFunctionID()
+ vend, _ := vendorID()
+
+ if mfi < 0x4 || (vend != Intel && vend != AMD) {
+ return 1
+ }
+
+ if mfi < 0xb {
+ if vend != Intel {
+ return 1
+ }
+ _, b, _, d := cpuid(1)
+ if (d & (1 << 28)) != 0 {
+ // v will contain logical core count
+ v := (b >> 16) & 255
+ if v > 1 {
+ a4, _, _, _ := cpuid(4)
+ // physical cores
+ v2 := (a4 >> 26) + 1
+ if v2 > 0 {
+ return int(v) / int(v2)
+ }
+ }
+ }
+ return 1
+ }
+ _, b, _, _ := cpuidex(0xb, 0)
+ if b&0xffff == 0 {
+ if vend == AMD {
+ // Workaround for AMD returning 0, assume 2 if >= Zen 2
+ // It will be more correct than not.
+ fam, _ := familyModel()
+ _, _, _, d := cpuid(1)
+ if (d&(1<<28)) != 0 && fam >= 23 {
+ return 2
+ }
+ }
+ return 1
+ }
+ return int(b & 0xffff)
+}
+
+func logicalCores() int {
+ mfi := maxFunctionID()
+ v, _ := vendorID()
+ switch v {
+ case Intel:
+ // Use this on old Intel processors
+ if mfi < 0xb {
+ if mfi < 1 {
+ return 0
+ }
+ // CPUID.1:EBX[23:16] represents the maximum number of addressable IDs (initial APIC ID)
+ // that can be assigned to logical processors in a physical package.
+ // The value may not be the same as the number of logical processors that are present in the hardware of a physical package.
+ _, ebx, _, _ := cpuid(1)
+ logical := (ebx >> 16) & 0xff
+ return int(logical)
+ }
+ _, b, _, _ := cpuidex(0xb, 1)
+ return int(b & 0xffff)
+ case AMD, Hygon:
+ _, b, _, _ := cpuid(1)
+ return int((b >> 16) & 0xff)
+ default:
+ return 0
+ }
+}
+
+func familyModel() (int, int) {
+ if maxFunctionID() < 0x1 {
+ return 0, 0
+ }
+ eax, _, _, _ := cpuid(1)
+ family := ((eax >> 8) & 0xf) + ((eax >> 20) & 0xff)
+ model := ((eax >> 4) & 0xf) + ((eax >> 12) & 0xf0)
+ return int(family), int(model)
+}
+
+func physicalCores() int {
+ v, _ := vendorID()
+ switch v {
+ case Intel:
+ return logicalCores() / threadsPerCore()
+ case AMD, Hygon:
+ lc := logicalCores()
+ tpc := threadsPerCore()
+ if lc > 0 && tpc > 0 {
+ return lc / tpc
+ }
+
+ // The following is inaccurate on AMD EPYC 7742 64-Core Processor
+ if maxExtendedFunction() >= 0x80000008 {
+ _, _, c, _ := cpuid(0x80000008)
+ if c&0xff > 0 {
+ return int(c&0xff) + 1
+ }
+ }
+ }
+ return 0
+}
+
+// Except from http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID
+var vendorMapping = map[string]Vendor{
+ "AMDisbetter!": AMD,
+ "AuthenticAMD": AMD,
+ "CentaurHauls": VIA,
+ "GenuineIntel": Intel,
+ "TransmetaCPU": Transmeta,
+ "GenuineTMx86": Transmeta,
+ "Geode by NSC": NSC,
+ "VIA VIA VIA ": VIA,
+ "KVMKVMKVMKVM": KVM,
+ "Microsoft Hv": MSVM,
+ "VMwareVMware": VMware,
+ "XenVMMXenVMM": XenHVM,
+ "bhyve bhyve ": Bhyve,
+ "HygonGenuine": Hygon,
+ "Vortex86 SoC": SiS,
+ "SiS SiS SiS ": SiS,
+ "RiseRiseRise": SiS,
+ "Genuine RDC": RDC,
+}
+
+func vendorID() (Vendor, string) {
+ _, b, c, d := cpuid(0)
+ v := string(valAsString(b, d, c))
+ vend, ok := vendorMapping[v]
+ if !ok {
+ return VendorUnknown, v
+ }
+ return vend, v
+}
+
+func cacheLine() int {
+ if maxFunctionID() < 0x1 {
+ return 0
+ }
+
+ _, ebx, _, _ := cpuid(1)
+ cache := (ebx & 0xff00) >> 5 // cflush size
+ if cache == 0 && maxExtendedFunction() >= 0x80000006 {
+ _, _, ecx, _ := cpuid(0x80000006)
+ cache = ecx & 0xff // cacheline size
+ }
+ // TODO: Read from Cache and TLB Information
+ return int(cache)
+}
+
+func (c *CPUInfo) cacheSize() {
+ c.Cache.L1D = -1
+ c.Cache.L1I = -1
+ c.Cache.L2 = -1
+ c.Cache.L3 = -1
+ vendor, _ := vendorID()
+ switch vendor {
+ case Intel:
+ if maxFunctionID() < 4 {
+ return
+ }
+ for i := uint32(0); ; i++ {
+ eax, ebx, ecx, _ := cpuidex(4, i)
+ cacheType := eax & 15
+ if cacheType == 0 {
+ break
+ }
+ cacheLevel := (eax >> 5) & 7
+ coherency := int(ebx&0xfff) + 1
+ partitions := int((ebx>>12)&0x3ff) + 1
+ associativity := int((ebx>>22)&0x3ff) + 1
+ sets := int(ecx) + 1
+ size := associativity * partitions * coherency * sets
+ switch cacheLevel {
+ case 1:
+ if cacheType == 1 {
+ // 1 = Data Cache
+ c.Cache.L1D = size
+ } else if cacheType == 2 {
+ // 2 = Instruction Cache
+ c.Cache.L1I = size
+ } else {
+ if c.Cache.L1D < 0 {
+ c.Cache.L1I = size
+ }
+ if c.Cache.L1I < 0 {
+ c.Cache.L1I = size
+ }
+ }
+ case 2:
+ c.Cache.L2 = size
+ case 3:
+ c.Cache.L3 = size
+ }
+ }
+ case AMD, Hygon:
+ // Untested.
+ if maxExtendedFunction() < 0x80000005 {
+ return
+ }
+ _, _, ecx, edx := cpuid(0x80000005)
+ c.Cache.L1D = int(((ecx >> 24) & 0xFF) * 1024)
+ c.Cache.L1I = int(((edx >> 24) & 0xFF) * 1024)
+
+ if maxExtendedFunction() < 0x80000006 {
+ return
+ }
+ _, _, ecx, _ = cpuid(0x80000006)
+ c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024)
+
+ // CPUID Fn8000_001D_EAX_x[N:0] Cache Properties
+ if maxExtendedFunction() < 0x8000001D {
+ return
+ }
+ for i := uint32(0); i < math.MaxUint32; i++ {
+ eax, ebx, ecx, _ := cpuidex(0x8000001D, i)
+
+ level := (eax >> 5) & 7
+ cacheNumSets := ecx + 1
+ cacheLineSize := 1 + (ebx & 2047)
+ cachePhysPartitions := 1 + ((ebx >> 12) & 511)
+ cacheNumWays := 1 + ((ebx >> 22) & 511)
+
+ typ := eax & 15
+ size := int(cacheNumSets * cacheLineSize * cachePhysPartitions * cacheNumWays)
+ if typ == 0 {
+ return
+ }
+
+ switch level {
+ case 1:
+ switch typ {
+ case 1:
+ // Data cache
+ c.Cache.L1D = size
+ case 2:
+ // Inst cache
+ c.Cache.L1I = size
+ default:
+ if c.Cache.L1D < 0 {
+ c.Cache.L1I = size
+ }
+ if c.Cache.L1I < 0 {
+ c.Cache.L1I = size
+ }
+ }
+ case 2:
+ c.Cache.L2 = size
+ case 3:
+ c.Cache.L3 = size
+ }
+ }
+ }
+
+ return
+}
+
+type SGXEPCSection struct {
+ BaseAddress uint64
+ EPCSize uint64
+}
+
+type SGXSupport struct {
+ Available bool
+ LaunchControl bool
+ SGX1Supported bool
+ SGX2Supported bool
+ MaxEnclaveSizeNot64 int64
+ MaxEnclaveSize64 int64
+ EPCSections []SGXEPCSection
+}
+
+func hasSGX(available, lc bool) (rval SGXSupport) {
+ rval.Available = available
+
+ if !available {
+ return
+ }
+
+ rval.LaunchControl = lc
+
+ a, _, _, d := cpuidex(0x12, 0)
+ rval.SGX1Supported = a&0x01 != 0
+ rval.SGX2Supported = a&0x02 != 0
+ rval.MaxEnclaveSizeNot64 = 1 << (d & 0xFF) // pow 2
+ rval.MaxEnclaveSize64 = 1 << ((d >> 8) & 0xFF) // pow 2
+ rval.EPCSections = make([]SGXEPCSection, 0)
+
+ for subleaf := uint32(2); subleaf < 2+8; subleaf++ {
+ eax, ebx, ecx, edx := cpuidex(0x12, subleaf)
+ leafType := eax & 0xf
+
+ if leafType == 0 {
+ // Invalid subleaf, stop iterating
+ break
+ } else if leafType == 1 {
+ // EPC Section subleaf
+ baseAddress := uint64(eax&0xfffff000) + (uint64(ebx&0x000fffff) << 32)
+ size := uint64(ecx&0xfffff000) + (uint64(edx&0x000fffff) << 32)
+
+ section := SGXEPCSection{BaseAddress: baseAddress, EPCSize: size}
+ rval.EPCSections = append(rval.EPCSections, section)
+ }
+ }
+
+ return
+}
+
+func support() flagSet {
+ var fs flagSet
+ mfi := maxFunctionID()
+ vend, _ := vendorID()
+ if mfi < 0x1 {
+ return fs
+ }
+ family, model := familyModel()
+
+ _, _, c, d := cpuid(1)
+ fs.setIf((d&(1<<15)) != 0, CMOV)
+ fs.setIf((d&(1<<23)) != 0, MMX)
+ fs.setIf((d&(1<<25)) != 0, MMXEXT)
+ fs.setIf((d&(1<<25)) != 0, SSE)
+ fs.setIf((d&(1<<26)) != 0, SSE2)
+ fs.setIf((c&1) != 0, SSE3)
+ fs.setIf((c&(1<<5)) != 0, VMX)
+ fs.setIf((c&0x00000200) != 0, SSSE3)
+ fs.setIf((c&0x00080000) != 0, SSE4)
+ fs.setIf((c&0x00100000) != 0, SSE42)
+ fs.setIf((c&(1<<25)) != 0, AESNI)
+ fs.setIf((c&(1<<1)) != 0, CLMUL)
+ fs.setIf(c&(1<<23) != 0, POPCNT)
+ fs.setIf(c&(1<<30) != 0, RDRAND)
+
+ // This bit has been reserved by Intel & AMD for use by hypervisors,
+ // and indicates the presence of a hypervisor.
+ fs.setIf(c&(1<<31) != 0, HYPERVISOR)
+ fs.setIf(c&(1<<29) != 0, F16C)
+ fs.setIf(c&(1<<13) != 0, CX16)
+
+ if vend == Intel && (d&(1<<28)) != 0 && mfi >= 4 {
+ fs.setIf(threadsPerCore() > 1, HTT)
+ }
+ if vend == AMD && (d&(1<<28)) != 0 && mfi >= 4 {
+ fs.setIf(threadsPerCore() > 1, HTT)
+ }
+ // Check XGETBV/XSAVE (26), OXSAVE (27) and AVX (28) bits
+ const avxCheck = 1<<26 | 1<<27 | 1<<28
+ if c&avxCheck == avxCheck {
+ // Check for OS support
+ eax, _ := xgetbv(0)
+ if (eax & 0x6) == 0x6 {
+ fs.set(AVX)
+ switch vend {
+ case Intel:
+ // Older than Haswell.
+ fs.setIf(family == 6 && model < 60, AVXSLOW)
+ case AMD:
+ // Older than Zen 2
+ fs.setIf(family < 23 || (family == 23 && model < 49), AVXSLOW)
+ }
+ }
+ }
+ // FMA3 can be used with SSE registers, so no OS support is strictly needed.
+ // fma3 and OSXSAVE needed.
+ const fma3Check = 1<<12 | 1<<27
+ fs.setIf(c&fma3Check == fma3Check, FMA3)
+
+ // Check AVX2, AVX2 requires OS support, but BMI1/2 don't.
+ if mfi >= 7 {
+ _, ebx, ecx, edx := cpuidex(7, 0)
+ eax1, _, _, _ := cpuidex(7, 1)
+ if fs.inSet(AVX) && (ebx&0x00000020) != 0 {
+ fs.set(AVX2)
+ }
+ // CPUID.(EAX=7, ECX=0).EBX
+ if (ebx & 0x00000008) != 0 {
+ fs.set(BMI1)
+ fs.setIf((ebx&0x00000100) != 0, BMI2)
+ }
+ fs.setIf(ebx&(1<<2) != 0, SGX)
+ fs.setIf(ebx&(1<<4) != 0, HLE)
+ fs.setIf(ebx&(1<<9) != 0, ERMS)
+ fs.setIf(ebx&(1<<11) != 0, RTM)
+ fs.setIf(ebx&(1<<14) != 0, MPX)
+ fs.setIf(ebx&(1<<18) != 0, RDSEED)
+ fs.setIf(ebx&(1<<19) != 0, ADX)
+ fs.setIf(ebx&(1<<29) != 0, SHA)
+ // CPUID.(EAX=7, ECX=0).ECX
+ fs.setIf(ecx&(1<<5) != 0, WAITPKG)
+ fs.setIf(ecx&(1<<25) != 0, CLDEMOTE)
+ fs.setIf(ecx&(1<<27) != 0, MOVDIRI)
+ fs.setIf(ecx&(1<<28) != 0, MOVDIR64B)
+ fs.setIf(ecx&(1<<29) != 0, ENQCMD)
+ fs.setIf(ecx&(1<<30) != 0, SGXLC)
+ // CPUID.(EAX=7, ECX=0).EDX
+ fs.setIf(edx&(1<<14) != 0, SERIALIZE)
+ fs.setIf(edx&(1<<16) != 0, TSXLDTRK)
+ fs.setIf(edx&(1<<26) != 0, IBPB)
+ fs.setIf(edx&(1<<27) != 0, STIBP)
+
+ // Only detect AVX-512 features if XGETBV is supported
+ if c&((1<<26)|(1<<27)) == (1<<26)|(1<<27) {
+ // Check for OS support
+ eax, _ := xgetbv(0)
+
+ // Verify that XCR0[7:5] = โ€˜111bโ€™ (OPMASK state, upper 256-bit of ZMM0-ZMM15 and
+ // ZMM16-ZMM31 state are enabled by OS)
+ /// and that XCR0[2:1] = โ€˜11bโ€™ (XMM state and YMM state are enabled by OS).
+ hasAVX512 := (eax>>5)&7 == 7 && (eax>>1)&3 == 3
+ if runtime.GOOS == "darwin" {
+ hasAVX512 = fs.inSet(AVX) && darwinHasAVX512()
+ }
+ if hasAVX512 {
+ fs.setIf(ebx&(1<<16) != 0, AVX512F)
+ fs.setIf(ebx&(1<<17) != 0, AVX512DQ)
+ fs.setIf(ebx&(1<<21) != 0, AVX512IFMA)
+ fs.setIf(ebx&(1<<26) != 0, AVX512PF)
+ fs.setIf(ebx&(1<<27) != 0, AVX512ER)
+ fs.setIf(ebx&(1<<28) != 0, AVX512CD)
+ fs.setIf(ebx&(1<<30) != 0, AVX512BW)
+ fs.setIf(ebx&(1<<31) != 0, AVX512VL)
+ // ecx
+ fs.setIf(ecx&(1<<1) != 0, AVX512VBMI)
+ fs.setIf(ecx&(1<<6) != 0, AVX512VBMI2)
+ fs.setIf(ecx&(1<<8) != 0, GFNI)
+ fs.setIf(ecx&(1<<9) != 0, VAES)
+ fs.setIf(ecx&(1<<10) != 0, VPCLMULQDQ)
+ fs.setIf(ecx&(1<<11) != 0, AVX512VNNI)
+ fs.setIf(ecx&(1<<12) != 0, AVX512BITALG)
+ fs.setIf(ecx&(1<<14) != 0, AVX512VPOPCNTDQ)
+ // edx
+ fs.setIf(edx&(1<<8) != 0, AVX512VP2INTERSECT)
+ fs.setIf(edx&(1<<22) != 0, AMXBF16)
+ fs.setIf(edx&(1<<24) != 0, AMXTILE)
+ fs.setIf(edx&(1<<25) != 0, AMXINT8)
+ // eax1 = CPUID.(EAX=7, ECX=1).EAX
+ fs.setIf(eax1&(1<<5) != 0, AVX512BF16)
+ }
+ }
+ }
+
+ if maxExtendedFunction() >= 0x80000001 {
+ _, _, c, d := cpuid(0x80000001)
+ if (c & (1 << 5)) != 0 {
+ fs.set(LZCNT)
+ fs.set(POPCNT)
+ }
+ fs.setIf((c&(1<<10)) != 0, IBS)
+ fs.setIf((d&(1<<31)) != 0, AMD3DNOW)
+ fs.setIf((d&(1<<30)) != 0, AMD3DNOWEXT)
+ fs.setIf((d&(1<<23)) != 0, MMX)
+ fs.setIf((d&(1<<22)) != 0, MMXEXT)
+ fs.setIf((c&(1<<6)) != 0, SSE4A)
+ fs.setIf(d&(1<<20) != 0, NX)
+ fs.setIf(d&(1<<27) != 0, RDTSCP)
+
+ /* XOP and FMA4 use the AVX instruction coding scheme, so they can't be
+ * used unless the OS has AVX support. */
+ if fs.inSet(AVX) {
+ fs.setIf((c&0x00000800) != 0, XOP)
+ fs.setIf((c&0x00010000) != 0, FMA4)
+ }
+
+ }
+ if maxExtendedFunction() >= 0x80000008 {
+ _, b, _, _ := cpuid(0x80000008)
+ fs.setIf((b&(1<<9)) != 0, WBNOINVD)
+ }
+
+ if maxExtendedFunction() >= 0x8000001b && fs.inSet(IBS) {
+ eax, _, _, _ := cpuid(0x8000001b)
+ fs.setIf((eax>>0)&1 == 1, IBSFFV)
+ fs.setIf((eax>>1)&1 == 1, IBSFETCHSAM)
+ fs.setIf((eax>>2)&1 == 1, IBSOPSAM)
+ fs.setIf((eax>>3)&1 == 1, IBSRDWROPCNT)
+ fs.setIf((eax>>4)&1 == 1, IBSOPCNT)
+ fs.setIf((eax>>5)&1 == 1, IBSBRNTRGT)
+ fs.setIf((eax>>6)&1 == 1, IBSOPCNTEXT)
+ fs.setIf((eax>>7)&1 == 1, IBSRIPINVALIDCHK)
+ }
+
+ return fs
+}
+
+func valAsString(values ...uint32) []byte {
+ r := make([]byte, 4*len(values))
+ for i, v := range values {
+ dst := r[i*4:]
+ dst[0] = byte(v & 0xff)
+ dst[1] = byte((v >> 8) & 0xff)
+ dst[2] = byte((v >> 16) & 0xff)
+ dst[3] = byte((v >> 24) & 0xff)
+ switch {
+ case dst[0] == 0:
+ return r[:i*4]
+ case dst[1] == 0:
+ return r[:i*4+1]
+ case dst[2] == 0:
+ return r[:i*4+2]
+ case dst[3] == 0:
+ return r[:i*4+3]
+ }
+ }
+ return r
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/cpuid_386.s b/vendor/github.com/klauspost/cpuid/v2/cpuid_386.s
new file mode 100644
index 00000000..8587c3a1
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/cpuid_386.s
@@ -0,0 +1,47 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build 386,!gccgo,!noasm,!appengine
+
+// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
+TEXT ยทasmCpuid(SB), 7, $0
+ XORL CX, CX
+ MOVL op+0(FP), AX
+ CPUID
+ MOVL AX, eax+4(FP)
+ MOVL BX, ebx+8(FP)
+ MOVL CX, ecx+12(FP)
+ MOVL DX, edx+16(FP)
+ RET
+
+// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
+TEXT ยทasmCpuidex(SB), 7, $0
+ MOVL op+0(FP), AX
+ MOVL op2+4(FP), CX
+ CPUID
+ MOVL AX, eax+8(FP)
+ MOVL BX, ebx+12(FP)
+ MOVL CX, ecx+16(FP)
+ MOVL DX, edx+20(FP)
+ RET
+
+// func xgetbv(index uint32) (eax, edx uint32)
+TEXT ยทasmXgetbv(SB), 7, $0
+ MOVL index+0(FP), CX
+ BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
+ MOVL AX, eax+4(FP)
+ MOVL DX, edx+8(FP)
+ RET
+
+// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
+TEXT ยทasmRdtscpAsm(SB), 7, $0
+ BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
+ MOVL AX, eax+0(FP)
+ MOVL BX, ebx+4(FP)
+ MOVL CX, ecx+8(FP)
+ MOVL DX, edx+12(FP)
+ RET
+
+// func asmDarwinHasAVX512() bool
+TEXT ยทasmDarwinHasAVX512(SB), 7, $0
+ MOVL $0, eax+0(FP)
+ RET
diff --git a/vendor/github.com/klauspost/cpuid/v2/cpuid_amd64.s b/vendor/github.com/klauspost/cpuid/v2/cpuid_amd64.s
new file mode 100644
index 00000000..bc11f894
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/cpuid_amd64.s
@@ -0,0 +1,72 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build amd64,!gccgo,!noasm,!appengine
+
+// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
+TEXT ยทasmCpuid(SB), 7, $0
+ XORQ CX, CX
+ MOVL op+0(FP), AX
+ CPUID
+ MOVL AX, eax+8(FP)
+ MOVL BX, ebx+12(FP)
+ MOVL CX, ecx+16(FP)
+ MOVL DX, edx+20(FP)
+ RET
+
+// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
+TEXT ยทasmCpuidex(SB), 7, $0
+ MOVL op+0(FP), AX
+ MOVL op2+4(FP), CX
+ CPUID
+ MOVL AX, eax+8(FP)
+ MOVL BX, ebx+12(FP)
+ MOVL CX, ecx+16(FP)
+ MOVL DX, edx+20(FP)
+ RET
+
+// func asmXgetbv(index uint32) (eax, edx uint32)
+TEXT ยทasmXgetbv(SB), 7, $0
+ MOVL index+0(FP), CX
+ BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
+ MOVL AX, eax+8(FP)
+ MOVL DX, edx+12(FP)
+ RET
+
+// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
+TEXT ยทasmRdtscpAsm(SB), 7, $0
+ BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
+ MOVL AX, eax+0(FP)
+ MOVL BX, ebx+4(FP)
+ MOVL CX, ecx+8(FP)
+ MOVL DX, edx+12(FP)
+ RET
+
+// From https://go-review.googlesource.com/c/sys/+/285572/
+// func asmDarwinHasAVX512() bool
+TEXT ยทasmDarwinHasAVX512(SB), 7, $0-1
+ MOVB $0, ret+0(FP) // default to false
+
+#ifdef GOOS_darwin // return if not darwin
+#ifdef GOARCH_amd64 // return if not amd64
+// These values from:
+// https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/osfmk/i386/cpu_capabilities.h
+#define commpage64_base_address 0x00007fffffe00000
+#define commpage64_cpu_capabilities64 (commpage64_base_address+0x010)
+#define commpage64_version (commpage64_base_address+0x01E)
+#define hasAVX512F 0x0000004000000000
+ MOVQ $commpage64_version, BX
+ MOVW (BX), AX
+ CMPW AX, $13 // versions < 13 do not support AVX512
+ JL no_avx512
+ MOVQ $commpage64_cpu_capabilities64, BX
+ MOVQ (BX), AX
+ MOVQ $hasAVX512F, CX
+ ANDQ CX, AX
+ JZ no_avx512
+ MOVB $1, ret+0(FP)
+
+no_avx512:
+#endif
+#endif
+ RET
+
diff --git a/vendor/github.com/klauspost/cpuid/v2/cpuid_arm64.s b/vendor/github.com/klauspost/cpuid/v2/cpuid_arm64.s
new file mode 100644
index 00000000..b31d6aec
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/cpuid_arm64.s
@@ -0,0 +1,26 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build arm64,!gccgo,!noasm,!appengine
+
+// See https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
+
+// func getMidr
+TEXT ยทgetMidr(SB), 7, $0
+ WORD $0xd5380000 // mrs x0, midr_el1 /* Main ID Register */
+ MOVD R0, midr+0(FP)
+ RET
+
+// func getProcFeatures
+TEXT ยทgetProcFeatures(SB), 7, $0
+ WORD $0xd5380400 // mrs x0, id_aa64pfr0_el1 /* Processor Feature Register 0 */
+ MOVD R0, procFeatures+0(FP)
+ RET
+
+// func getInstAttributes
+TEXT ยทgetInstAttributes(SB), 7, $0
+ WORD $0xd5380600 // mrs x0, id_aa64isar0_el1 /* Instruction Set Attribute Register 0 */
+ WORD $0xd5380621 // mrs x1, id_aa64isar1_el1 /* Instruction Set Attribute Register 1 */
+ MOVD R0, instAttrReg0+0(FP)
+ MOVD R1, instAttrReg1+8(FP)
+ RET
+
diff --git a/vendor/github.com/klauspost/cpuid/v2/detect_arm64.go b/vendor/github.com/klauspost/cpuid/v2/detect_arm64.go
new file mode 100644
index 00000000..9bf9f77f
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/detect_arm64.go
@@ -0,0 +1,246 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build arm64,!gccgo,!noasm,!appengine
+
+package cpuid
+
+import "runtime"
+
+func getMidr() (midr uint64)
+func getProcFeatures() (procFeatures uint64)
+func getInstAttributes() (instAttrReg0, instAttrReg1 uint64)
+
+func initCPU() {
+ cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
+ cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
+ xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
+ rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
+}
+
+func addInfo(c *CPUInfo, safe bool) {
+ // Seems to be safe to assume on ARM64
+ c.CacheLine = 64
+ detectOS(c)
+
+ // ARM64 disabled since it may crash if interrupt is not intercepted by OS.
+ if safe && !c.Supports(ARMCPUID) && runtime.GOOS != "freebsd" {
+ return
+ }
+ midr := getMidr()
+
+ // MIDR_EL1 - Main ID Register
+ // https://developer.arm.com/docs/ddi0595/h/aarch64-system-registers/midr_el1
+ // x--------------------------------------------------x
+ // | Name | bits | visible |
+ // |--------------------------------------------------|
+ // | Implementer | [31-24] | y |
+ // |--------------------------------------------------|
+ // | Variant | [23-20] | y |
+ // |--------------------------------------------------|
+ // | Architecture | [19-16] | y |
+ // |--------------------------------------------------|
+ // | PartNum | [15-4] | y |
+ // |--------------------------------------------------|
+ // | Revision | [3-0] | y |
+ // x--------------------------------------------------x
+
+ switch (midr >> 24) & 0xff {
+ case 0xC0:
+ c.VendorString = "Ampere Computing"
+ c.VendorID = Ampere
+ case 0x41:
+ c.VendorString = "Arm Limited"
+ c.VendorID = ARM
+ case 0x42:
+ c.VendorString = "Broadcom Corporation"
+ c.VendorID = Broadcom
+ case 0x43:
+ c.VendorString = "Cavium Inc"
+ c.VendorID = Cavium
+ case 0x44:
+ c.VendorString = "Digital Equipment Corporation"
+ c.VendorID = DEC
+ case 0x46:
+ c.VendorString = "Fujitsu Ltd"
+ c.VendorID = Fujitsu
+ case 0x49:
+ c.VendorString = "Infineon Technologies AG"
+ c.VendorID = Infineon
+ case 0x4D:
+ c.VendorString = "Motorola or Freescale Semiconductor Inc"
+ c.VendorID = Motorola
+ case 0x4E:
+ c.VendorString = "NVIDIA Corporation"
+ c.VendorID = NVIDIA
+ case 0x50:
+ c.VendorString = "Applied Micro Circuits Corporation"
+ c.VendorID = AMCC
+ case 0x51:
+ c.VendorString = "Qualcomm Inc"
+ c.VendorID = Qualcomm
+ case 0x56:
+ c.VendorString = "Marvell International Ltd"
+ c.VendorID = Marvell
+ case 0x69:
+ c.VendorString = "Intel Corporation"
+ c.VendorID = Intel
+ }
+
+ // Lower 4 bits: Architecture
+ // Architecture Meaning
+ // 0b0001 Armv4.
+ // 0b0010 Armv4T.
+ // 0b0011 Armv5 (obsolete).
+ // 0b0100 Armv5T.
+ // 0b0101 Armv5TE.
+ // 0b0110 Armv5TEJ.
+ // 0b0111 Armv6.
+ // 0b1111 Architectural features are individually identified in the ID_* registers, see 'ID registers'.
+ // Upper 4 bit: Variant
+ // An IMPLEMENTATION DEFINED variant number.
+ // Typically, this field is used to distinguish between different product variants, or major revisions of a product.
+ c.Family = int(midr>>16) & 0xff
+
+ // PartNum, bits [15:4]
+ // An IMPLEMENTATION DEFINED primary part number for the device.
+ // On processors implemented by Arm, if the top four bits of the primary
+ // part number are 0x0 or 0x7, the variant and architecture are encoded differently.
+ // Revision, bits [3:0]
+ // An IMPLEMENTATION DEFINED revision number for the device.
+ c.Model = int(midr) & 0xffff
+
+ procFeatures := getProcFeatures()
+
+ // ID_AA64PFR0_EL1 - Processor Feature Register 0
+ // x--------------------------------------------------x
+ // | Name | bits | visible |
+ // |--------------------------------------------------|
+ // | DIT | [51-48] | y |
+ // |--------------------------------------------------|
+ // | SVE | [35-32] | y |
+ // |--------------------------------------------------|
+ // | GIC | [27-24] | n |
+ // |--------------------------------------------------|
+ // | AdvSIMD | [23-20] | y |
+ // |--------------------------------------------------|
+ // | FP | [19-16] | y |
+ // |--------------------------------------------------|
+ // | EL3 | [15-12] | n |
+ // |--------------------------------------------------|
+ // | EL2 | [11-8] | n |
+ // |--------------------------------------------------|
+ // | EL1 | [7-4] | n |
+ // |--------------------------------------------------|
+ // | EL0 | [3-0] | n |
+ // x--------------------------------------------------x
+
+ var f flagSet
+ // if procFeatures&(0xf<<48) != 0 {
+ // fmt.Println("DIT")
+ // }
+ f.setIf(procFeatures&(0xf<<32) != 0, SVE)
+ if procFeatures&(0xf<<20) != 15<<20 {
+ f.set(ASIMD)
+ // https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64pfr0_el1
+ // 0b0001 --> As for 0b0000, and also includes support for half-precision floating-point arithmetic.
+ f.setIf(procFeatures&(0xf<<20) == 1<<20, FPHP, ASIMDHP)
+ }
+ f.setIf(procFeatures&(0xf<<16) != 0, FP)
+
+ instAttrReg0, instAttrReg1 := getInstAttributes()
+
+ // https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
+ //
+ // ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0
+ // x--------------------------------------------------x
+ // | Name | bits | visible |
+ // |--------------------------------------------------|
+ // | TS | [55-52] | y |
+ // |--------------------------------------------------|
+ // | FHM | [51-48] | y |
+ // |--------------------------------------------------|
+ // | DP | [47-44] | y |
+ // |--------------------------------------------------|
+ // | SM4 | [43-40] | y |
+ // |--------------------------------------------------|
+ // | SM3 | [39-36] | y |
+ // |--------------------------------------------------|
+ // | SHA3 | [35-32] | y |
+ // |--------------------------------------------------|
+ // | RDM | [31-28] | y |
+ // |--------------------------------------------------|
+ // | ATOMICS | [23-20] | y |
+ // |--------------------------------------------------|
+ // | CRC32 | [19-16] | y |
+ // |--------------------------------------------------|
+ // | SHA2 | [15-12] | y |
+ // |--------------------------------------------------|
+ // | SHA1 | [11-8] | y |
+ // |--------------------------------------------------|
+ // | AES | [7-4] | y |
+ // x--------------------------------------------------x
+
+ // if instAttrReg0&(0xf<<52) != 0 {
+ // fmt.Println("TS")
+ // }
+ // if instAttrReg0&(0xf<<48) != 0 {
+ // fmt.Println("FHM")
+ // }
+ f.setIf(instAttrReg0&(0xf<<44) != 0, ASIMDDP)
+ f.setIf(instAttrReg0&(0xf<<40) != 0, SM4)
+ f.setIf(instAttrReg0&(0xf<<36) != 0, SM3)
+ f.setIf(instAttrReg0&(0xf<<32) != 0, SHA3)
+ f.setIf(instAttrReg0&(0xf<<28) != 0, ASIMDRDM)
+ f.setIf(instAttrReg0&(0xf<<20) != 0, ATOMICS)
+ f.setIf(instAttrReg0&(0xf<<16) != 0, CRC32)
+ f.setIf(instAttrReg0&(0xf<<12) != 0, SHA2)
+ // https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
+ // 0b0010 --> As 0b0001, plus SHA512H, SHA512H2, SHA512SU0, and SHA512SU1 instructions implemented.
+ f.setIf(instAttrReg0&(0xf<<12) == 2<<12, SHA512)
+ f.setIf(instAttrReg0&(0xf<<8) != 0, SHA1)
+ f.setIf(instAttrReg0&(0xf<<4) != 0, AESARM)
+ // https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
+ // 0b0010 --> As for 0b0001, plus PMULL/PMULL2 instructions operating on 64-bit data quantities.
+ f.setIf(instAttrReg0&(0xf<<4) == 2<<4, PMULL)
+
+ // https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar1_el1
+ //
+ // ID_AA64ISAR1_EL1 - Instruction set attribute register 1
+ // x--------------------------------------------------x
+ // | Name | bits | visible |
+ // |--------------------------------------------------|
+ // | GPI | [31-28] | y |
+ // |--------------------------------------------------|
+ // | GPA | [27-24] | y |
+ // |--------------------------------------------------|
+ // | LRCPC | [23-20] | y |
+ // |--------------------------------------------------|
+ // | FCMA | [19-16] | y |
+ // |--------------------------------------------------|
+ // | JSCVT | [15-12] | y |
+ // |--------------------------------------------------|
+ // | API | [11-8] | y |
+ // |--------------------------------------------------|
+ // | APA | [7-4] | y |
+ // |--------------------------------------------------|
+ // | DPB | [3-0] | y |
+ // x--------------------------------------------------x
+
+ // if instAttrReg1&(0xf<<28) != 0 {
+ // fmt.Println("GPI")
+ // }
+ f.setIf(instAttrReg1&(0xf<<28) != 24, GPA)
+ f.setIf(instAttrReg1&(0xf<<20) != 0, LRCPC)
+ f.setIf(instAttrReg1&(0xf<<16) != 0, FCMA)
+ f.setIf(instAttrReg1&(0xf<<12) != 0, JSCVT)
+ // if instAttrReg1&(0xf<<8) != 0 {
+ // fmt.Println("API")
+ // }
+ // if instAttrReg1&(0xf<<4) != 0 {
+ // fmt.Println("APA")
+ // }
+ f.setIf(instAttrReg1&(0xf<<0) != 0, DCPOP)
+
+ // Store
+ c.featureSet.or(f)
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/detect_ref.go b/vendor/github.com/klauspost/cpuid/v2/detect_ref.go
new file mode 100644
index 00000000..e9c8606a
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/detect_ref.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build !amd64,!386,!arm64 gccgo noasm appengine
+
+package cpuid
+
+func initCPU() {
+ cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
+ cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
+ xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
+ rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
+}
+
+func addInfo(info *CPUInfo, safe bool) {}
diff --git a/vendor/github.com/klauspost/cpuid/v2/detect_x86.go b/vendor/github.com/klauspost/cpuid/v2/detect_x86.go
new file mode 100644
index 00000000..93bc20f4
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/detect_x86.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build 386,!gccgo,!noasm,!appengine amd64,!gccgo,!noasm,!appengine
+
+package cpuid
+
+func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
+func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
+func asmXgetbv(index uint32) (eax, edx uint32)
+func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
+func asmDarwinHasAVX512() bool
+
+func initCPU() {
+ cpuid = asmCpuid
+ cpuidex = asmCpuidex
+ xgetbv = asmXgetbv
+ rdtscpAsm = asmRdtscpAsm
+ darwinHasAVX512 = asmDarwinHasAVX512
+}
+
+func addInfo(c *CPUInfo, safe bool) {
+ c.maxFunc = maxFunctionID()
+ c.maxExFunc = maxExtendedFunction()
+ c.BrandName = brandName()
+ c.CacheLine = cacheLine()
+ c.Family, c.Model = familyModel()
+ c.featureSet = support()
+ c.SGX = hasSGX(c.featureSet.inSet(SGX), c.featureSet.inSet(SGXLC))
+ c.ThreadsPerCore = threadsPerCore()
+ c.LogicalCores = logicalCores()
+ c.PhysicalCores = physicalCores()
+ c.VendorID, c.VendorString = vendorID()
+ c.Hz = hertz(c.BrandName)
+ c.cacheSize()
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/featureid_string.go b/vendor/github.com/klauspost/cpuid/v2/featureid_string.go
new file mode 100644
index 00000000..0e764f90
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/featureid_string.go
@@ -0,0 +1,173 @@
+// Code generated by "stringer -type=FeatureID,Vendor"; DO NOT EDIT.
+
+package cpuid
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[ADX-1]
+ _ = x[AESNI-2]
+ _ = x[AMD3DNOW-3]
+ _ = x[AMD3DNOWEXT-4]
+ _ = x[AMXBF16-5]
+ _ = x[AMXINT8-6]
+ _ = x[AMXTILE-7]
+ _ = x[AVX-8]
+ _ = x[AVX2-9]
+ _ = x[AVX512BF16-10]
+ _ = x[AVX512BITALG-11]
+ _ = x[AVX512BW-12]
+ _ = x[AVX512CD-13]
+ _ = x[AVX512DQ-14]
+ _ = x[AVX512ER-15]
+ _ = x[AVX512F-16]
+ _ = x[AVX512IFMA-17]
+ _ = x[AVX512PF-18]
+ _ = x[AVX512VBMI-19]
+ _ = x[AVX512VBMI2-20]
+ _ = x[AVX512VL-21]
+ _ = x[AVX512VNNI-22]
+ _ = x[AVX512VP2INTERSECT-23]
+ _ = x[AVX512VPOPCNTDQ-24]
+ _ = x[AVXSLOW-25]
+ _ = x[BMI1-26]
+ _ = x[BMI2-27]
+ _ = x[CLDEMOTE-28]
+ _ = x[CLMUL-29]
+ _ = x[CMOV-30]
+ _ = x[CX16-31]
+ _ = x[ENQCMD-32]
+ _ = x[ERMS-33]
+ _ = x[F16C-34]
+ _ = x[FMA3-35]
+ _ = x[FMA4-36]
+ _ = x[GFNI-37]
+ _ = x[HLE-38]
+ _ = x[HTT-39]
+ _ = x[HYPERVISOR-40]
+ _ = x[IBPB-41]
+ _ = x[IBS-42]
+ _ = x[IBSBRNTRGT-43]
+ _ = x[IBSFETCHSAM-44]
+ _ = x[IBSFFV-45]
+ _ = x[IBSOPCNT-46]
+ _ = x[IBSOPCNTEXT-47]
+ _ = x[IBSOPSAM-48]
+ _ = x[IBSRDWROPCNT-49]
+ _ = x[IBSRIPINVALIDCHK-50]
+ _ = x[LZCNT-51]
+ _ = x[MMX-52]
+ _ = x[MMXEXT-53]
+ _ = x[MOVDIR64B-54]
+ _ = x[MOVDIRI-55]
+ _ = x[MPX-56]
+ _ = x[NX-57]
+ _ = x[POPCNT-58]
+ _ = x[RDRAND-59]
+ _ = x[RDSEED-60]
+ _ = x[RDTSCP-61]
+ _ = x[RTM-62]
+ _ = x[SERIALIZE-63]
+ _ = x[SGX-64]
+ _ = x[SGXLC-65]
+ _ = x[SHA-66]
+ _ = x[SSE-67]
+ _ = x[SSE2-68]
+ _ = x[SSE3-69]
+ _ = x[SSE4-70]
+ _ = x[SSE42-71]
+ _ = x[SSE4A-72]
+ _ = x[SSSE3-73]
+ _ = x[STIBP-74]
+ _ = x[TBM-75]
+ _ = x[TSXLDTRK-76]
+ _ = x[VAES-77]
+ _ = x[VMX-78]
+ _ = x[VPCLMULQDQ-79]
+ _ = x[WAITPKG-80]
+ _ = x[WBNOINVD-81]
+ _ = x[XOP-82]
+ _ = x[AESARM-83]
+ _ = x[ARMCPUID-84]
+ _ = x[ASIMD-85]
+ _ = x[ASIMDDP-86]
+ _ = x[ASIMDHP-87]
+ _ = x[ASIMDRDM-88]
+ _ = x[ATOMICS-89]
+ _ = x[CRC32-90]
+ _ = x[DCPOP-91]
+ _ = x[EVTSTRM-92]
+ _ = x[FCMA-93]
+ _ = x[FP-94]
+ _ = x[FPHP-95]
+ _ = x[GPA-96]
+ _ = x[JSCVT-97]
+ _ = x[LRCPC-98]
+ _ = x[PMULL-99]
+ _ = x[SHA1-100]
+ _ = x[SHA2-101]
+ _ = x[SHA3-102]
+ _ = x[SHA512-103]
+ _ = x[SM3-104]
+ _ = x[SM4-105]
+ _ = x[SVE-106]
+ _ = x[lastID-107]
+ _ = x[firstID-0]
+}
+
+const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXINT8AMXTILEAVXAVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXSLOWBMI1BMI2CLDEMOTECLMULCMOVCX16ENQCMDERMSF16CFMA3FMA4GFNIHLEHTTHYPERVISORIBPBIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKLZCNTMMXMMXEXTMOVDIR64BMOVDIRIMPXNXPOPCNTRDRANDRDSEEDRDTSCPRTMSERIALIZESGXSGXLCSHASSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPTBMTSXLDTRKVAESVMXVPCLMULQDQWAITPKGWBNOINVDXOPAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
+
+var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 58, 62, 72, 84, 92, 100, 108, 116, 123, 133, 141, 151, 162, 170, 180, 198, 213, 220, 224, 228, 236, 241, 245, 249, 255, 259, 263, 267, 271, 275, 278, 281, 291, 295, 298, 308, 319, 325, 333, 344, 352, 364, 380, 385, 388, 394, 403, 410, 413, 415, 421, 427, 433, 439, 442, 451, 454, 459, 462, 465, 469, 473, 477, 482, 487, 492, 497, 500, 508, 512, 515, 525, 532, 540, 543, 549, 557, 562, 569, 576, 584, 591, 596, 601, 608, 612, 614, 618, 621, 626, 631, 636, 640, 644, 648, 654, 657, 660, 663, 669}
+
+func (i FeatureID) String() string {
+ if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {
+ return "FeatureID(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _FeatureID_name[_FeatureID_index[i]:_FeatureID_index[i+1]]
+}
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[VendorUnknown-0]
+ _ = x[Intel-1]
+ _ = x[AMD-2]
+ _ = x[VIA-3]
+ _ = x[Transmeta-4]
+ _ = x[NSC-5]
+ _ = x[KVM-6]
+ _ = x[MSVM-7]
+ _ = x[VMware-8]
+ _ = x[XenHVM-9]
+ _ = x[Bhyve-10]
+ _ = x[Hygon-11]
+ _ = x[SiS-12]
+ _ = x[RDC-13]
+ _ = x[Ampere-14]
+ _ = x[ARM-15]
+ _ = x[Broadcom-16]
+ _ = x[Cavium-17]
+ _ = x[DEC-18]
+ _ = x[Fujitsu-19]
+ _ = x[Infineon-20]
+ _ = x[Motorola-21]
+ _ = x[NVIDIA-22]
+ _ = x[AMCC-23]
+ _ = x[Qualcomm-24]
+ _ = x[Marvell-25]
+ _ = x[lastVendor-26]
+}
+
+const _Vendor_name = "VendorUnknownIntelAMDVIATransmetaNSCKVMMSVMVMwareXenHVMBhyveHygonSiSRDCAmpereARMBroadcomCaviumDECFujitsuInfineonMotorolaNVIDIAAMCCQualcommMarvelllastVendor"
+
+var _Vendor_index = [...]uint8{0, 13, 18, 21, 24, 33, 36, 39, 43, 49, 55, 60, 65, 68, 71, 77, 80, 88, 94, 97, 104, 112, 120, 126, 130, 138, 145, 155}
+
+func (i Vendor) String() string {
+ if i < 0 || i >= Vendor(len(_Vendor_index)-1) {
+ return "Vendor(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Vendor_name[_Vendor_index[i]:_Vendor_index[i+1]]
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/go.mod b/vendor/github.com/klauspost/cpuid/v2/go.mod
new file mode 100644
index 00000000..2afac8eb
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/go.mod
@@ -0,0 +1,3 @@
+module github.com/klauspost/cpuid/v2
+
+go 1.13
diff --git a/vendor/github.com/klauspost/cpuid/v2/os_darwin_arm64.go b/vendor/github.com/klauspost/cpuid/v2/os_darwin_arm64.go
new file mode 100644
index 00000000..8d2cb036
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/os_darwin_arm64.go
@@ -0,0 +1,19 @@
+// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
+
+package cpuid
+
+import "runtime"
+
+func detectOS(c *CPUInfo) bool {
+ // There are no hw.optional sysctl values for the below features on Mac OS 11.0
+ // to detect their supported state dynamically. Assume the CPU features that
+ // Apple Silicon M1 supports to be available as a minimal set of features
+ // to all Go programs running on darwin/arm64.
+ // TODO: Add more if we know them.
+ c.featureSet.setIf(runtime.GOOS != "ios", AESARM, PMULL, SHA1, SHA2)
+ c.PhysicalCores = runtime.NumCPU()
+ // For now assuming 1 thread per core...
+ c.ThreadsPerCore = 1
+ c.LogicalCores = c.PhysicalCores
+ return true
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/os_linux_arm64.go b/vendor/github.com/klauspost/cpuid/v2/os_linux_arm64.go
new file mode 100644
index 00000000..ee278b9e
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/os_linux_arm64.go
@@ -0,0 +1,130 @@
+// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
+
+// Copyright 2018 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 located
+// here https://github.com/golang/sys/blob/master/LICENSE
+
+package cpuid
+
+import (
+ "encoding/binary"
+ "io/ioutil"
+ "runtime"
+)
+
+// HWCAP bits.
+const (
+ hwcap_FP = 1 << 0
+ hwcap_ASIMD = 1 << 1
+ hwcap_EVTSTRM = 1 << 2
+ hwcap_AES = 1 << 3
+ hwcap_PMULL = 1 << 4
+ hwcap_SHA1 = 1 << 5
+ hwcap_SHA2 = 1 << 6
+ hwcap_CRC32 = 1 << 7
+ hwcap_ATOMICS = 1 << 8
+ hwcap_FPHP = 1 << 9
+ hwcap_ASIMDHP = 1 << 10
+ hwcap_CPUID = 1 << 11
+ hwcap_ASIMDRDM = 1 << 12
+ hwcap_JSCVT = 1 << 13
+ hwcap_FCMA = 1 << 14
+ hwcap_LRCPC = 1 << 15
+ hwcap_DCPOP = 1 << 16
+ hwcap_SHA3 = 1 << 17
+ hwcap_SM3 = 1 << 18
+ hwcap_SM4 = 1 << 19
+ hwcap_ASIMDDP = 1 << 20
+ hwcap_SHA512 = 1 << 21
+ hwcap_SVE = 1 << 22
+ hwcap_ASIMDFHM = 1 << 23
+)
+
+func detectOS(c *CPUInfo) bool {
+ // For now assuming no hyperthreading is reasonable.
+ c.LogicalCores = runtime.NumCPU()
+ c.PhysicalCores = c.LogicalCores
+ c.ThreadsPerCore = 1
+ if hwcap == 0 {
+ // We did not get values from the runtime.
+ // Try reading /proc/self/auxv
+
+ // From https://github.com/golang/sys
+ const (
+ _AT_HWCAP = 16
+ _AT_HWCAP2 = 26
+
+ uintSize = int(32 << (^uint(0) >> 63))
+ )
+
+ buf, err := ioutil.ReadFile("/proc/self/auxv")
+ if err != nil {
+ // e.g. on android /proc/self/auxv is not accessible, so silently
+ // ignore the error and leave Initialized = false. On some
+ // architectures (e.g. arm64) doinit() implements a fallback
+ // readout and will set Initialized = true again.
+ return false
+ }
+ bo := binary.LittleEndian
+ for len(buf) >= 2*(uintSize/8) {
+ var tag, val uint
+ switch uintSize {
+ case 32:
+ tag = uint(bo.Uint32(buf[0:]))
+ val = uint(bo.Uint32(buf[4:]))
+ buf = buf[8:]
+ case 64:
+ tag = uint(bo.Uint64(buf[0:]))
+ val = uint(bo.Uint64(buf[8:]))
+ buf = buf[16:]
+ }
+ switch tag {
+ case _AT_HWCAP:
+ hwcap = val
+ case _AT_HWCAP2:
+ // Not used
+ }
+ }
+ if hwcap == 0 {
+ return false
+ }
+ }
+
+ // HWCap was populated by the runtime from the auxiliary vector.
+ // Use HWCap information since reading aarch64 system registers
+ // is not supported in user space on older linux kernels.
+ c.featureSet.setIf(isSet(hwcap, hwcap_AES), AESARM)
+ c.featureSet.setIf(isSet(hwcap, hwcap_ASIMD), ASIMD)
+ c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDDP), ASIMDDP)
+ c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDHP), ASIMDHP)
+ c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDRDM), ASIMDRDM)
+ c.featureSet.setIf(isSet(hwcap, hwcap_CPUID), ARMCPUID)
+ c.featureSet.setIf(isSet(hwcap, hwcap_CRC32), CRC32)
+ c.featureSet.setIf(isSet(hwcap, hwcap_DCPOP), DCPOP)
+ c.featureSet.setIf(isSet(hwcap, hwcap_EVTSTRM), EVTSTRM)
+ c.featureSet.setIf(isSet(hwcap, hwcap_FCMA), FCMA)
+ c.featureSet.setIf(isSet(hwcap, hwcap_FP), FP)
+ c.featureSet.setIf(isSet(hwcap, hwcap_FPHP), FPHP)
+ c.featureSet.setIf(isSet(hwcap, hwcap_JSCVT), JSCVT)
+ c.featureSet.setIf(isSet(hwcap, hwcap_LRCPC), LRCPC)
+ c.featureSet.setIf(isSet(hwcap, hwcap_PMULL), PMULL)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SHA1), SHA1)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SHA2), SHA2)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SHA3), SHA3)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SHA512), SHA512)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SM3), SM3)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SM4), SM4)
+ c.featureSet.setIf(isSet(hwcap, hwcap_SVE), SVE)
+
+ // The Samsung S9+ kernel reports support for atomics, but not all cores
+ // actually support them, resulting in SIGILL. See issue #28431.
+ // TODO(elias.naur): Only disable the optimization on bad chipsets on android.
+ c.featureSet.setIf(isSet(hwcap, hwcap_ATOMICS) && runtime.GOOS != "android", ATOMICS)
+
+ return true
+}
+
+func isSet(hwc uint, value uint) bool {
+ return hwc&value != 0
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/os_other_arm64.go b/vendor/github.com/klauspost/cpuid/v2/os_other_arm64.go
new file mode 100644
index 00000000..1a951e6c
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/os_other_arm64.go
@@ -0,0 +1,17 @@
+// Copyright (c) 2020 Klaus Post, released under MIT License. See LICENSE file.
+
+// +build arm64
+// +build !linux
+// +build !darwin
+
+package cpuid
+
+import "runtime"
+
+func detectOS(c *CPUInfo) bool {
+ c.PhysicalCores = runtime.NumCPU()
+ // For now assuming 1 thread per core...
+ c.ThreadsPerCore = 1
+ c.LogicalCores = c.PhysicalCores
+ return false
+}
diff --git a/vendor/github.com/klauspost/cpuid/v2/os_safe_linux_arm64.go b/vendor/github.com/klauspost/cpuid/v2/os_safe_linux_arm64.go
new file mode 100644
index 00000000..4d0b8b46
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/os_safe_linux_arm64.go
@@ -0,0 +1,7 @@
+// Copyright (c) 2021 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build nounsafe
+
+package cpuid
+
+var hwcap uint
diff --git a/vendor/github.com/klauspost/cpuid/v2/os_unsafe_linux_arm64.go b/vendor/github.com/klauspost/cpuid/v2/os_unsafe_linux_arm64.go
new file mode 100644
index 00000000..32980028
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/os_unsafe_linux_arm64.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2021 Klaus Post, released under MIT License. See LICENSE file.
+
+//+build !nounsafe
+
+package cpuid
+
+import _ "unsafe" // needed for go:linkname
+
+//go:linkname hwcap internal/cpu.HWCap
+var hwcap uint
diff --git a/vendor/github.com/klauspost/cpuid/v2/test-architectures.sh b/vendor/github.com/klauspost/cpuid/v2/test-architectures.sh
new file mode 100644
index 00000000..471d986d
--- /dev/null
+++ b/vendor/github.com/klauspost/cpuid/v2/test-architectures.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -e
+
+go tool dist list | while IFS=/ read os arch; do
+ echo "Checking $os/$arch..."
+ echo " normal"
+ GOARCH=$arch GOOS=$os go build -o /dev/null .
+ echo " noasm"
+ GOARCH=$arch GOOS=$os go build -tags noasm -o /dev/null .
+ echo " appengine"
+ GOARCH=$arch GOOS=$os go build -tags appengine -o /dev/null .
+ echo " noasm,appengine"
+ GOARCH=$arch GOOS=$os go build -tags 'appengine noasm' -o /dev/null .
+done
diff --git a/vendor/github.com/matterbridge/matterclient/channels.go b/vendor/github.com/matterbridge/matterclient/channels.go
new file mode 100644
index 00000000..6bf22a68
--- /dev/null
+++ b/vendor/github.com/matterbridge/matterclient/channels.go
@@ -0,0 +1,291 @@
+package matterclient
+
+import (
+ "strings"
+
+ "github.com/mattermost/mattermost-server/v6/model"
+)
+
+// GetChannels returns all channels we're members off
+func (m *Client) GetChannels() []*model.Channel {
+ m.RLock()
+ defer m.RUnlock()
+
+ var channels []*model.Channel
+ // our primary team channels first
+ channels = append(channels, m.Team.Channels...)
+
+ for _, t := range m.OtherTeams {
+ if t.ID != m.Team.ID {
+ channels = append(channels, t.Channels...)
+ }
+ }
+
+ return channels
+}
+
+func (m *Client) GetChannelHeader(channelID string) string {
+ m.RLock()
+ defer m.RUnlock()
+
+ for _, t := range m.OtherTeams {
+ for _, channel := range append(t.Channels, t.MoreChannels...) {
+ if channel.Id == channelID {
+ return channel.Header
+ }
+ }
+ }
+
+ return ""
+}
+
+func getNormalisedName(channel *model.Channel) string {
+ if channel.Type == model.ChannelTypeGroup {
+ res := strings.ReplaceAll(channel.DisplayName, ", ", "-")
+ res = strings.ReplaceAll(res, " ", "_")
+
+ return res
+ }
+
+ return channel.Name
+}
+
+func (m *Client) GetChannelID(name string, teamID string) string {
+ m.RLock()
+ defer m.RUnlock()
+
+ if teamID != "" {
+ return m.getChannelIDTeam(name, teamID)
+ }
+
+ for _, t := range m.OtherTeams {
+ for _, channel := range append(t.Channels, t.MoreChannels...) {
+ if getNormalisedName(channel) == name {
+ return channel.Id
+ }
+ }
+ }
+
+ return ""
+}
+
+func (m *Client) getChannelIDTeam(name string, teamID string) string {
+ for _, t := range m.OtherTeams {
+ if t.ID == teamID {
+ for _, channel := range append(t.Channels, t.MoreChannels...) {
+ if getNormalisedName(channel) == name {
+ return channel.Id
+ }
+ }
+ }
+ }
+
+ return ""
+}
+
+func (m *Client) GetChannelName(channelID string) string {
+ m.RLock()
+ defer m.RUnlock()
+
+ for _, t := range m.OtherTeams {
+ if t == nil {
+ continue
+ }
+
+ for _, channel := range append(t.Channels, t.MoreChannels...) {
+ if channel.Id == channelID {
+ return getNormalisedName(channel)
+ }
+ }
+ }
+
+ return ""
+}
+
+func (m *Client) GetChannelTeamID(id string) string {
+ m.RLock()
+ defer m.RUnlock()
+
+ for _, t := range append(m.OtherTeams, m.Team) {
+ for _, channel := range append(t.Channels, t.MoreChannels...) {
+ if channel.Id == id {
+ return channel.TeamId
+ }
+ }
+ }
+
+ return ""
+}
+
+func (m *Client) GetLastViewedAt(channelID string) int64 {
+ m.RLock()
+ defer m.RUnlock()
+
+ for {
+ res, resp, err := m.Client.GetChannelMember(channelID, m.User.Id, "")
+ if err == nil {
+ return res.LastViewedAt
+ }
+
+ if err := m.HandleRatelimit("GetChannelMember", resp); err != nil {
+ return model.GetMillis()
+ }
+ }
+}
+
+// GetMoreChannels returns existing channels where we're not a member off.
+func (m *Client) GetMoreChannels() []*model.Channel {
+ m.RLock()
+ defer m.RUnlock()
+
+ var channels []*model.Channel
+ for _, t := range m.OtherTeams {
+ channels = append(channels, t.MoreChannels...)
+ }
+
+ return channels
+}
+
+// GetTeamFromChannel returns teamId belonging to channel (DM channels have no teamId).
+func (m *Client) GetTeamFromChannel(channelID string) string {
+ m.RLock()
+ defer m.RUnlock()
+
+ var channels []*model.Channel
+
+ for _, t := range m.OtherTeams {
+ channels = append(channels, t.Channels...)
+
+ if t.MoreChannels != nil {
+ channels = append(channels, t.MoreChannels...)
+ }
+
+ for _, c := range channels {
+ if c.Id == channelID {
+ if c.Type == model.ChannelTypeGroup {
+ return "G"
+ }
+
+ return t.ID
+ }
+ }
+
+ channels = nil
+ }
+
+ return ""
+}
+
+func (m *Client) JoinChannel(channelID string) error {
+ m.RLock()
+ defer m.RUnlock()
+
+ for _, c := range m.Team.Channels {
+ if c.Id == channelID {
+ m.logger.Debug("Not joining ", channelID, " already joined.")
+
+ return nil
+ }
+ }
+
+ m.logger.Debug("Joining ", channelID)
+
+ _, _, err := m.Client.AddChannelMember(channelID, m.User.Id)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Client) UpdateChannelsTeam(teamID string) error {
+ var (
+ mmchannels []*model.Channel
+ resp *model.Response
+ err error
+ )
+
+ for {
+ mmchannels, resp, err = m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, false, "")
+ if err == nil {
+ break
+ }
+
+ if err = m.HandleRatelimit("GetChannelsForTeamForUser", resp); err != nil {
+ return err
+ }
+ }
+
+ for idx, t := range m.OtherTeams {
+ if t.ID == teamID {
+ m.Lock()
+ m.OtherTeams[idx].Channels = mmchannels
+ m.Unlock()
+ }
+ }
+
+ for {
+ mmchannels, resp, err = m.Client.GetPublicChannelsForTeam(teamID, 0, 5000, "")
+ if err == nil {
+ break
+ }
+
+ if err := m.HandleRatelimit("GetPublicChannelsForTeam", resp); err != nil {
+ return err
+ }
+ }
+
+ for idx, t := range m.OtherTeams {
+ if t.ID == teamID {
+ m.Lock()
+ m.OtherTeams[idx].MoreChannels = mmchannels
+ m.Unlock()
+ }
+ }
+
+ return nil
+}
+
+func (m *Client) UpdateChannels() error {
+ if err := m.UpdateChannelsTeam(m.Team.ID); err != nil {
+ return err
+ }
+
+ for _, t := range m.OtherTeams {
+ if err := m.UpdateChannelsTeam(t.ID); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (m *Client) UpdateChannelHeader(channelID string, header string) {
+ channel := &model.Channel{Id: channelID, Header: header}
+
+ m.logger.Debugf("updating channelheader %#v, %#v", channelID, header)
+
+ _, _, err := m.Client.UpdateChannel(channel)
+ if err != nil {
+ m.logger.Error(err)
+ }
+}
+
+func (m *Client) UpdateLastViewed(channelID string) error {
+ m.logger.Debugf("posting lastview %#v", channelID)
+
+ view := &model.ChannelView{ChannelId: channelID}
+
+ for {
+ _, resp, err := m.Client.ViewChannel(m.User.Id, view)
+ if err == nil {
+ return nil
+ }
+
+ if err := m.HandleRatelimit("ViewChannel", resp); err != nil {
+ m.logger.Errorf("ChannelView update for %s failed: %s", channelID, err)
+
+ return err
+ }
+ }
+}
diff --git a/vendor/github.com/matterbridge/matterclient/go.mod b/vendor/github.com/matterbridge/matterclient/go.mod
new file mode 100644
index 00000000..3e9e70bc
--- /dev/null
+++ b/vendor/github.com/matterbridge/matterclient/go.mod
@@ -0,0 +1,14 @@
+module github.com/matterbridge/matterclient
+
+go 1.16
+
+require (
+ github.com/gorilla/websocket v1.4.2
+ github.com/hashicorp/golang-lru v0.5.4
+ github.com/jpillora/backoff v1.0.0
+ github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
+ github.com/mattermost/mattermost-server/v6 v6.0.0
+ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
+ github.com/sirupsen/logrus v1.8.1
+ github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
+)
diff --git a/vendor/github.com/matterbridge/matterclient/go.sum b/vendor/github.com/matterbridge/matterclient/go.sum
new file mode 100644
index 00000000..05e82457
--- /dev/null
+++ b/vendor/github.com/matterbridge/matterclient/go.sum
@@ -0,0 +1,1404 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
+cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg=
+cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/spanner v1.9.0/go.mod h1:xvlEn0NZ5v1iJPYsBnUVRDNvccDxsBTEi16pJRKQVws=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+code.sajari.com/docconv v1.1.1-0.20210427001343-7b3472bc323a/go.mod h1:KPNt2zuWplps1W0TpOb6ltHj4Xu+j6h7a+YkqGHrxQE=
+contrib.go.opencensus.io/exporter/ocagent v0.4.9/go.mod h1:ueLzZcP7LPhPulEBukGn4aLh7Mx9YJwpVJ9nL2FYltw=
+dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
+dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
+dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
+git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
+git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
+github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
+github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
+github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
+github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4=
+github.com/JalfResi/justext v0.0.0-20170829062021-c0282dea7198/go.mod h1:0SURuH1rsE8aVWvutuMZghRNrNrYEUzibzJfhEYR8L0=
+github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
+github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74=
+github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
+github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
+github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
+github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk=
+github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/PuerkitoBio/goquery v1.7.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
+github.com/RoaringBitmap/roaring v0.8.0/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
+github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/advancedlogic/GoOse v0.0.0-20191112112754-e742535969c1/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w=
+github.com/advancedlogic/GoOse v0.0.0-20200830213114-1225d531e0ad/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w=
+github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
+github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8=
+github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.19.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.38.67/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
+github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
+github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ=
+github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc=
+github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc=
+github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
+github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
+github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
+github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
+github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY=
+github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg=
+github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw=
+github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
+github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
+github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
+github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
+github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
+github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
+github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
+github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
+github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
+github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
+github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
+github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ=
+github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU=
+github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
+github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
+github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
+github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
+github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
+github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 h1:AQLr//nh20BzN3hIWj2+/Gt3FwSs8Nwo/nz4hMIcLPg=
+github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
+github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
+github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
+github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
+github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
+github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
+github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
+github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
+github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573/go.mod h1:eBvb3i++NHDH4Ugo9qCvMw8t0mTSctaEa5blJbWcNxs=
+github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-asn1-ber/asn1-ber v1.5.3 h1:u7utq56RUFiynqUzgVMFDymapcOtQ/MZkh3H4QYkxag=
+github.com/go-asn1-ber/asn1-ber v1.5.3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
+github.com/go-redis/redis/v8 v8.0.0/go.mod h1:isLoQT/NFSP7V67lyvM9GmdvLdyZ7pEhsXvvyQtnQTo=
+github.com/go-redis/redis/v8 v8.10.0/go.mod h1:vXLTvigok0VtUX0znvbcEW1SOt4OA9CU1ZfnOtKOaiM=
+github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
+github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
+github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
+github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20210621113107-84c6004145de h1:q+tIagAgwJBQPaeOPvis2b+cxfaZ5HNkzoctl7SMxDQ=
+github.com/gopherjs/gopherjs v0.0.0-20210621113107-84c6004145de/go.mod h1:MtKwTfDNYAP5EtbQSMYjTSqvj1aXJKQRASWq3bwaP+g=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
+github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
+github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
+github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
+github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
+github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg=
+github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
+github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
+github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
+github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
+github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvzG7RsPnHus=
+github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
+github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
+github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
+github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
+github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
+github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
+github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
+github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
+github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
+github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
+github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
+github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
+github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
+github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
+github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
+github.com/ledongthuc/pdf v0.0.0-20210621053716-e28cb8259002/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
+github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
+github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
+github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba h1:XleOY4IjAEIcxAh+IFwT5JT5Ze3RHiYz6m+4ZfZ0rc0=
+github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU=
+github.com/mattermost/go-i18n v1.11.0 h1:1hLKqn/ZvhZ80OekjVPGYcCrBfMz+YxNNgqS+beL7zE=
+github.com/mattermost/go-i18n v1.11.0/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
+github.com/mattermost/gorp v1.6.2-0.20210714143452-8b50f5209a7f/go.mod h1:QCQ3U0M9T/BlAdjKFJo0I1oe/YAgbyjNdhU8bpOLafk=
+github.com/mattermost/gosaml2 v0.3.3/go.mod h1:Z429EIOiEi9kbq6yHoApfzlcXpa6dzRDc6pO+Vy2Ksk=
+github.com/mattermost/gziphandler v0.0.1/go.mod h1:CvvZR7sXqhj81V2swXuQY7T04Ccc89u7W7pHNPKev8g=
+github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ0KNm4yZxxFvC1nvRz/gY/Daa35aI=
+github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
+github.com/mattermost/logr v1.0.13 h1:6F/fM3csvH6Oy5sUpJuW7YyZSzZZAhJm5VcgKMxA2P8=
+github.com/mattermost/logr v1.0.13/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
+github.com/mattermost/logr/v2 v2.0.10 h1:i6rJbuX/EkBM9maM8M0eJ3rxB+fsBKNslPvzSlA2w/M=
+github.com/mattermost/logr/v2 v2.0.10/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
+github.com/mattermost/mattermost-server/v6 v6.0.0 h1:iSe5XGow5sKUBe9eKXRLoPoLJaiMYXzQ/uNED4lXLIA=
+github.com/mattermost/mattermost-server/v6 v6.0.0/go.mod h1:+S8CsNEPv1FOl1usaPBQ6Gu9+Sm1Cc9YdU/Qh1YMGVI=
+github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
+github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
+github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
+github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
+github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
+github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
+github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
+github.com/minio/minio-go/v7 v7.0.11 h1:7utSkCtMQPYYB1UB8FR3d0QSiOWE6F/JYXon29imYek=
+github.com/minio/minio-go/v7 v7.0.11/go.mod h1:WoyW+ySKAKjY98B9+7ZbI8z8S3jaxaisdcvj9TGlazA=
+github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
+github.com/muesli/smartcrop v0.2.1-0.20181030220600-548bbf0c0965/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
+github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
+github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
+github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
+github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
+github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
+github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/olivere/elastic v6.2.35+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
+github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/oov/psd v0.0.0-20210618170533-9fb823ddb631/go.mod h1:GHI1bnmAcbp96z6LNfBJvtrjxhaXGkbsk967utPlvL8=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
+github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
+github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
+github.com/otiai10/gosseract/v2 v2.2.4/go.mod h1:ahOp/kHojnOMGv1RaUnR0jwY5JVa6BYKhYAS8nbMLSo=
+github.com/otiai10/gosseract/v2 v2.3.1/go.mod h1:2ZOGgdTIXQzCS5f+N1HkcXRgDX6K3ZoYe3Yvo++cpp4=
+github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
+github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
+github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/reflog/dateconstraints v0.2.1/go.mod h1:Ax8AxTBcJc3E/oVS2hd2j7RDM/5MDtuPwuR7lIHtPLo=
+github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
+github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/rudderlabs/analytics-go v3.3.1+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30=
+github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
+github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
+github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
+github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
+github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
+github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
+github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
+github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
+github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
+github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
+github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
+github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
+github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
+github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
+github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
+github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
+github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
+github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
+github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
+github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
+github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0=
+github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
+github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/splitio/go-client/v6 v6.1.0/go.mod h1:CEGAEFT99Fwb32ZIRcnZoXTMXddtB6IIpTmt3RP8mnM=
+github.com/splitio/go-split-commons/v3 v3.1.0/go.mod h1:29NCy20oAS4ZMy4qkwTd6277eieVDonx4V/aeDU/wUQ=
+github.com/splitio/go-toolkit/v4 v4.2.0/go.mod h1:EdIHN0yzB1GTXDYQc0KdKvnjkO/jfUM2YqHVYfhD3Wo=
+github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
+github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc=
+github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
+github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg=
+github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
+github.com/throttled/throttled v2.2.5+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos=
+github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
+github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
+github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II=
+github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
+github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
+github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
+github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
+github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
+github.com/wiggin77/cfg v1.0.2/go.mod h1:b3gotba2e5bXTqTW48DwIFoLc+4lWKP7WPi/CdvZ4aE=
+github.com/wiggin77/merror v1.0.2/go.mod h1:uQTcIU0Z6jRK4OwqganPYerzQxSFJ4GSHM3aurxxQpg=
+github.com/wiggin77/merror v1.0.3 h1:8+ZHV+aSnJoYghE3EUThl15C6rvF2TYRSvOSBjdmNR8=
+github.com/wiggin77/merror v1.0.3/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0=
+github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=
+github.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=
+github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
+github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
+github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
+github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
+github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
+github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
+github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
+github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
+github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
+github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
+go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
+go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
+go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
+go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
+go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
+go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo=
+go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
+go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc=
+go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
+go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
+go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
+golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
+golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
+google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
+google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
+google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190321212433-e79c0c59cdb5/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
+gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
+modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
+modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
+modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
+modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
+modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
+modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
+modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
+modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
+modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
+modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
+modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
+sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
+willnorris.com/go/gifresize v1.0.0/go.mod h1:eBM8gogBGCcaH603vxSpnfjwXIpq6nmnj/jauBDKtAk=
+willnorris.com/go/imageproxy v0.10.0/go.mod h1:2tWdKRneln3E9X/zwH1RINpQAQWPeUiNynZ7UQ9OROk=
diff --git a/vendor/github.com/matterbridge/matterclient/matterclient.go b/vendor/github.com/matterbridge/matterclient/matterclient.go
new file mode 100644
index 00000000..a32219fe
--- /dev/null
+++ b/vendor/github.com/matterbridge/matterclient/matterclient.go
@@ -0,0 +1,743 @@
+package matterclient
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/http/cookiejar"
+ "net/url"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/gorilla/websocket"
+ lru "github.com/hashicorp/golang-lru"
+ "github.com/jpillora/backoff"
+ prefixed "github.com/matterbridge/logrus-prefixed-formatter"
+ "github.com/mattermost/mattermost-server/v6/model"
+ "github.com/sirupsen/logrus"
+)
+
+type Credentials struct {
+ Login string
+ Team string
+ Pass string
+ Token string
+ CookieToken bool
+ Server string
+ NoTLS bool
+ SkipTLSVerify bool
+ SkipVersionCheck bool
+ MFAToken string
+}
+
+type Team struct {
+ Team *model.Team
+ ID string
+ Channels []*model.Channel
+ MoreChannels []*model.Channel
+ Users map[string]*model.User
+}
+
+type Message struct {
+ Raw *model.WebSocketEvent
+ Post *model.Post
+ Team string
+ Channel string
+ Username string
+ Text string
+ Type string
+ UserID string
+}
+
+type Client struct {
+ sync.RWMutex
+ *Credentials
+
+ Team *Team
+ OtherTeams []*Team
+ Client *model.Client4
+ User *model.User
+ Users map[string]*model.User
+ MessageChan chan *Message
+ WsClient *model.WebSocketClient
+ AntiIdle bool
+ AntiIdleChan string
+ AntiIdleIntvl int
+ WsQuit bool
+ WsConnected bool
+ OnWsConnect func()
+ reconnectBusy bool
+
+ logger *logrus.Entry
+ rootLogger *logrus.Logger
+ lruCache *lru.Cache
+ aliveChan chan bool
+ loginCancel context.CancelFunc
+ lastPong time.Time
+}
+
+func New(login string, pass string, team string, server string, mfatoken string) *Client {
+ rootLogger := logrus.New()
+ rootLogger.SetFormatter(&prefixed.TextFormatter{
+ PrefixPadding: 13,
+ DisableColors: true,
+ FullTimestamp: true,
+ })
+
+ cred := &Credentials{
+ Login: login,
+ Pass: pass,
+ Team: team,
+ Server: server,
+ MFAToken: mfatoken,
+ }
+
+ cache, _ := lru.New(500)
+
+ return &Client{
+ Credentials: cred,
+ MessageChan: make(chan *Message, 100),
+ Users: make(map[string]*model.User),
+ rootLogger: rootLogger,
+ lruCache: cache,
+ logger: rootLogger.WithFields(logrus.Fields{"prefix": "matterclient"}),
+ aliveChan: make(chan bool),
+ }
+}
+
+// Login tries to connect the client with the loging details with which it was initialized.
+func (m *Client) Login() error {
+ // check if this is a first connect or a reconnection
+ firstConnection := true
+ if m.WsConnected {
+ firstConnection = false
+ }
+
+ m.WsConnected = false
+ if m.WsQuit {
+ return nil
+ }
+
+ b := &backoff.Backoff{
+ Min: time.Second,
+ Max: 5 * time.Minute,
+ Jitter: true,
+ }
+
+ // do initialization setup
+ if err := m.initClient(b); err != nil {
+ return err
+ }
+
+ if err := m.doLogin(firstConnection, b); err != nil {
+ return err
+ }
+
+ if err := m.initUser(); err != nil {
+ return err
+ }
+
+ if m.Team == nil {
+ validTeamNames := make([]string, len(m.OtherTeams))
+ for i, t := range m.OtherTeams {
+ validTeamNames[i] = t.Team.Name
+ }
+
+ return fmt.Errorf("Team '%s' not found in %v", m.Credentials.Team, validTeamNames)
+ }
+
+ // connect websocket
+ m.wsConnect()
+
+ ctx, loginCancel := context.WithCancel(context.Background())
+ m.loginCancel = loginCancel
+
+ m.logger.Debug("starting wsreceiver")
+
+ go m.WsReceiver(ctx)
+
+ if m.OnWsConnect != nil {
+ m.logger.Debug("executing OnWsConnect()")
+
+ go m.OnWsConnect()
+ }
+
+ go m.checkConnection(ctx)
+
+ if m.AntiIdle {
+ if m.AntiIdleChan == "" {
+ // do anti idle on town-square, every installation should have this channel
+ m.AntiIdleChan = "town-square"
+ }
+
+ channels := m.GetChannels()
+ for _, channel := range channels {
+ if channel.Name == m.AntiIdleChan {
+ go m.antiIdle(ctx, channel.Id, m.AntiIdleIntvl)
+
+ continue
+ }
+ }
+ }
+
+ return nil
+}
+
+func (m *Client) Reconnect() {
+ if m.reconnectBusy {
+ return
+ }
+
+ m.reconnectBusy = true
+
+ m.logger.Info("reconnect: logout")
+ m.reconnectLogout()
+
+ for {
+ m.logger.Info("reconnect: login")
+
+ err := m.Login()
+ if err != nil {
+ m.logger.Errorf("reconnect: login failed: %s, retrying in 10 seconds", err)
+ time.Sleep(time.Second * 10)
+
+ continue
+ }
+
+ break
+ }
+
+ m.logger.Info("reconnect successful")
+
+ m.reconnectBusy = false
+}
+
+func (m *Client) initClient(b *backoff.Backoff) error {
+ uriScheme := "https://"
+ if m.NoTLS {
+ uriScheme = "http://"
+ }
+ // login to mattermost
+ m.Client = model.NewAPIv4Client(uriScheme + m.Credentials.Server)
+ m.Client.HTTPClient.Transport = &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: m.SkipTLSVerify, //nolint:gosec
+ },
+ Proxy: http.ProxyFromEnvironment,
+ }
+ m.Client.HTTPClient.Timeout = time.Second * 10
+
+ // handle MMAUTHTOKEN and personal token
+ if err := m.handleLoginToken(); err != nil {
+ return err
+ }
+
+ // check if server alive, retry until
+ if err := m.serverAlive(b); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Client) handleLoginToken() error {
+ switch {
+ case strings.Contains(m.Credentials.Pass, model.SessionCookieToken):
+ token := strings.Split(m.Credentials.Pass, model.SessionCookieToken+"=")
+ if len(token) != 2 {
+ return errors.New("incorrect MMAUTHTOKEN. valid input is MMAUTHTOKEN=yourtoken")
+ }
+
+ m.Credentials.Token = token[1]
+ m.Credentials.CookieToken = true
+ case strings.Contains(m.Credentials.Pass, "token="):
+ token := strings.Split(m.Credentials.Pass, "token=")
+ if len(token) != 2 {
+ return errors.New("incorrect personal token. valid input is token=yourtoken")
+ }
+
+ m.Credentials.Token = token[1]
+ }
+
+ return nil
+}
+
+func (m *Client) serverAlive(b *backoff.Backoff) error {
+ defer b.Reset()
+
+ for {
+ d := b.Duration()
+ // bogus call to get the serverversion
+ resp, err := m.Client.Logout()
+ if err != nil {
+ return err
+ }
+
+ if resp.ServerVersion == "" {
+ m.logger.Debugf("Server not up yet, reconnecting in %s", d)
+ time.Sleep(d)
+ } else {
+ m.logger.Infof("Found version %s", resp.ServerVersion)
+
+ return nil
+ }
+ }
+}
+
+// initialize user and teams
+// nolint:funlen
+func (m *Client) initUser() error {
+ m.Lock()
+ defer m.Unlock()
+ // we only load all team data on initial login.
+ // all other updates are for channels from our (primary) team only.
+ teams, _, err := m.Client.GetTeamsForUser(m.User.Id, "")
+ if err != nil {
+ return err
+ }
+
+ for _, team := range teams {
+ idx := 0
+ max := 200
+ usermap := make(map[string]*model.User)
+
+ mmusers, _, err := m.Client.GetUsersInTeam(team.Id, idx, max, "")
+ if err != nil {
+ return err
+ }
+
+ for len(mmusers) > 0 {
+ for _, user := range mmusers {
+ usermap[user.Id] = user
+ }
+
+ mmusers, _, err = m.Client.GetUsersInTeam(team.Id, idx, max, "")
+ if err != nil {
+ return err
+ }
+
+ idx++
+
+ time.Sleep(time.Millisecond * 200)
+ }
+
+ m.logger.Infof("found %d users in team %s", len(usermap), team.Name)
+
+ t := &Team{
+ Team: team,
+ Users: usermap,
+ ID: team.Id,
+ }
+
+ mmchannels, _, err := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, false, "")
+ if err != nil {
+ return err
+ }
+
+ t.Channels = mmchannels
+
+ mmchannels, _, err = m.Client.GetPublicChannelsForTeam(team.Id, 0, 5000, "")
+ if err != nil {
+ return err
+ }
+
+ t.MoreChannels = mmchannels
+ m.OtherTeams = append(m.OtherTeams, t)
+
+ if team.Name == m.Credentials.Team {
+ m.Team = t
+ m.logger.Debugf("initUser(): found our team %s (id: %s)", team.Name, team.Id)
+ }
+ // add all users
+ for k, v := range t.Users {
+ m.Users[k] = v
+ }
+ }
+
+ return nil
+}
+
+func (m *Client) doLogin(firstConnection bool, b *backoff.Backoff) error {
+ var (
+ logmsg = "trying login"
+ err error
+ user *model.User
+ )
+
+ for {
+ m.logger.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
+
+ switch {
+ case m.Credentials.Token != "":
+ user, _, err = m.doLoginToken()
+ if err != nil {
+ return err
+ }
+ case m.Credentials.MFAToken != "":
+ user, _, err = m.Client.LoginWithMFA(m.Credentials.Login, m.Credentials.Pass, m.Credentials.MFAToken)
+ default:
+ user, _, err = m.Client.Login(m.Credentials.Login, m.Credentials.Pass)
+ }
+
+ if err != nil {
+ d := b.Duration()
+
+ m.logger.Debug(err)
+
+ if firstConnection {
+ return err
+ }
+
+ m.logger.Debugf("LOGIN: %s, reconnecting in %s", err, d)
+
+ time.Sleep(d)
+
+ logmsg = "retrying login"
+
+ continue
+ }
+
+ m.User = user
+
+ break
+ }
+ // reset timer
+ b.Reset()
+
+ return nil
+}
+
+func (m *Client) doLoginToken() (*model.User, *model.Response, error) {
+ var (
+ resp *model.Response
+ logmsg = "trying login"
+ user *model.User
+ err error
+ )
+
+ m.Client.AuthType = model.HeaderBearer
+ m.Client.AuthToken = m.Credentials.Token
+
+ if m.Credentials.CookieToken {
+ m.logger.Debugf(logmsg + " with cookie (MMAUTH) token")
+ m.Client.HTTPClient.Jar = m.createCookieJar(m.Credentials.Token)
+ } else {
+ m.logger.Debugf(logmsg + " with personal token")
+ }
+
+ user, resp, err = m.Client.GetMe("")
+ if err != nil {
+ return user, resp, err
+ }
+
+ if user == nil {
+ m.logger.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
+
+ return user, resp, errors.New("invalid token")
+ }
+
+ return user, resp, nil
+}
+
+func (m *Client) createCookieJar(token string) *cookiejar.Jar {
+ var cookies []*http.Cookie
+
+ jar, _ := cookiejar.New(nil)
+
+ firstCookie := &http.Cookie{
+ Name: "MMAUTHTOKEN",
+ Value: token,
+ Path: "/",
+ Domain: m.Credentials.Server,
+ }
+
+ cookies = append(cookies, firstCookie)
+ cookieURL, _ := url.Parse("https://" + m.Credentials.Server)
+
+ jar.SetCookies(cookieURL, cookies)
+
+ return jar
+}
+
+func (m *Client) wsConnect() {
+ b := &backoff.Backoff{
+ Min: time.Second,
+ Max: 5 * time.Minute,
+ Jitter: true,
+ }
+
+ m.WsConnected = false
+ wsScheme := "wss://"
+
+ if m.NoTLS {
+ wsScheme = "ws://"
+ }
+
+ // setup websocket connection
+ wsurl := wsScheme + m.Credentials.Server
+ // + model.API_URL_SUFFIX_V4
+ // + "/websocket"
+ header := http.Header{}
+ header.Set(model.HeaderAuth, "BEARER "+m.Client.AuthToken)
+
+ m.logger.Debugf("WsClient: making connection: %s", wsurl)
+
+ for {
+ wsDialer := &websocket.Dialer{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: m.SkipTLSVerify, //nolint:gosec
+ },
+ Proxy: http.ProxyFromEnvironment,
+ }
+
+ var err error
+
+ m.WsClient, err = model.NewWebSocketClientWithDialer(wsDialer, wsurl, m.Client.AuthToken)
+ if err != nil {
+ d := b.Duration()
+
+ m.logger.Debugf("WSS: %s, reconnecting in %s", err, d)
+
+ time.Sleep(d)
+
+ continue
+ }
+
+ break
+ }
+
+ m.WsClient.Listen()
+
+ m.lastPong = time.Now()
+
+ m.logger.Debug("WsClient: connected")
+
+ // only start to parse WS messages when login is completely done
+ m.WsConnected = true
+}
+
+func (m *Client) doCheckAlive() error {
+ if _, _, err := m.Client.GetMe(""); err != nil {
+ return err
+ }
+
+ if m.reconnectBusy {
+ return nil
+ }
+
+ if m.WsClient.ListenError == nil {
+ m.WsClient.SendMessage("ping", nil)
+ } else {
+ m.logger.Errorf("got a listen error: %#v", m.WsClient.ListenError)
+
+ return m.WsClient.ListenError
+ }
+
+ if time.Since(m.lastPong) > 90*time.Second {
+ return errors.New("no pong received in 90 seconds")
+ }
+
+ return nil
+}
+
+func (m *Client) checkAlive(ctx context.Context) {
+ ticker := time.NewTicker(time.Second * 45)
+
+ for {
+ select {
+ case <-ctx.Done():
+ m.logger.Debugf("checkAlive: ctx.Done() triggered")
+
+ return
+ case <-ticker.C:
+ // check if session still is valid
+ err := m.doCheckAlive()
+ if err != nil {
+ m.logger.Errorf("connection not alive: %s", err)
+ m.aliveChan <- false
+ }
+
+ m.aliveChan <- true
+ }
+ }
+}
+
+func (m *Client) checkConnection(ctx context.Context) {
+ go m.checkAlive(ctx)
+
+ for {
+ select {
+ case alive := <-m.aliveChan:
+ if !alive {
+ time.Sleep(time.Second * 10)
+
+ if m.doCheckAlive() != nil {
+ m.Reconnect()
+ }
+ }
+ case <-ctx.Done():
+ m.logger.Debug("checkConnection: ctx.Done() triggered, exiting")
+
+ return
+ }
+ }
+}
+
+// WsReceiver implements the core loop that manages the connection to the chat server. In
+// case of a disconnect it will try to reconnect. A call to this method is blocking until
+// the 'WsQuite' field of the MMClient object is set to 'true'.
+func (m *Client) WsReceiver(ctx context.Context) {
+ m.logger.Debug("starting WsReceiver")
+
+ ticker := time.NewTicker(time.Second * 10)
+
+ for {
+ select {
+ case event := <-m.WsClient.EventChannel:
+ if event == nil {
+ return
+ }
+
+ if !event.IsValid() {
+ continue
+ }
+
+ m.logger.Debugf("WsReceiver event: %#v", event)
+
+ msg := &Message{
+ Raw: event,
+ Team: m.Credentials.Team,
+ }
+
+ m.parseMessage(msg)
+
+ m.MessageChan <- msg
+ case response := <-m.WsClient.ResponseChannel:
+ if response == nil || !response.IsValid() {
+ continue
+ }
+
+ m.logger.Debugf("WsReceiver response: %#v", response)
+
+ if text, ok := response.Data["text"].(string); ok {
+ if text == "pong" {
+ m.lastPong = time.Now()
+ }
+ }
+
+ m.parseResponse(response)
+ case <-m.WsClient.PingTimeoutChannel:
+ m.logger.Error("got a ping timeout")
+ m.Reconnect()
+
+ return
+ case <-ticker.C:
+ if m.WsClient.ListenError != nil {
+ m.logger.Errorf("%#v", m.WsClient.ListenError)
+ m.Reconnect()
+
+ return
+ }
+ case <-ctx.Done():
+ m.logger.Debugf("wsReceiver: ctx.Done() triggered")
+
+ return
+ }
+ }
+}
+
+// Logout disconnects the client from the chat server.
+func (m *Client) reconnectLogout() error {
+ err := m.Logout()
+ m.WsQuit = false
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Logout disconnects the client from the chat server.
+func (m *Client) Logout() error {
+ m.logger.Debug("logout running loginCancel to exit goroutines")
+ m.loginCancel()
+
+ m.logger.Debugf("logout as %s (team: %s) on %s", m.Credentials.Login, m.Credentials.Team, m.Credentials.Server)
+ m.WsQuit = true
+ // close the websocket
+ m.logger.Debug("closing websocket")
+ m.WsClient.Close()
+
+ if strings.Contains(m.Credentials.Pass, model.SessionCookieToken) {
+ m.logger.Debug("Not invalidating session in logout, credential is a token")
+
+ return nil
+ }
+
+ // actually log out
+ m.logger.Debug("running m.Client.Logout")
+
+ if _, err := m.Client.Logout(); err != nil {
+ return err
+ }
+
+ m.logger.Debug("exiting Logout()")
+
+ return nil
+}
+
+// SetLogLevel tries to parse the specified level and if successful sets
+// the log level accordingly. Accepted levels are: 'debug', 'info', 'warn',
+// 'error', 'fatal' and 'panic'.
+func (m *Client) SetLogLevel(level string) {
+ l, err := logrus.ParseLevel(level)
+ if err != nil {
+ m.logger.Warnf("Failed to parse specified log-level '%s': %#v", level, err)
+ } else {
+ m.rootLogger.SetLevel(l)
+ }
+}
+
+func (m *Client) HandleRatelimit(name string, resp *model.Response) error {
+ if resp.StatusCode != 429 {
+ return fmt.Errorf("StatusCode error: %d", resp.StatusCode)
+ }
+
+ waitTime, err := strconv.Atoi(resp.Header.Get("X-RateLimit-Reset"))
+ if err != nil {
+ return err
+ }
+
+ m.logger.Warnf("Ratelimited on %s for %d", name, waitTime)
+
+ time.Sleep(time.Duration(waitTime) * time.Second)
+
+ return nil
+}
+
+func (m *Client) antiIdle(ctx context.Context, channelID string, interval int) {
+ if interval == 0 {
+ interval = 60
+ }
+
+ m.logger.Debugf("starting antiIdle for %s every %d secs", channelID, interval)
+ ticker := time.NewTicker(time.Second * time.Duration(interval))
+
+ for {
+ select {
+ case <-ctx.Done():
+ m.logger.Debugf("antiIlde: ctx.Done() triggered, exiting for %s", channelID)
+
+ return
+ case <-ticker.C:
+ m.logger.Tracef("antiIdle %s", channelID)
+
+ m.UpdateLastViewed(channelID)
+ }
+ }
+}
diff --git a/vendor/github.com/matterbridge/matterclient/messages.go b/vendor/github.com/matterbridge/matterclient/messages.go
new file mode 100644
index 00000000..dc5d5380
--- /dev/null
+++ b/vendor/github.com/matterbridge/matterclient/messages.go
@@ -0,0 +1,280 @@
+package matterclient
+
+import (
+ "crypto/md5"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/mattermost/mattermost-server/v6/model"
+)
+
+func (m *Client) parseResponse(rmsg *model.WebSocketResponse) {
+ m.logger.Debugf("getting response: %#v", rmsg)
+}
+
+func (m *Client) DeleteMessage(postID string) error {
+ _, err := m.Client.DeletePost(postID)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Client) EditMessage(postID string, text string) (string, error) {
+ post := &model.Post{Message: text, Id: postID}
+
+ res, _, err := m.Client.UpdatePost(postID, post)
+ if err != nil {
+ return "", err
+ }
+
+ return res.Id, nil
+}
+
+func (m *Client) GetFileLinks(filenames []string) []string {
+ uriScheme := "https://"
+ if m.NoTLS {
+ uriScheme = "http://"
+ }
+
+ var output []string
+
+ for _, f := range filenames {
+ res, _, err := m.Client.GetFileLink(f)
+ if err != nil {
+ // public links is probably disabled, create the link ourselves
+ output = append(output, uriScheme+m.Credentials.Server+model.APIURLSuffix+"/files/"+f)
+
+ continue
+ }
+
+ output = append(output, res)
+ }
+
+ return output
+}
+
+func (m *Client) GetPosts(channelID string, limit int) *model.PostList {
+ for {
+ res, resp, err := m.Client.GetPostsForChannel(channelID, 0, limit, "", true)
+ if err == nil {
+ return res
+ }
+
+ if err := m.HandleRatelimit("GetPostsForChannel", resp); err != nil {
+ return nil
+ }
+ }
+}
+
+func (m *Client) GetPostsSince(channelID string, time int64) *model.PostList {
+ for {
+ res, resp, err := m.Client.GetPostsSince(channelID, time, true)
+ if err == nil {
+ return res
+ }
+
+ if err := m.HandleRatelimit("GetPostsSince", resp); err != nil {
+ return nil
+ }
+ }
+}
+
+func (m *Client) GetPublicLink(filename string) string {
+ res, _, err := m.Client.GetFileLink(filename)
+ if err != nil {
+ return ""
+ }
+
+ return res
+}
+
+func (m *Client) GetPublicLinks(filenames []string) []string {
+ var output []string
+
+ for _, f := range filenames {
+ res, _, err := m.Client.GetFileLink(f)
+ if err != nil {
+ continue
+ }
+
+ output = append(output, res)
+ }
+
+ return output
+}
+
+func (m *Client) PostMessage(channelID string, text string, rootID string) (string, error) {
+ post := &model.Post{
+ ChannelId: channelID,
+ Message: text,
+ RootId: rootID,
+ }
+
+ for {
+ res, resp, err := m.Client.CreatePost(post)
+ if err == nil {
+ return res.Id, nil
+ }
+
+ if err := m.HandleRatelimit("CreatePost", resp); err != nil {
+ return "", err
+ }
+ }
+}
+
+func (m *Client) PostMessageWithFiles(channelID string, text string, rootID string, fileIds []string) (string, error) {
+ post := &model.Post{
+ ChannelId: channelID,
+ Message: text,
+ RootId: rootID,
+ FileIds: fileIds,
+ }
+
+ for {
+ res, resp, err := m.Client.CreatePost(post)
+ if err == nil {
+ return res.Id, nil
+ }
+
+ if err := m.HandleRatelimit("CreatePost", resp); err != nil {
+ return "", err
+ }
+ }
+}
+
+func (m *Client) SearchPosts(query string) *model.PostList {
+ res, _, err := m.Client.SearchPosts(m.Team.ID, query, false)
+ if err != nil {
+ return nil
+ }
+
+ return res
+}
+
+// SendDirectMessage sends a direct message to specified user
+func (m *Client) SendDirectMessage(toUserID string, msg string, rootID string) error {
+ return m.SendDirectMessageProps(toUserID, msg, rootID, nil)
+}
+
+func (m *Client) SendDirectMessageProps(toUserID string, msg string, rootID string, props map[string]interface{}) error {
+ m.logger.Debugf("SendDirectMessage to %s, msg %s", toUserID, msg)
+
+ for {
+ // create DM channel (only happens on first message)
+ _, resp, err := m.Client.CreateDirectChannel(m.User.Id, toUserID)
+ if err == nil {
+ break
+ }
+
+ if err := m.HandleRatelimit("CreateDirectChannel", resp); err != nil {
+ m.logger.Debugf("SendDirectMessage to %#v failed: %s", toUserID, err)
+
+ return err
+ }
+ }
+
+ channelName := model.GetDMNameFromIds(toUserID, m.User.Id)
+
+ // update our channels
+ if err := m.UpdateChannels(); err != nil {
+ m.logger.Errorf("failed to update channels: %#v", err)
+ }
+
+ // build & send the message
+ msg = strings.ReplaceAll(msg, "\r", "")
+ post := &model.Post{
+ ChannelId: m.GetChannelID(channelName, m.Team.ID),
+ Message: msg,
+ RootId: rootID,
+ }
+
+ post.SetProps(props)
+
+ for {
+ _, resp, err := m.Client.CreatePost(post)
+ if err == nil {
+ return nil
+ }
+
+ if err := m.HandleRatelimit("CreatePost", resp); err != nil {
+ return err
+ }
+ }
+}
+
+func (m *Client) UploadFile(data []byte, channelID string, filename string) (string, error) {
+ f, _, err := m.Client.UploadFile(data, channelID, filename)
+ if err != nil {
+ return "", err
+ }
+
+ return f.FileInfos[0].Id, nil
+}
+
+func (m *Client) parseActionPost(rmsg *Message) {
+ // add post to cache, if it already exists don't relay this again.
+ // this should fix reposts
+ if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.GetData()["post"].(string)), true); ok {
+ m.logger.Debugf("message %#v in cache, not processing again", rmsg.Raw.GetData()["post"].(string))
+ rmsg.Text = ""
+
+ return
+ }
+
+ var data model.Post
+ if err := json.NewDecoder(strings.NewReader(rmsg.Raw.GetData()["post"].(string))).Decode(&data); err != nil {
+ return
+ }
+ // we don't have the user, refresh the userlist
+ if m.GetUser(data.UserId) == nil {
+ m.logger.Infof("User '%v' is not known, ignoring message '%#v'",
+ data.UserId, data)
+ return
+ }
+
+ rmsg.Username = m.GetUserName(data.UserId)
+ rmsg.Channel = m.GetChannelName(data.ChannelId)
+ rmsg.UserID = data.UserId
+ rmsg.Type = data.Type
+ teamid, _ := rmsg.Raw.GetData()["team_id"].(string)
+ // edit messsages have no team_id for some reason
+ if teamid == "" {
+ // we can find the team_id from the channelid
+ teamid = m.GetChannelTeamID(data.ChannelId)
+ rmsg.Raw.GetData()["team_id"] = teamid
+ }
+
+ if teamid != "" {
+ rmsg.Team = m.GetTeamName(teamid)
+ }
+ // direct message
+ if rmsg.Raw.GetData()["channel_type"] == "D" {
+ rmsg.Channel = m.GetUser(data.UserId).Username
+ }
+
+ rmsg.Text = data.Message
+ rmsg.Post = &data
+}
+
+func (m *Client) parseMessage(rmsg *Message) {
+ switch rmsg.Raw.EventType() {
+ case model.WebsocketEventPosted, model.WebsocketEventPostEdited, model.WebsocketEventPostDeleted:
+ m.parseActionPost(rmsg)
+ case "user_updated":
+ user := rmsg.Raw.GetData()["user"].(map[string]interface{})
+ if _, ok := user["id"].(string); ok {
+ m.UpdateUser(user["id"].(string))
+ }
+ case "group_added":
+ if err := m.UpdateChannels(); err != nil {
+ m.logger.Errorf("failed to update channels: %#v", err)
+ }
+ }
+}
+
+func digestString(s string) string {
+ return fmt.Sprintf("%x", md5.Sum([]byte(s))) //nolint:gosec
+}
diff --git a/vendor/github.com/matterbridge/matterclient/users.go b/vendor/github.com/matterbridge/matterclient/users.go
new file mode 100644
index 00000000..30ea51a2
--- /dev/null
+++ b/vendor/github.com/matterbridge/matterclient/users.go
@@ -0,0 +1,212 @@
+package matterclient
+
+import (
+ "github.com/mattermost/mattermost-server/v6/model"
+)
+
+func (m *Client) GetNickName(userID string) string {
+ if user := m.GetUser(userID); user != nil {
+ return user.Nickname
+ }
+
+ return ""
+}
+
+func (m *Client) GetStatus(userID string) string {
+ res, _, err := m.Client.GetUserStatus(userID, "")
+ if err != nil {
+ return ""
+ }
+
+ if res.Status == model.StatusAway {
+ return "away"
+ }
+
+ if res.Status == model.StatusOnline {
+ return "online"
+ }
+
+ return "offline"
+}
+
+func (m *Client) GetStatuses() map[string]string {
+ var ids []string
+
+ statuses := make(map[string]string)
+
+ for id := range m.Users {
+ ids = append(ids, id)
+ }
+
+ res, _, err := m.Client.GetUsersStatusesByIds(ids)
+ if err != nil {
+ return statuses
+ }
+
+ for _, status := range res {
+ statuses[status.UserId] = "offline"
+ if status.Status == model.StatusAway {
+ statuses[status.UserId] = "away"
+ }
+
+ if status.Status == model.StatusOnline {
+ statuses[status.UserId] = "online"
+ }
+ }
+
+ return statuses
+}
+
+func (m *Client) GetTeamID() string {
+ return m.Team.ID
+}
+
+// GetTeamName returns the name of the specified teamId
+func (m *Client) GetTeamName(teamID string) string {
+ m.RLock()
+ defer m.RUnlock()
+
+ for _, t := range m.OtherTeams {
+ if t.ID == teamID {
+ return t.Team.Name
+ }
+ }
+
+ return ""
+}
+
+func (m *Client) GetUser(userID string) *model.User {
+ m.Lock()
+ defer m.Unlock()
+
+ _, ok := m.Users[userID]
+ if !ok {
+ res, _, err := m.Client.GetUser(userID, "")
+ if err != nil {
+ return nil
+ }
+
+ m.Users[userID] = res
+ }
+
+ return m.Users[userID]
+}
+
+func (m *Client) GetUserName(userID string) string {
+ if user := m.GetUser(userID); user != nil {
+ return user.Username
+ }
+
+ return ""
+}
+
+func (m *Client) GetUsers() map[string]*model.User {
+ users := make(map[string]*model.User)
+
+ m.RLock()
+ defer m.RUnlock()
+
+ for k, v := range m.Users {
+ users[k] = v
+ }
+
+ return users
+}
+
+func (m *Client) UpdateUsers() error {
+ idx := 0
+ max := 200
+
+ var (
+ mmusers []*model.User
+ resp *model.Response
+ err error
+ )
+
+ for {
+ mmusers, resp, err = m.Client.GetUsers(idx, max, "")
+ if err == nil {
+ break
+ }
+
+ if err = m.HandleRatelimit("GetUsers", resp); err != nil {
+ return err
+ }
+ }
+
+ for len(mmusers) > 0 {
+ m.Lock()
+
+ for _, user := range mmusers {
+ m.Users[user.Id] = user
+ }
+
+ m.Unlock()
+
+ for {
+ mmusers, resp, err = m.Client.GetUsers(idx, max, "")
+ if err == nil {
+ idx++
+
+ break
+ }
+
+ if err := m.HandleRatelimit("GetUsers", resp); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (m *Client) UpdateUserNick(nick string) error {
+ user := m.User
+ user.Nickname = nick
+
+ _, _, err := m.Client.UpdateUser(user)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Client) UsernamesInChannel(channelID string) []string {
+ res, _, err := m.Client.GetChannelMembers(channelID, 0, 50000, "")
+ if err != nil {
+ m.logger.Errorf("UsernamesInChannel(%s) failed: %s", channelID, err)
+
+ return []string{}
+ }
+
+ allusers := m.GetUsers()
+ result := []string{}
+
+ for _, member := range res {
+ result = append(result, allusers[member.UserId].Nickname)
+ }
+
+ return result
+}
+
+func (m *Client) UpdateStatus(userID string, status string) error {
+ _, _, err := m.Client.UpdateUserStatus(userID, &model.Status{Status: status})
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *Client) UpdateUser(userID string) {
+ m.Lock()
+ defer m.Unlock()
+
+ res, _, err := m.Client.GetUser(userID, "")
+ if err != nil {
+ return
+ }
+
+ m.Users[userID] = res
+}
diff --git a/vendor/github.com/mattermost/ldap/conn.go b/vendor/github.com/mattermost/ldap/conn.go
index 5b7ac478..9e55a5d0 100644
--- a/vendor/github.com/mattermost/ldap/conn.go
+++ b/vendor/github.com/mattermost/ldap/conn.go
@@ -120,7 +120,6 @@ func Dial(network, addr string) (*Conn, error) {
return nil, NewError(ErrorNetwork, err)
}
conn := NewConn(c, false)
- conn.Start()
return conn, nil
}
@@ -132,7 +131,6 @@ func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
return nil, NewError(ErrorNetwork, err)
}
conn := NewConn(c, true)
- conn.Start()
return conn, nil
}
diff --git a/vendor/github.com/mattermost/logr/v2/.gitignore b/vendor/github.com/mattermost/logr/v2/.gitignore
new file mode 100644
index 00000000..bac5e1c1
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/.gitignore
@@ -0,0 +1,37 @@
+# Binaries for programs and plugins
+*.exe
+*.dll
+*.so
+*.dylib
+debug
+dynip
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Output of profiler
+*.prof
+
+# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
+.glide/
+
+# IntelliJ config
+.idea
+
+# log files
+*.log
+
+# transient directories
+vendor
+output
+build
+app
+logs
+
+# test apps
+test/cmd/testapp1/testapp1
+test/cmd/simple/simple
+test/cmd/gelf/gelf
diff --git a/vendor/github.com/mattermost/logr/v2/.travis.yml b/vendor/github.com/mattermost/logr/v2/.travis.yml
new file mode 100644
index 00000000..e6c7caf1
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/.travis.yml
@@ -0,0 +1,4 @@
+language: go
+sudo: false
+go:
+ - 1.x \ No newline at end of file
diff --git a/vendor/github.com/mattermost/logr/v2/LICENSE b/vendor/github.com/mattermost/logr/v2/LICENSE
new file mode 100644
index 00000000..3bea6788
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 wiggin77
+
+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/logr/v2/README.md b/vendor/github.com/mattermost/logr/v2/README.md
new file mode 100644
index 00000000..9ee0f17c
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/README.md
@@ -0,0 +1,188 @@
+# logr
+
+[![GoDoc](https://godoc.org/github.com/mattermost/logr?status.svg)](http://godoc.org/github.com/mattermost/logr)
+[![Report Card](https://goreportcard.com/badge/github.com/mattermost/logr)](https://goreportcard.com/report/github.com/mattermost/logr)
+
+Logr is a fully asynchronous, contextual logger for Go.
+
+It is very much inspired by [Logrus](https://github.com/sirupsen/logrus) but addresses two issues:
+
+1. Logr is fully asynchronous, meaning that all formatting and writing is done in the background. Latency sensitive applications benefit from not waiting for logging to complete.
+
+2. Logr provides custom filters which provide more flexibility than Trace, Debug, Info... levels. If you need to temporarily increase verbosity of logging while tracking down a problem you can avoid the fire-hose that typically comes from Debug or Trace by using custom filters.
+
+## Concepts
+
+<!-- markdownlint-disable MD033 -->
+| entity | description |
+| ------ | ----------- |
+| Logr | Engine instance typically instantiated once; used to configure logging.<br>```lgr,_ := logr.New()```|
+| Logger | Provides contextual logging via fields; lightweight, can be created once and accessed globally or create on demand.<br>```logger := lgr.NewLogger()```<br>```logger2 := logger.WithField("user", "Sam")```|
+| Target | A destination for log items such as console, file, database or just about anything that can be written to. Each target has its own filter/level and formatter, and any number of targets can be added to a Logr. Targets for file, syslog and any io.Writer are built-in and it is easy to create your own. You can also use any [Logrus hooks](https://github.com/sirupsen/logrus/wiki/Hooks) via a simple [adapter](https://github.com/wiggin77/logrus4logr).|
+| Filter | Determines which logging calls get written versus filtered out. Also determines which logging calls generate a stack trace.<br>```filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Fatal}```|
+| Formatter | Formats the output. Logr includes built-in formatters for JSON and plain text with delimiters. It is easy to create your own formatters or you can also use any [Logrus formatters](https://github.com/sirupsen/logrus#formatters) via a simple [adapter](https://github.com/wiggin77/logrus4logr).<br>```formatter := &format.Plain{Delim: " \| "}```|
+
+## Usage
+
+```go
+// Create Logr instance.
+lgr,_ := logr.New()
+
+// Create a filter and formatter. Both can be shared by multiple
+// targets.
+filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Error}
+formatter := &formatters.Plain{Delim: " | "}
+
+// WriterTarget outputs to any io.Writer
+t := targets.NewWriterTarget(filter, formatter, os.StdOut, 1000)
+lgr.AddTarget(t)
+
+// One or more Loggers can be created, shared, used concurrently,
+// or created on demand.
+logger := lgr.NewLogger().WithField("user", "Sarah")
+
+// Now we can log to the target(s).
+logger.Debug("login attempt")
+logger.Error("login failed")
+
+// Ensure targets are drained before application exit.
+lgr.Shutdown()
+```
+
+## Fields
+
+Fields allow for contextual logging, meaning information can be added to log statements without changing the statements themselves. Information can be shared across multiple logging statements thus allowing log analysis tools to group them.
+
+Fields are added via Loggers:
+
+```go
+lgr,_ := logr.New()
+// ... add targets ...
+logger := lgr.NewLogger().WithFields(logr.Fields{
+ "user": user,
+ "role": role})
+logger.Info("login attempt")
+// ... later ...
+logger.Info("login successful")
+```
+
+`Logger.WithFields` can be used to create additional Loggers that add more fields.
+
+Logr fields are inspired by and work the same as [Logrus fields](https://github.com/sirupsen/logrus#fields).
+
+## Filters
+
+Logr supports the traditional seven log levels via `logr.StdFilter`: Panic, Fatal, Error, Warning, Info, Debug, and Trace.
+
+```go
+// When added to a target, this filter will only allow
+// log statements with level severity Warn or higher.
+// It will also generate stack traces for Error or higher.
+filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Error}
+```
+
+Logr also supports custom filters (logr.CustomFilter) which allow fine grained inclusion of log items without turning on the fire-hose.
+
+```go
+ // create custom levels; use IDs > 10.
+ LoginLevel := logr.Level{ID: 100, Name: "login ", Stacktrace: false}
+ LogoutLevel := logr.Level{ID: 101, Name: "logout", Stacktrace: false}
+
+ lgr,_ := logr.New()
+
+ // create a custom filter with custom levels.
+ filter := &logr.CustomFilter{}
+ filter.Add(LoginLevel, LogoutLevel)
+
+ formatter := &formatters.Plain{Delim: " | "}
+ tgr := targets.NewWriterTarget(filter, formatter, os.StdOut, 1000)
+ lgr.AddTarget(tgr)
+ logger := lgr.NewLogger().WithFields(logr.Fields{"user": "Bob", "role": "admin"})
+
+ logger.Log(LoginLevel, "this item will get logged")
+ logger.Debug("won't be logged since Debug wasn't added to custom filter")
+```
+
+Both filter types allow you to determine which levels require a stack trace to be output. Note that generating stack traces cannot happen fully asynchronously and thus add latency to the calling goroutine.
+
+## Targets
+
+There are built-in targets for outputting to syslog, file, or any `io.Writer`. More will be added.
+
+You can use any [Logrus hooks](https://github.com/sirupsen/logrus/wiki/Hooks) via a simple [adapter](https://github.com/wiggin77/logrus4logr).
+
+You can create your own target by implementing the [Target](./target.go) interface.
+
+Example target that outputs to `io.Writer`:
+
+```go
+type Writer struct {
+ out io.Writer
+}
+
+func NewWriterTarget(out io.Writer) *Writer {
+ w := &Writer{out: out}
+ return w
+}
+
+// Called once to initialize target.
+func (w *Writer) Init() error {
+ return nil
+}
+
+// Write will always be called by a single goroutine, so no locking needed.
+func (w *Writer) Write(p []byte, rec *logr.LogRec) (int, error) {
+ return w.out.Write(buf.Bytes())
+}
+
+// Called once to cleanup/free resources for target.
+func (w *Writer) Shutdown() error {
+ return nil
+}
+```
+
+## Formatters
+
+Logr has two built-in formatters, one for JSON and the other plain, delimited text.
+
+You can use any [Logrus formatters](https://github.com/sirupsen/logrus#formatters) via a simple [adapter](https://github.com/wiggin77/logrus4logr).
+
+You can create your own formatter by implementing the [Formatter](./formatter.go) interface:
+
+```go
+Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error)
+```
+
+## Handlers
+
+When creating the Logr instance, you can add several handlers that get called when exceptional events occur:
+
+### ```Logr.OnLoggerError(err error)```
+
+Called any time an internal logging error occurs. For example, this can happen when a target cannot connect to its data sink.
+
+It may be tempting to log this error, however there is a danger that logging this will simply generate another error and so on. If you must log it, use a target and custom level specifically for this event and ensure it cannot generate more errors.
+
+### ```Logr.OnQueueFull func(rec *LogRec, maxQueueSize int) bool```
+
+Called on an attempt to add a log record to a full Logr queue. This generally means the Logr maximum queue size is too small, or at least one target is very slow. Logr maximum queue size can be changed before adding any targets via:
+
+```go
+lgr := logr.Logr{MaxQueueSize: 10000}
+```
+
+Returning true will drop the log record. False will block until the log record can be added, which creates a natural throttle at the expense of latency for the calling goroutine. The default is to block.
+
+### ```Logr.OnTargetQueueFull func(target Target, rec *LogRec, maxQueueSize int) bool```
+
+Called on an attempt to add a log record to a full target queue. This generally means your target's max queue size is too small, or the target is very slow to output.
+
+As with the Logr queue, returning true will drop the log record. False will block until the log record can be added, which creates a natural throttle at the expense of latency for the calling goroutine. The default is to block.
+
+### ```Logr.OnExit func(code int) and Logr.OnPanic func(err interface{})```
+
+OnExit and OnPanic are called when the Logger.FatalXXX and Logger.PanicXXX functions are called respectively.
+
+In both cases the default behavior is to shut down gracefully, draining all targets, and calling `os.Exit` or `panic` respectively.
+
+When adding your own handlers, be sure to call `Logr.Shutdown` before exiting the application to avoid losing log records.
diff --git a/vendor/github.com/mattermost/logr/v2/config/config.go b/vendor/github.com/mattermost/logr/v2/config/config.go
new file mode 100644
index 00000000..a93b7a25
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/config/config.go
@@ -0,0 +1,209 @@
+package config
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/mattermost/logr/v2"
+ "github.com/mattermost/logr/v2/formatters"
+ "github.com/mattermost/logr/v2/targets"
+)
+
+type TargetCfg struct {
+ Type string `json:"type"` // one of "console", "file", "tcp", "syslog", "none".
+ Options json.RawMessage `json:"options,omitempty"`
+ Format string `json:"format"` // one of "json", "plain", "gelf"
+ FormatOptions json.RawMessage `json:"format_options,omitempty"`
+ Levels []logr.Level `json:"levels"`
+ MaxQueueSize int `json:"maxqueuesize,omitempty"`
+}
+
+type ConsoleOptions struct {
+ Out string `json:"out"` // one of "stdout", "stderr"
+}
+
+type TargetFactory func(targetType string, options json.RawMessage) (logr.Target, error)
+type FormatterFactory func(format string, options json.RawMessage) (logr.Formatter, error)
+
+type Factories struct {
+ targetFactory TargetFactory // can be nil
+ formatterFactory FormatterFactory // can be nil
+}
+
+var removeAll = func(ti logr.TargetInfo) bool { return true }
+
+// ConfigureTargets replaces the current list of log targets with a new one based on a map
+// of name->TargetCfg. The map of TargetCfg's would typically be serialized from a JSON
+// source or can be programmatically created.
+//
+// An optional set of factories can be provided which will be called to create any target
+// types or formatters not built-in.
+//
+// To append log targets to an existing config, use `(*Logr).AddTarget` or
+// `(*Logr).AddTargetFromConfig` instead.
+func ConfigureTargets(lgr *logr.Logr, config map[string]TargetCfg, factories *Factories) error {
+ if err := lgr.RemoveTargets(context.Background(), removeAll); err != nil {
+ return fmt.Errorf("error removing existing log targets: %w", err)
+ }
+
+ if factories == nil {
+ factories = &Factories{nil, nil}
+ }
+
+ for name, tcfg := range config {
+ target, err := newTarget(tcfg.Type, tcfg.Options, factories.targetFactory)
+ if err != nil {
+ return fmt.Errorf("error creating log target %s: %w", name, err)
+ }
+
+ if target == nil {
+ continue
+ }
+
+ formatter, err := newFormatter(tcfg.Format, tcfg.FormatOptions, factories.formatterFactory)
+ if err != nil {
+ return fmt.Errorf("error creating formatter for log target %s: %w", name, err)
+ }
+
+ filter := newFilter(tcfg.Levels)
+ qSize := tcfg.MaxQueueSize
+ if qSize == 0 {
+ qSize = logr.DefaultMaxQueueSize
+ }
+
+ if err = lgr.AddTarget(target, name, filter, formatter, qSize); err != nil {
+ return fmt.Errorf("error adding log target %s: %w", name, err)
+ }
+ }
+ return nil
+}
+
+func newFilter(levels []logr.Level) logr.Filter {
+ filter := &logr.CustomFilter{}
+ for _, lvl := range levels {
+ filter.Add(lvl)
+ }
+ return filter
+}
+
+func newTarget(targetType string, options json.RawMessage, factory TargetFactory) (logr.Target, error) {
+ switch strings.ToLower(targetType) {
+ case "console":
+ c := ConsoleOptions{}
+ if len(options) != 0 {
+ if err := json.Unmarshal(options, &c); err != nil {
+ return nil, fmt.Errorf("error decoding console target options: %w", err)
+ }
+ }
+ var w io.Writer
+ switch c.Out {
+ case "stderr":
+ w = os.Stderr
+ case "stdout", "":
+ w = os.Stdout
+ default:
+ return nil, fmt.Errorf("invalid console target option '%s'", c.Out)
+ }
+ return targets.NewWriterTarget(w), nil
+ case "file":
+ fo := targets.FileOptions{}
+ if len(options) == 0 {
+ return nil, errors.New("missing file target options")
+ }
+ if err := json.Unmarshal(options, &fo); err != nil {
+ return nil, fmt.Errorf("error decoding file target options: %w", err)
+ }
+ if err := fo.CheckValid(); err != nil {
+ return nil, fmt.Errorf("invalid file target options: %w", err)
+ }
+ return targets.NewFileTarget(fo), nil
+ case "tcp":
+ to := targets.TcpOptions{}
+ if len(options) == 0 {
+ return nil, errors.New("missing TCP target options")
+ }
+ if err := json.Unmarshal(options, &to); err != nil {
+ return nil, fmt.Errorf("error decoding TCP target options: %w", err)
+ }
+ if err := to.CheckValid(); err != nil {
+ return nil, fmt.Errorf("invalid TCP target options: %w", err)
+ }
+ return targets.NewTcpTarget(&to), nil
+ case "syslog":
+ so := targets.SyslogOptions{}
+ if len(options) == 0 {
+ return nil, errors.New("missing SysLog target options")
+ }
+ if err := json.Unmarshal(options, &so); err != nil {
+ return nil, fmt.Errorf("error decoding Syslog target options: %w", err)
+ }
+ if err := so.CheckValid(); err != nil {
+ return nil, fmt.Errorf("invalid SysLog target options: %w", err)
+ }
+ return targets.NewSyslogTarget(&so)
+ case "none":
+ return nil, nil
+ default:
+ if factory != nil {
+ t, err := factory(targetType, options)
+ if err != nil || t == nil {
+ return nil, fmt.Errorf("error from target factory: %w", err)
+ }
+ return t, nil
+ }
+ }
+ return nil, fmt.Errorf("target type '%s' is unrecogized", targetType)
+}
+
+func newFormatter(format string, options json.RawMessage, factory FormatterFactory) (logr.Formatter, error) {
+ switch strings.ToLower(format) {
+ case "json":
+ j := formatters.JSON{}
+ if len(options) != 0 {
+ if err := json.Unmarshal(options, &j); err != nil {
+ return nil, fmt.Errorf("error decoding JSON formatter options: %w", err)
+ }
+ if err := j.CheckValid(); err != nil {
+ return nil, fmt.Errorf("invalid JSON formatter options: %w", err)
+ }
+ }
+ return &j, nil
+ case "plain":
+ p := formatters.Plain{}
+ if len(options) != 0 {
+ if err := json.Unmarshal(options, &p); err != nil {
+ return nil, fmt.Errorf("error decoding Plain formatter options: %w", err)
+ }
+ if err := p.CheckValid(); err != nil {
+ return nil, fmt.Errorf("invalid plain formatter options: %w", err)
+ }
+ }
+ return &p, nil
+ case "gelf":
+ g := formatters.Gelf{}
+ if len(options) != 0 {
+ if err := json.Unmarshal(options, &g); err != nil {
+ return nil, fmt.Errorf("error decoding Gelf formatter options: %w", err)
+ }
+ if err := g.CheckValid(); err != nil {
+ return nil, fmt.Errorf("invalid GELF formatter options: %w", err)
+ }
+ }
+ return &g, nil
+
+ default:
+ if factory != nil {
+ f, err := factory(format, options)
+ if err != nil || f == nil {
+ return nil, fmt.Errorf("error from formatter factory: %w", err)
+ }
+ return f, nil
+ }
+ }
+ return nil, fmt.Errorf("format '%s' is unrecogized", format)
+}
diff --git a/vendor/github.com/mattermost/logr/v2/config/sample-config.json b/vendor/github.com/mattermost/logr/v2/config/sample-config.json
new file mode 100644
index 00000000..540bafbb
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/config/sample-config.json
@@ -0,0 +1,90 @@
+{
+ "sample-console": {
+ "type": "console",
+ "options": {
+ "out": "stdout"
+ },
+ "format": "plain",
+ "format_options": {
+ "delim": " | "
+ },
+ "levels": [
+ {"id": 5, "name": "debug"},
+ {"id": 4, "name": "info"},
+ {"id": 3, "name": "warn"},
+ {"id": 2, "name": "error", "stacktrace": true},
+ {"id": 1, "name": "fatal", "stacktrace": true},
+ {"id": 0, "name": "panic", "stacktrace": true}
+ ],
+ "maxqueuesize": 1000
+ },
+ "sample-file": {
+ "type": "file",
+ "options": {
+ "filename": "test.log",
+ "max_size": 1000000,
+ "max_age": 1,
+ "max_backups": 10,
+ "compress": true
+ },
+ "format": "json",
+ "format_options": {
+ },
+ "levels": [
+ {"id": 5, "name": "debug"},
+ {"id": 4, "name": "info"},
+ {"id": 3, "name": "warn"},
+ {"id": 2, "name": "error", "stacktrace": true},
+ {"id": 1, "name": "fatal", "stacktrace": true},
+ {"id": 0, "name": "panic", "stacktrace": true}
+ ],
+ "maxqueuesize": 1000
+ },
+ "sample-tcp": {
+ "type": "tcp",
+ "options": {
+ "host": "localhost",
+ "port": 18066,
+ "tls": false,
+ "cert": "",
+ "insecure": false
+ },
+ "format": "gelf",
+ "format_options": {
+ "hostname": "server01"
+ },
+ "levels": [
+ {"id": 5, "name": "debug"},
+ {"id": 4, "name": "info"},
+ {"id": 3, "name": "warn"},
+ {"id": 2, "name": "error", "stacktrace": true},
+ {"id": 1, "name": "fatal", "stacktrace": true},
+ {"id": 0, "name": "panic", "stacktrace": true}
+ ],
+ "maxqueuesize": 1000
+ },
+ "sample-syslog": {
+ "type": "syslog",
+ "options": {
+ "host": "localhost",
+ "port": 18066,
+ "tls": false,
+ "cert": "",
+ "insecure": false,
+ "tag": "testapp"
+ },
+ "format": "plain",
+ "format_options": {
+ "delim": " "
+ },
+ "levels": [
+ {"id": 5, "name": "debug"},
+ {"id": 4, "name": "info"},
+ {"id": 3, "name": "warn"},
+ {"id": 2, "name": "error", "stacktrace": true},
+ {"id": 1, "name": "fatal", "stacktrace": true},
+ {"id": 0, "name": "panic", "stacktrace": true}
+ ],
+ "maxqueuesize": 1000
+ }
+}
diff --git a/vendor/github.com/mattermost/logr/v2/const.go b/vendor/github.com/mattermost/logr/v2/const.go
new file mode 100644
index 00000000..29d92241
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/const.go
@@ -0,0 +1,34 @@
+package logr
+
+import "time"
+
+// Defaults.
+const (
+ // DefaultMaxQueueSize is the default maximum queue size for Logr instances.
+ DefaultMaxQueueSize = 1000
+
+ // DefaultMaxStackFrames is the default maximum max number of stack frames collected
+ // when generating stack traces for logging.
+ DefaultMaxStackFrames = 30
+
+ // MaxLevelID is the maximum value of a level ID. Some level cache implementations will
+ // allocate a cache of this size. Cannot exceed uint.
+ MaxLevelID = 65535
+
+ // DefaultEnqueueTimeout is the default amount of time a log record can take to be queued.
+ // This only applies to blocking enqueue which happen after `logr.OnQueueFull` is called
+ // and returns false.
+ DefaultEnqueueTimeout = time.Second * 30
+
+ // DefaultShutdownTimeout is the default amount of time `logr.Shutdown` can execute before
+ // timing out.
+ DefaultShutdownTimeout = time.Second * 30
+
+ // DefaultFlushTimeout is the default amount of time `logr.Flush` can execute before
+ // timing out.
+ DefaultFlushTimeout = time.Second * 30
+
+ // DefaultMaxPooledBuffer is the maximum size a pooled buffer can be.
+ // Buffers that grow beyond this size are garbage collected.
+ DefaultMaxPooledBuffer = 1024 * 1024
+)
diff --git a/vendor/github.com/mattermost/logr/v2/field.go b/vendor/github.com/mattermost/logr/v2/field.go
new file mode 100644
index 00000000..5725d0a1
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/field.go
@@ -0,0 +1,403 @@
+package logr
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+ "time"
+)
+
+var (
+ Comma = []byte{','}
+ Equals = []byte{'='}
+ Space = []byte{' '}
+ Newline = []byte{'\n'}
+ Quote = []byte{'"'}
+ Colon = []byte{'"'}
+)
+
+// LogCloner is implemented by `Any` types that require a clone to be provided
+// to the logger because the original may mutate.
+type LogCloner interface {
+ LogClone() interface{}
+}
+
+// LogWriter is implemented by `Any` types that provide custom formatting for
+// log output. A string representation of the type should be written directly to
+// the `io.Writer`.
+type LogWriter interface {
+ LogWrite(w io.Writer) error
+}
+
+type FieldType uint8
+
+const (
+ UnknownType FieldType = iota
+ StringType
+ StringerType
+ StructType
+ ErrorType
+ BoolType
+ TimestampMillisType
+ TimeType
+ DurationType
+ Int64Type
+ Int32Type
+ IntType
+ Uint64Type
+ Uint32Type
+ UintType
+ Float64Type
+ Float32Type
+ BinaryType
+ ArrayType
+ MapType
+)
+
+type Field struct {
+ Key string
+ Type FieldType
+ Integer int64
+ Float float64
+ String string
+ Interface interface{}
+}
+
+func quoteString(w io.Writer, s string, shouldQuote func(s string) bool) error {
+ b := shouldQuote(s)
+ if b {
+ if _, err := w.Write(Quote); err != nil {
+ return err
+ }
+ }
+
+ if _, err := w.Write([]byte(s)); err != nil {
+ return err
+ }
+
+ if b {
+ if _, err := w.Write(Quote); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ValueString converts a known type to a string using default formatting.
+// This is called lazily by a formatter.
+// Formatters can provide custom formatting or types passed via `Any` can implement
+// the `LogString` interface to generate output for logging.
+// If the optional shouldQuote callback is provided, then it will be called for any
+// string output that could potentially need to be quoted.
+func (f Field) ValueString(w io.Writer, shouldQuote func(s string) bool) error {
+ if shouldQuote == nil {
+ shouldQuote = func(s string) bool { return false }
+ }
+ var err error
+ switch f.Type {
+ case StringType:
+ err = quoteString(w, f.String, shouldQuote)
+
+ case StringerType:
+ s, ok := f.Interface.(fmt.Stringer)
+ if ok {
+ err = quoteString(w, s.String(), shouldQuote)
+ } else if f.Interface == nil {
+ err = quoteString(w, "", shouldQuote)
+ } else {
+ err = fmt.Errorf("invalid fmt.Stringer for key %s", f.Key)
+ }
+
+ case StructType:
+ s, ok := f.Interface.(LogWriter)
+ if ok {
+ err = s.LogWrite(w)
+ break
+ }
+ // structs that do not implement LogWriter fall back to reflection via Printf.
+ // TODO: create custom reflection-based encoder.
+ _, err = fmt.Fprintf(w, "%v", f.Interface)
+
+ case ErrorType:
+ // TODO: create custom error encoder.
+ err = quoteString(w, fmt.Sprintf("%v", f.Interface), shouldQuote)
+
+ case BoolType:
+ var b bool
+ if f.Integer != 0 {
+ b = true
+ }
+ _, err = io.WriteString(w, strconv.FormatBool(b))
+
+ case TimestampMillisType:
+ ts := time.Unix(f.Integer/1000, (f.Integer%1000)*int64(time.Millisecond))
+ err = quoteString(w, ts.UTC().Format(TimestampMillisFormat), shouldQuote)
+
+ case TimeType:
+ t, ok := f.Interface.(time.Time)
+ if !ok {
+ err = errors.New("invalid time")
+ break
+ }
+ err = quoteString(w, t.Format(DefTimestampFormat), shouldQuote)
+
+ case DurationType:
+ _, err = fmt.Fprintf(w, "%s", time.Duration(f.Integer))
+
+ case Int64Type, Int32Type, IntType:
+ _, err = io.WriteString(w, strconv.FormatInt(f.Integer, 10))
+
+ case Uint64Type, Uint32Type, UintType:
+ _, err = io.WriteString(w, strconv.FormatUint(uint64(f.Integer), 10))
+
+ case Float64Type, Float32Type:
+ size := 64
+ if f.Type == Float32Type {
+ size = 32
+ }
+ err = quoteString(w, strconv.FormatFloat(f.Float, 'f', -1, size), shouldQuote)
+
+ case BinaryType:
+ b, ok := f.Interface.([]byte)
+ if ok {
+ _, err = fmt.Fprintf(w, "[%X]", b)
+ break
+ }
+ _, err = fmt.Fprintf(w, "[%v]", f.Interface)
+
+ case ArrayType:
+ a := reflect.ValueOf(f.Interface)
+ arr:
+ for i := 0; i < a.Len(); i++ {
+ item := a.Index(i)
+ switch v := item.Interface().(type) {
+ case LogWriter:
+ if err = v.LogWrite(w); err != nil {
+ break arr
+ }
+ case fmt.Stringer:
+ if err = quoteString(w, v.String(), shouldQuote); err != nil {
+ break arr
+ }
+ default:
+ s := fmt.Sprintf("%v", v)
+ if err = quoteString(w, s, shouldQuote); err != nil {
+ break arr
+ }
+ }
+ if _, err = w.Write(Comma); err != nil {
+ break arr
+ }
+ }
+
+ case MapType:
+ a := reflect.ValueOf(f.Interface)
+ iter := a.MapRange()
+ it:
+ for iter.Next() {
+ if _, err = io.WriteString(w, iter.Key().String()); err != nil {
+ break it
+ }
+ if _, err = w.Write(Equals); err != nil {
+ break it
+ }
+ val := iter.Value().Interface()
+ switch v := val.(type) {
+ case LogWriter:
+ if err = v.LogWrite(w); err != nil {
+ break it
+ }
+ case fmt.Stringer:
+ if err = quoteString(w, v.String(), shouldQuote); err != nil {
+ break it
+ }
+ default:
+ s := fmt.Sprintf("%v", v)
+ if err = quoteString(w, s, shouldQuote); err != nil {
+ break it
+ }
+ }
+ if _, err = w.Write(Comma); err != nil {
+ break it
+ }
+ }
+
+ case UnknownType:
+ _, err = fmt.Fprintf(w, "%v", f.Interface)
+
+ default:
+ err = fmt.Errorf("invalid type %d", f.Type)
+ }
+ return err
+}
+
+func nilField(key string) Field {
+ return String(key, "")
+}
+
+func fieldForAny(key string, val interface{}) Field {
+ switch v := val.(type) {
+ case LogCloner:
+ if v == nil {
+ return nilField(key)
+ }
+ c := v.LogClone()
+ return Field{Key: key, Type: StructType, Interface: c}
+ case *LogCloner:
+ if v == nil {
+ return nilField(key)
+ }
+ c := (*v).LogClone()
+ return Field{Key: key, Type: StructType, Interface: c}
+ case LogWriter:
+ if v == nil {
+ return nilField(key)
+ }
+ return Field{Key: key, Type: StructType, Interface: v}
+ case *LogWriter:
+ if v == nil {
+ return nilField(key)
+ }
+ return Field{Key: key, Type: StructType, Interface: *v}
+ case bool:
+ return Bool(key, v)
+ case *bool:
+ if v == nil {
+ return nilField(key)
+ }
+ return Bool(key, *v)
+ case float64:
+ return Float64(key, v)
+ case *float64:
+ if v == nil {
+ return nilField(key)
+ }
+ return Float64(key, *v)
+ case float32:
+ return Float32(key, v)
+ case *float32:
+ if v == nil {
+ return nilField(key)
+ }
+ return Float32(key, *v)
+ case int:
+ return Int(key, v)
+ case *int:
+ if v == nil {
+ return nilField(key)
+ }
+ return Int(key, *v)
+ case int64:
+ return Int64(key, v)
+ case *int64:
+ if v == nil {
+ return nilField(key)
+ }
+ return Int64(key, *v)
+ case int32:
+ return Int32(key, v)
+ case *int32:
+ if v == nil {
+ return nilField(key)
+ }
+ return Int32(key, *v)
+ case int16:
+ return Int32(key, int32(v))
+ case *int16:
+ if v == nil {
+ return nilField(key)
+ }
+ return Int32(key, int32(*v))
+ case int8:
+ return Int32(key, int32(v))
+ case *int8:
+ if v == nil {
+ return nilField(key)
+ }
+ return Int32(key, int32(*v))
+ case string:
+ return String(key, v)
+ case *string:
+ if v == nil {
+ return nilField(key)
+ }
+ return String(key, *v)
+ case uint:
+ return Uint(key, v)
+ case *uint:
+ if v == nil {
+ return nilField(key)
+ }
+ return Uint(key, *v)
+ case uint64:
+ return Uint64(key, v)
+ case *uint64:
+ if v == nil {
+ return nilField(key)
+ }
+ return Uint64(key, *v)
+ case uint32:
+ return Uint32(key, v)
+ case *uint32:
+ if v == nil {
+ return nilField(key)
+ }
+ return Uint32(key, *v)
+ case uint16:
+ return Uint32(key, uint32(v))
+ case *uint16:
+ if v == nil {
+ return nilField(key)
+ }
+ return Uint32(key, uint32(*v))
+ case uint8:
+ return Uint32(key, uint32(v))
+ case *uint8:
+ if v == nil {
+ return nilField(key)
+ }
+ return Uint32(key, uint32(*v))
+ case []byte:
+ if v == nil {
+ return nilField(key)
+ }
+ return Field{Key: key, Type: BinaryType, Interface: v}
+ case time.Time:
+ return Time(key, v)
+ case *time.Time:
+ if v == nil {
+ return nilField(key)
+ }
+ return Time(key, *v)
+ case time.Duration:
+ return Duration(key, v)
+ case *time.Duration:
+ if v == nil {
+ return nilField(key)
+ }
+ return Duration(key, *v)
+ case error:
+ return NamedErr(key, v)
+ case fmt.Stringer:
+ if v == nil {
+ return nilField(key)
+ }
+ return Field{Key: key, Type: StringerType, Interface: v}
+ case *fmt.Stringer:
+ if v == nil {
+ return nilField(key)
+ }
+ return Field{Key: key, Type: StringerType, Interface: *v}
+ default:
+ return Field{Key: key, Type: UnknownType, Interface: val}
+ }
+}
+
+// FieldSorter provides sorting of an array of fields by key.
+type FieldSorter []Field
+
+func (fs FieldSorter) Len() int { return len(fs) }
+func (fs FieldSorter) Less(i, j int) bool { return fs[i].Key < fs[j].Key }
+func (fs FieldSorter) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] }
diff --git a/vendor/github.com/mattermost/logr/v2/fieldapi.go b/vendor/github.com/mattermost/logr/v2/fieldapi.go
new file mode 100644
index 00000000..58b12280
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/fieldapi.go
@@ -0,0 +1,110 @@
+package logr
+
+import (
+ "fmt"
+ "time"
+)
+
+// Any picks the best supported field type based on type of val.
+// For best performance when passing a struct (or struct pointer),
+// implement `logr.LogWriter` on the struct, otherwise reflection
+// will be used to generate a string representation.
+func Any(key string, val interface{}) Field {
+ return fieldForAny(key, val)
+}
+
+// Int64 constructs a field containing a key and Int64 value.
+func Int64(key string, val int64) Field {
+ return Field{Key: key, Type: Int64Type, Integer: val}
+}
+
+// Int32 constructs a field containing a key and Int32 value.
+func Int32(key string, val int32) Field {
+ return Field{Key: key, Type: Int32Type, Integer: int64(val)}
+}
+
+// Int constructs a field containing a key and Int value.
+func Int(key string, val int) Field {
+ return Field{Key: key, Type: IntType, Integer: int64(val)}
+}
+
+// Uint64 constructs a field containing a key and Uint64 value.
+func Uint64(key string, val uint64) Field {
+ return Field{Key: key, Type: Uint64Type, Integer: int64(val)}
+}
+
+// Uint32 constructs a field containing a key and Uint32 value.
+func Uint32(key string, val uint32) Field {
+ return Field{Key: key, Type: Uint32Type, Integer: int64(val)}
+}
+
+// Uint constructs a field containing a key and Uint value.
+func Uint(key string, val uint) Field {
+ return Field{Key: key, Type: UintType, Integer: int64(val)}
+}
+
+// Float64 constructs a field containing a key and Float64 value.
+func Float64(key string, val float64) Field {
+ return Field{Key: key, Type: Float64Type, Float: val}
+}
+
+// Float32 constructs a field containing a key and Float32 value.
+func Float32(key string, val float32) Field {
+ return Field{Key: key, Type: Float32Type, Float: float64(val)}
+}
+
+// String constructs a field containing a key and String value.
+func String(key string, val string) Field {
+ return Field{Key: key, Type: StringType, String: val}
+}
+
+// Stringer constructs a field containing a key and a `fmt.Stringer` value.
+// The `String` method will be called in lazy fashion.
+func Stringer(key string, val fmt.Stringer) Field {
+ return Field{Key: key, Type: StringerType, Interface: val}
+}
+
+// Err constructs a field containing a default key ("error") and error value.
+func Err(err error) Field {
+ return NamedErr("error", err)
+}
+
+// NamedErr constructs a field containing a key and error value.
+func NamedErr(key string, err error) Field {
+ return Field{Key: key, Type: ErrorType, Interface: err}
+}
+
+// Bool constructs a field containing a key and bool value.
+func Bool(key string, val bool) Field {
+ var b int64
+ if val {
+ b = 1
+ }
+ return Field{Key: key, Type: BoolType, Integer: b}
+}
+
+// Time constructs a field containing a key and time.Time value.
+func Time(key string, val time.Time) Field {
+ return Field{Key: key, Type: TimeType, Interface: val}
+}
+
+// Duration constructs a field containing a key and time.Duration value.
+func Duration(key string, val time.Duration) Field {
+ return Field{Key: key, Type: DurationType, Integer: int64(val)}
+}
+
+// Millis constructs a field containing a key and timestamp value.
+// The timestamp is expected to be milliseconds since Jan 1, 1970 UTC.
+func Millis(key string, val int64) Field {
+ return Field{Key: key, Type: TimestampMillisType, Integer: val}
+}
+
+// Array constructs a field containing a key and array value.
+func Array(key string, val interface{}) Field {
+ return Field{Key: key, Type: ArrayType, Interface: val}
+}
+
+// Map constructs a field containing a key and map value.
+func Map(key string, val interface{}) Field {
+ return Field{Key: key, Type: MapType, Interface: val}
+}
diff --git a/vendor/github.com/mattermost/logr/v2/filter.go b/vendor/github.com/mattermost/logr/v2/filter.go
new file mode 100644
index 00000000..a52a7cf4
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/filter.go
@@ -0,0 +1,10 @@
+package logr
+
+// Filter allows targets to determine which Level(s) are active
+// for logging and which Level(s) require a stack trace to be output.
+// A default implementation using "panic, fatal..." is provided, and
+// a more flexible alternative implementation is also provided that
+// allows any number of custom levels.
+type Filter interface {
+ GetEnabledLevel(level Level) (Level, bool)
+}
diff --git a/vendor/github.com/mattermost/logr/v2/filtercustom.go b/vendor/github.com/mattermost/logr/v2/filtercustom.go
new file mode 100644
index 00000000..c20f2811
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/filtercustom.go
@@ -0,0 +1,47 @@
+package logr
+
+import (
+ "sync"
+)
+
+// CustomFilter allows targets to enable logging via a list of discrete levels.
+type CustomFilter struct {
+ mux sync.RWMutex
+ levels map[LevelID]Level
+}
+
+// NewCustomFilter creates a filter supporting discrete log levels.
+func NewCustomFilter(levels ...Level) *CustomFilter {
+ filter := &CustomFilter{}
+ filter.Add(levels...)
+ return filter
+}
+
+// GetEnabledLevel returns the Level with the specified Level.ID and whether the level
+// is enabled for this filter.
+func (cf *CustomFilter) GetEnabledLevel(level Level) (Level, bool) {
+ cf.mux.RLock()
+ defer cf.mux.RUnlock()
+ levelEnabled, ok := cf.levels[level.ID]
+
+ if ok && levelEnabled.Name == "" {
+ levelEnabled.Name = level.Name
+ }
+
+ return levelEnabled, ok
+}
+
+// Add adds one or more levels to the list. Adding a level enables logging for
+// that level on any targets using this CustomFilter.
+func (cf *CustomFilter) Add(levels ...Level) {
+ cf.mux.Lock()
+ defer cf.mux.Unlock()
+
+ if cf.levels == nil {
+ cf.levels = make(map[LevelID]Level)
+ }
+
+ for _, s := range levels {
+ cf.levels[s.ID] = s
+ }
+}
diff --git a/vendor/github.com/mattermost/logr/v2/filterstd.go b/vendor/github.com/mattermost/logr/v2/filterstd.go
new file mode 100644
index 00000000..7f38a332
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/filterstd.go
@@ -0,0 +1,65 @@
+package logr
+
+// StdFilter allows targets to filter via classic log levels where any level
+// beyond a certain verbosity/severity is enabled.
+type StdFilter struct {
+ Lvl Level
+ Stacktrace Level
+}
+
+// GetEnabledLevel returns the Level with the specified Level.ID and whether the level
+// is enabled for this filter.
+func (lt StdFilter) GetEnabledLevel(level Level) (Level, bool) {
+ enabled := level.ID <= lt.Lvl.ID
+ var levelEnabled Level
+
+ if enabled {
+ switch level.ID {
+ case Panic.ID:
+ levelEnabled = Panic
+ case Fatal.ID:
+ levelEnabled = Fatal
+ case Error.ID:
+ levelEnabled = Error
+ case Warn.ID:
+ levelEnabled = Warn
+ case Info.ID:
+ levelEnabled = Info
+ case Debug.ID:
+ levelEnabled = Debug
+ case Trace.ID:
+ levelEnabled = Trace
+ default:
+ levelEnabled = level
+ }
+ }
+ return levelEnabled, enabled
+}
+
+// IsEnabled returns true if the specified Level is at or above this verbosity. Also
+// determines if a stack trace is required.
+func (lt StdFilter) IsEnabled(level Level) bool {
+ return level.ID <= lt.Lvl.ID
+}
+
+// IsStacktraceEnabled returns true if the specified Level requires a stack trace.
+func (lt StdFilter) IsStacktraceEnabled(level Level) bool {
+ return level.ID <= lt.Stacktrace.ID
+}
+
+var (
+ // Panic is the highest level of severity.
+ Panic = Level{ID: 0, Name: "panic", Color: Red}
+ // Fatal designates a catastrophic error.
+ Fatal = Level{ID: 1, Name: "fatal", Color: Red}
+ // Error designates a serious but possibly recoverable error.
+ Error = Level{ID: 2, Name: "error", Color: Red}
+ // Warn designates non-critical error.
+ Warn = Level{ID: 3, Name: "warn", Color: Yellow}
+ // Info designates information regarding application events.
+ Info = Level{ID: 4, Name: "info", Color: Cyan}
+ // Debug designates verbose information typically used for debugging.
+ Debug = Level{ID: 5, Name: "debug", Color: NoColor}
+ // Trace designates the highest verbosity of log output.
+ Trace = Level{ID: 6, Name: "trace", Color: NoColor}
+)
diff --git a/vendor/github.com/mattermost/logr/v2/formatter.go b/vendor/github.com/mattermost/logr/v2/formatter.go
new file mode 100644
index 00000000..c8bb9b70
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/formatter.go
@@ -0,0 +1,184 @@
+package logr
+
+import (
+ "bytes"
+ "io"
+ "runtime"
+ "strconv"
+)
+
+// Formatter turns a LogRec into a formatted string.
+type Formatter interface {
+ // IsStacktraceNeeded returns true if this formatter requires a stacktrace to be
+ // generated for each LogRecord. Enabling features such as `Caller` field require
+ // a stacktrace.
+ IsStacktraceNeeded() bool
+
+ // Format converts a log record to bytes. If buf is not nil then it will be
+ // be filled with the formatted results, otherwise a new buffer will be allocated.
+ Format(rec *LogRec, level Level, buf *bytes.Buffer) (*bytes.Buffer, error)
+}
+
+const (
+ // DefTimestampFormat is the default time stamp format used by Plain formatter and others.
+ DefTimestampFormat = "2006-01-02 15:04:05.000 Z07:00"
+
+ // TimestampMillisFormat is the format for logging milliseconds UTC
+ TimestampMillisFormat = "Jan _2 15:04:05.000"
+)
+
+type Writer struct {
+ io.Writer
+}
+
+func (w Writer) Writes(elems ...[]byte) (int, error) {
+ var count int
+ for _, e := range elems {
+ if c, err := w.Write(e); err != nil {
+ return count + c, err
+ } else {
+ count += c
+ }
+ }
+ return count, nil
+}
+
+// DefaultFormatter is the default formatter, outputting only text with
+// no colors and a space delimiter. Use `format.Plain` instead.
+type DefaultFormatter struct {
+}
+
+// IsStacktraceNeeded always returns false for default formatter since the
+// `Caller` field is not supported.
+func (p *DefaultFormatter) IsStacktraceNeeded() bool {
+ return false
+}
+
+// Format converts a log record to bytes.
+func (p *DefaultFormatter) Format(rec *LogRec, level Level, buf *bytes.Buffer) (*bytes.Buffer, error) {
+ if buf == nil {
+ buf = &bytes.Buffer{}
+ }
+ timestampFmt := DefTimestampFormat
+
+ buf.WriteString(rec.Time().Format(timestampFmt))
+ buf.Write(Space)
+
+ buf.WriteString(level.Name)
+ buf.Write(Space)
+
+ buf.WriteString(rec.Msg())
+ buf.Write(Space)
+
+ fields := rec.Fields()
+ if len(fields) > 0 {
+ if err := WriteFields(buf, fields, Space, NoColor); err != nil {
+ return nil, err
+ }
+ }
+
+ if level.Stacktrace {
+ frames := rec.StackFrames()
+ if len(frames) > 0 {
+ buf.Write(Newline)
+ if err := WriteStacktrace(buf, rec.StackFrames()); err != nil {
+ return nil, err
+ }
+ }
+ }
+ buf.Write(Newline)
+
+ return buf, nil
+}
+
+// WriteFields writes zero or more name value pairs to the io.Writer.
+// The pairs output in key=value format with optional separator between fields.
+func WriteFields(w io.Writer, fields []Field, separator []byte, color Color) error {
+ ws := Writer{w}
+
+ sep := []byte{}
+ for _, field := range fields {
+ if err := writeField(ws, field, sep, color); err != nil {
+ return err
+ }
+ sep = separator
+ }
+ return nil
+}
+
+func writeField(ws Writer, field Field, sep []byte, color Color) error {
+ if len(sep) != 0 {
+ if _, err := ws.Write(sep); err != nil {
+ return err
+ }
+ }
+ if err := WriteWithColor(ws, field.Key, color); err != nil {
+ return err
+ }
+ if _, err := ws.Write(Equals); err != nil {
+ return err
+ }
+ return field.ValueString(ws, shouldQuote)
+}
+
+// shouldQuote returns true if val contains any characters that might be unsafe
+// when injecting log output into an aggregator, viewer or report.
+func shouldQuote(val string) bool {
+ for _, c := range val {
+ if !((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ c == '-' || c == '.' || c == '_' || c == '/' || c == '@' || c == '^' || c == '+') {
+ return true
+ }
+ }
+ return false
+}
+
+// WriteStacktrace formats and outputs a stack trace to an io.Writer.
+func WriteStacktrace(w io.Writer, frames []runtime.Frame) error {
+ ws := Writer{w}
+ for _, frame := range frames {
+ if frame.Function != "" {
+ if _, err := ws.Writes(Space, Space, []byte(frame.Function), Newline); err != nil {
+ return err
+ }
+ }
+ if frame.File != "" {
+ s := strconv.FormatInt(int64(frame.Line), 10)
+ if _, err := ws.Writes([]byte{' ', ' ', ' ', ' ', ' ', ' '}, []byte(frame.File), Colon, []byte(s), Newline); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// WriteWithColor outputs a string with the specified ANSI color.
+func WriteWithColor(w io.Writer, s string, color Color) error {
+ var err error
+
+ writer := func(buf []byte) {
+ if err != nil {
+ return
+ }
+ _, err = w.Write(buf)
+ }
+
+ if color != NoColor {
+ writer(AnsiColorPrefix)
+ writer([]byte(strconv.FormatInt(int64(color), 10)))
+ writer(AnsiColorSuffix)
+ }
+
+ if err == nil {
+ _, err = io.WriteString(w, s)
+ }
+
+ if color != NoColor {
+ writer(AnsiColorPrefix)
+ writer([]byte(strconv.FormatInt(int64(NoColor), 10)))
+ writer(AnsiColorSuffix)
+ }
+ return err
+}
diff --git a/vendor/github.com/mattermost/logr/v2/formatters/gelf.go b/vendor/github.com/mattermost/logr/v2/formatters/gelf.go
new file mode 100644
index 00000000..9dece13c
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/formatters/gelf.go
@@ -0,0 +1,152 @@
+package formatters
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "os"
+ "strings"
+
+ "github.com/francoispqt/gojay"
+ "github.com/mattermost/logr/v2"
+)
+
+const (
+ GelfVersion = "1.1"
+ GelfVersionKey = "version"
+ GelfHostKey = "host"
+ GelfShortKey = "short_message"
+ GelfFullKey = "full_message"
+ GelfTimestampKey = "timestamp"
+ GelfLevelKey = "level"
+)
+
+// Gelf formats log records as GELF rcords (https://docs.graylog.org/en/4.0/pages/gelf.html).
+type Gelf struct {
+ // Hostname allows a custom hostname, otherwise os.Hostname is used
+ Hostname string `json:"hostname"`
+
+ // EnableCaller enables output of the file and line number that emitted a log record.
+ EnableCaller bool `json:"enable_caller"`
+
+ // FieldSorter allows custom sorting for the context fields.
+ FieldSorter func(fields []logr.Field) []logr.Field `json:"-"`
+}
+
+func (g *Gelf) CheckValid() error {
+ return nil
+}
+
+// IsStacktraceNeeded returns true if a stacktrace is needed so we can output the `Caller` field.
+func (g *Gelf) IsStacktraceNeeded() bool {
+ return g.EnableCaller
+}
+
+// Format converts a log record to bytes in GELF format.
+func (g *Gelf) Format(rec *logr.LogRec, level logr.Level, buf *bytes.Buffer) (*bytes.Buffer, error) {
+ if buf == nil {
+ buf = &bytes.Buffer{}
+ }
+ enc := gojay.BorrowEncoder(buf)
+ defer func() {
+ enc.Release()
+ }()
+
+ gr := gelfRecord{
+ LogRec: rec,
+ Gelf: g,
+ level: level,
+ sorter: g.FieldSorter,
+ }
+
+ err := enc.EncodeObject(gr)
+ if err != nil {
+ return nil, err
+ }
+
+ buf.WriteByte(0)
+ return buf, nil
+}
+
+type gelfRecord struct {
+ *logr.LogRec
+ *Gelf
+ level logr.Level
+ sorter func(fields []logr.Field) []logr.Field
+}
+
+// MarshalJSONObject encodes the LogRec as JSON.
+func (gr gelfRecord) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.AddStringKey(GelfVersionKey, GelfVersion)
+ enc.AddStringKey(GelfHostKey, gr.getHostname())
+ enc.AddStringKey(GelfShortKey, gr.Msg())
+
+ if gr.level.Stacktrace {
+ frames := gr.StackFrames()
+ if len(frames) != 0 {
+ var sbuf strings.Builder
+ for _, frame := range frames {
+ fmt.Fprintf(&sbuf, "%s\n %s:%d\n", frame.Function, frame.File, frame.Line)
+ }
+ enc.AddStringKey(GelfFullKey, sbuf.String())
+ }
+ }
+
+ secs := float64(gr.Time().UTC().Unix())
+ millis := float64(gr.Time().Nanosecond() / 1000000)
+ ts := secs + (millis / 1000)
+ enc.AddFloat64Key(GelfTimestampKey, ts)
+
+ enc.AddUint32Key(GelfLevelKey, uint32(gr.level.ID))
+
+ var fields []logr.Field
+ if gr.EnableCaller {
+ caller := logr.Field{
+ Key: "_caller",
+ Type: logr.StringType,
+ String: gr.LogRec.Caller(),
+ }
+ fields = append(fields, caller)
+ }
+
+ fields = append(fields, gr.Fields()...)
+ if gr.sorter != nil {
+ fields = gr.sorter(fields)
+ }
+
+ if len(fields) > 0 {
+ for _, field := range fields {
+ if !strings.HasPrefix("_", field.Key) {
+ field.Key = "_" + field.Key
+ }
+ if err := encodeField(enc, field); err != nil {
+ enc.AddStringKey(field.Key, fmt.Sprintf("<error encoding field: %v>", err))
+ }
+ }
+ }
+}
+
+// IsNil returns true if the gelf record pointer is nil.
+func (gr gelfRecord) IsNil() bool {
+ return gr.LogRec == nil
+}
+
+func (g *Gelf) getHostname() string {
+ if g.Hostname != "" {
+ return g.Hostname
+ }
+ h, err := os.Hostname()
+ if err == nil {
+ return h
+ }
+
+ // get the egress IP by fake dialing any address. UDP ensures no dial.
+ conn, err := net.Dial("udp", "8.8.8.8:80")
+ if err != nil {
+ return "unknown"
+ }
+ defer conn.Close()
+
+ local := conn.LocalAddr().(*net.UDPAddr)
+ return local.IP.String()
+}
diff --git a/vendor/github.com/mattermost/logr/v2/formatters/json.go b/vendor/github.com/mattermost/logr/v2/formatters/json.go
new file mode 100644
index 00000000..172b9612
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/formatters/json.go
@@ -0,0 +1,273 @@
+package formatters
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "runtime"
+ "strings"
+ "sync"
+
+ "github.com/francoispqt/gojay"
+ "github.com/mattermost/logr/v2"
+)
+
+// JSON formats log records as JSON.
+type JSON struct {
+ // DisableTimestamp disables output of timestamp field.
+ DisableTimestamp bool `json:"disable_timestamp"`
+ // DisableLevel disables output of level field.
+ DisableLevel bool `json:"disable_level"`
+ // DisableMsg disables output of msg field.
+ DisableMsg bool `json:"disable_msg"`
+ // DisableFields disables output of all fields.
+ DisableFields bool `json:"disable_fields"`
+ // DisableStacktrace disables output of stack trace.
+ DisableStacktrace bool `json:"disable_stacktrace"`
+ // EnableCaller enables output of the file and line number that emitted a log record.
+ EnableCaller bool `json:"enable_caller"`
+
+ // TimestampFormat is an optional format for timestamps. If empty
+ // then DefTimestampFormat is used.
+ TimestampFormat string `json:"timestamp_format"`
+
+ // KeyTimestamp overrides the timestamp field key name.
+ KeyTimestamp string `json:"key_timestamp"`
+
+ // KeyLevel overrides the level field key name.
+ KeyLevel string `json:"key_level"`
+
+ // KeyMsg overrides the msg field key name.
+ KeyMsg string `json:"key_msg"`
+
+ // KeyGroupFields when not empty will group all context fields
+ // under this key.
+ KeyGroupFields string `json:"key_group_fields"`
+
+ // KeyStacktrace overrides the stacktrace field key name.
+ KeyStacktrace string `json:"key_stacktrace"`
+
+ // KeyCaller overrides the caller field key name.
+ KeyCaller string `json:"key_caller"`
+
+ // FieldSorter allows custom sorting of the fields. If nil then
+ // no sorting is done.
+ FieldSorter func(fields []logr.Field) []logr.Field `json:"-"`
+
+ once sync.Once
+}
+
+func (j *JSON) CheckValid() error {
+ return nil
+}
+
+// IsStacktraceNeeded returns true if a stacktrace is needed so we can output the `Caller` field.
+func (j *JSON) IsStacktraceNeeded() bool {
+ return j.EnableCaller
+}
+
+// Format converts a log record to bytes in JSON format.
+func (j *JSON) Format(rec *logr.LogRec, level logr.Level, buf *bytes.Buffer) (*bytes.Buffer, error) {
+ j.once.Do(j.applyDefaultKeyNames)
+
+ if buf == nil {
+ buf = &bytes.Buffer{}
+ }
+ enc := gojay.BorrowEncoder(buf)
+ defer func() {
+ enc.Release()
+ }()
+
+ jlr := JSONLogRec{
+ LogRec: rec,
+ JSON: j,
+ level: level,
+ sorter: j.FieldSorter,
+ }
+
+ err := enc.EncodeObject(jlr)
+ if err != nil {
+ return nil, err
+ }
+ buf.WriteByte('\n')
+ return buf, nil
+}
+
+func (j *JSON) applyDefaultKeyNames() {
+ if j.KeyTimestamp == "" {
+ j.KeyTimestamp = "timestamp"
+ }
+ if j.KeyLevel == "" {
+ j.KeyLevel = "level"
+ }
+ if j.KeyMsg == "" {
+ j.KeyMsg = "msg"
+ }
+ if j.KeyStacktrace == "" {
+ j.KeyStacktrace = "stacktrace"
+ }
+ if j.KeyCaller == "" {
+ j.KeyCaller = "caller"
+ }
+}
+
+// JSONLogRec decorates a LogRec adding JSON encoding.
+type JSONLogRec struct {
+ *logr.LogRec
+ *JSON
+ level logr.Level
+ sorter func(fields []logr.Field) []logr.Field
+}
+
+// MarshalJSONObject encodes the LogRec as JSON.
+func (jlr JSONLogRec) MarshalJSONObject(enc *gojay.Encoder) {
+ if !jlr.DisableTimestamp {
+ timestampFmt := jlr.TimestampFormat
+ if timestampFmt == "" {
+ timestampFmt = logr.DefTimestampFormat
+ }
+ time := jlr.Time()
+ enc.AddTimeKey(jlr.KeyTimestamp, &time, timestampFmt)
+ }
+ if !jlr.DisableLevel {
+ enc.AddStringKey(jlr.KeyLevel, jlr.level.Name)
+ }
+ if !jlr.DisableMsg {
+ enc.AddStringKey(jlr.KeyMsg, jlr.Msg())
+ }
+ if jlr.EnableCaller {
+ enc.AddStringKey(jlr.KeyCaller, jlr.Caller())
+ }
+ if !jlr.DisableFields {
+ fields := jlr.Fields()
+ if jlr.sorter != nil {
+ fields = jlr.sorter(fields)
+ }
+ if jlr.KeyGroupFields != "" {
+ enc.AddObjectKey(jlr.KeyGroupFields, FieldArray(fields))
+ } else {
+ if len(fields) > 0 {
+ for _, field := range fields {
+ field = jlr.prefixCollision(field)
+ if err := encodeField(enc, field); err != nil {
+ enc.AddStringKey(field.Key, "<error encoding field: "+err.Error()+">")
+ }
+ }
+ }
+ }
+ }
+ if jlr.level.Stacktrace && !jlr.DisableStacktrace {
+ frames := jlr.StackFrames()
+ if len(frames) > 0 {
+ enc.AddArrayKey(jlr.KeyStacktrace, stackFrames(frames))
+ }
+ }
+}
+
+// IsNil returns true if the LogRec pointer is nil.
+func (rec JSONLogRec) IsNil() bool {
+ return rec.LogRec == nil
+}
+
+func (rec JSONLogRec) prefixCollision(field logr.Field) logr.Field {
+ switch field.Key {
+ case rec.KeyTimestamp, rec.KeyLevel, rec.KeyMsg, rec.KeyStacktrace:
+ f := field
+ f.Key = "_" + field.Key
+ return rec.prefixCollision(f)
+ }
+ return field
+}
+
+type stackFrames []runtime.Frame
+
+// MarshalJSONArray encodes stackFrames slice as JSON.
+func (s stackFrames) MarshalJSONArray(enc *gojay.Encoder) {
+ for _, frame := range s {
+ enc.AddObject(stackFrame(frame))
+ }
+}
+
+// IsNil returns true if stackFrames is empty slice.
+func (s stackFrames) IsNil() bool {
+ return len(s) == 0
+}
+
+type stackFrame runtime.Frame
+
+// MarshalJSONArray encodes stackFrame as JSON.
+func (f stackFrame) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.AddStringKey("Function", f.Function)
+ enc.AddStringKey("File", f.File)
+ enc.AddIntKey("Line", f.Line)
+}
+
+func (f stackFrame) IsNil() bool {
+ return false
+}
+
+type FieldArray []logr.Field
+
+// MarshalJSONObject encodes Fields map to JSON.
+func (fa FieldArray) MarshalJSONObject(enc *gojay.Encoder) {
+ for _, fld := range fa {
+ if err := encodeField(enc, fld); err != nil {
+ enc.AddStringKey(fld.Key, "<error encoding field: "+err.Error()+">")
+ }
+ }
+}
+
+// IsNil returns true if map is nil.
+func (fa FieldArray) IsNil() bool {
+ return fa == nil
+}
+
+func encodeField(enc *gojay.Encoder, field logr.Field) error {
+ // first check if the value has a marshaller already.
+ switch vt := field.Interface.(type) {
+ case gojay.MarshalerJSONObject:
+ enc.AddObjectKey(field.Key, vt)
+ return nil
+ case gojay.MarshalerJSONArray:
+ enc.AddArrayKey(field.Key, vt)
+ return nil
+ }
+
+ switch field.Type {
+ case logr.StringType:
+ enc.AddStringKey(field.Key, field.String)
+
+ case logr.BoolType:
+ var b bool
+ if field.Integer != 0 {
+ b = true
+ }
+ enc.AddBoolKey(field.Key, b)
+
+ case logr.StructType, logr.ArrayType, logr.MapType, logr.UnknownType:
+ b, err := json.Marshal(field.Interface)
+ if err != nil {
+ return err
+ }
+ embed := gojay.EmbeddedJSON(b)
+ enc.AddEmbeddedJSONKey(field.Key, &embed)
+
+ case logr.StringerType, logr.ErrorType, logr.TimestampMillisType, logr.TimeType, logr.DurationType, logr.BinaryType:
+ var buf strings.Builder
+ _ = field.ValueString(&buf, nil)
+ enc.AddStringKey(field.Key, buf.String())
+
+ case logr.Int64Type, logr.Int32Type, logr.IntType:
+ enc.AddInt64Key(field.Key, field.Integer)
+
+ case logr.Uint64Type, logr.Uint32Type, logr.UintType:
+ enc.AddUint64Key(field.Key, uint64(field.Integer))
+
+ case logr.Float64Type, logr.Float32Type:
+ enc.AddFloat64Key(field.Key, field.Float)
+
+ default:
+ return fmt.Errorf("invalid field type: %d", field.Type)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/logr/v2/formatters/plain.go b/vendor/github.com/mattermost/logr/v2/formatters/plain.go
new file mode 100644
index 00000000..4d8af643
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/formatters/plain.go
@@ -0,0 +1,146 @@
+package formatters
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+
+ "github.com/mattermost/logr/v2"
+)
+
+// Plain is the simplest formatter, outputting only text with
+// no colors.
+type Plain struct {
+ // DisableTimestamp disables output of timestamp field.
+ DisableTimestamp bool `json:"disable_timestamp"`
+ // DisableLevel disables output of level field.
+ DisableLevel bool `json:"disable_level"`
+ // DisableMsg disables output of msg field.
+ DisableMsg bool `json:"disable_msg"`
+ // DisableFields disables output of all fields.
+ DisableFields bool `json:"disable_fields"`
+ // DisableStacktrace disables output of stack trace.
+ DisableStacktrace bool `json:"disable_stacktrace"`
+ // EnableCaller enables output of the file and line number that emitted a log record.
+ EnableCaller bool `json:"enable_caller"`
+
+ // Delim is an optional delimiter output between each log field.
+ // Defaults to a single space.
+ Delim string `json:"delim"`
+
+ // MinLevelLen sets the minimum level name length. If the level name is less
+ // than the minimum it will be padded with spaces.
+ MinLevelLen int `json:"min_level_len"`
+
+ // MinMessageLen sets the minimum msg length. If the msg text is less
+ // than the minimum it will be padded with spaces.
+ MinMessageLen int `json:"min_msg_len"`
+
+ // TimestampFormat is an optional format for timestamps. If empty
+ // then DefTimestampFormat is used.
+ TimestampFormat string `json:"timestamp_format"`
+
+ // LineEnd sets the end of line character(s). Defaults to '\n'.
+ LineEnd string `json:"line_end"`
+
+ // EnableColor sets whether output should include color.
+ EnableColor bool `json:"enable_color"`
+}
+
+func (p *Plain) CheckValid() error {
+ if p.MinMessageLen < 0 || p.MinMessageLen > 1024 {
+ return fmt.Errorf("min_msg_len is invalid(%d)", p.MinMessageLen)
+ }
+ return nil
+}
+
+// IsStacktraceNeeded returns true if a stacktrace is needed so we can output the `Caller` field.
+func (p *Plain) IsStacktraceNeeded() bool {
+ return p.EnableCaller
+}
+
+// Format converts a log record to bytes.
+func (p *Plain) Format(rec *logr.LogRec, level logr.Level, buf *bytes.Buffer) (*bytes.Buffer, error) {
+ delim := p.Delim
+ if delim == "" {
+ delim = " "
+ }
+ if buf == nil {
+ buf = &bytes.Buffer{}
+ }
+
+ timestampFmt := p.TimestampFormat
+ if timestampFmt == "" {
+ timestampFmt = logr.DefTimestampFormat
+ }
+
+ color := logr.NoColor
+ if p.EnableColor {
+ color = level.Color
+ }
+
+ if !p.DisableLevel {
+ _ = logr.WriteWithColor(buf, level.Name, color)
+ count := len(level.Name)
+ if p.MinLevelLen > count {
+ _, _ = buf.WriteString(strings.Repeat(" ", p.MinLevelLen-count))
+ }
+ buf.WriteString(delim)
+ }
+
+ if !p.DisableTimestamp {
+ var arr [128]byte
+ tbuf := rec.Time().AppendFormat(arr[:0], timestampFmt)
+ buf.WriteByte('[')
+ buf.Write(tbuf)
+ buf.WriteByte(']')
+ buf.WriteString(delim)
+ }
+
+ if !p.DisableMsg {
+ count, _ := buf.WriteString(rec.Msg())
+ if p.MinMessageLen > count {
+ _, _ = buf.WriteString(strings.Repeat(" ", p.MinMessageLen-count))
+ }
+ _, _ = buf.WriteString(delim)
+ }
+
+ var fields []logr.Field
+
+ if p.EnableCaller {
+ fld := logr.Field{
+ Key: "caller",
+ Type: logr.StringType,
+ String: rec.Caller(),
+ }
+ fields = append(fields, fld)
+ }
+
+ if !p.DisableFields {
+ fields = append(fields, rec.Fields()...)
+ }
+
+ if len(fields) > 0 {
+ if err := logr.WriteFields(buf, fields, logr.Space, color); err != nil {
+ return nil, err
+ }
+ }
+
+ if level.Stacktrace && !p.DisableStacktrace {
+ frames := rec.StackFrames()
+ if len(frames) > 0 {
+ buf.WriteString("\n")
+ if err := logr.WriteStacktrace(buf, rec.StackFrames()); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if p.LineEnd == "" {
+ buf.WriteString("\n")
+ } else {
+ buf.WriteString(p.LineEnd)
+ }
+
+ return buf, nil
+}
diff --git a/vendor/github.com/mattermost/logr/v2/go.mod b/vendor/github.com/mattermost/logr/v2/go.mod
new file mode 100644
index 00000000..4cbe375e
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/go.mod
@@ -0,0 +1,11 @@
+module github.com/mattermost/logr/v2
+
+go 1.12
+
+require (
+ github.com/francoispqt/gojay v1.2.13
+ github.com/stretchr/testify v1.4.0
+ github.com/wiggin77/merror v1.0.2
+ github.com/wiggin77/srslog v1.0.1
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0
+)
diff --git a/vendor/github.com/mattermost/logr/v2/go.sum b/vendor/github.com/mattermost/logr/v2/go.sum
new file mode 100644
index 00000000..ae504492
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/go.sum
@@ -0,0 +1,178 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
+dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
+dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
+dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
+dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
+git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
+github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
+github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
+github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
+github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
+github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
+github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
+github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
+github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
+github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
+github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
+github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
+github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
+github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
+github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
+github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
+github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
+github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
+github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
+github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
+github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
+github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
+github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
+github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
+github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
+github.com/wiggin77/merror v1.0.2 h1:V0nH9eFp64ASyaXC+pB5WpvBoCg7NUwvaCSKdzlcHqw=
+github.com/wiggin77/merror v1.0.2/go.mod h1:uQTcIU0Z6jRK4OwqganPYerzQxSFJ4GSHM3aurxxQpg=
+github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=
+github.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=
+go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
+go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
+golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
+google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
+sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
diff --git a/vendor/github.com/mattermost/logr/v2/level.go b/vendor/github.com/mattermost/logr/v2/level.go
new file mode 100644
index 00000000..643d68e3
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/level.go
@@ -0,0 +1,34 @@
+package logr
+
+var AnsiColorPrefix = []byte("\u001b[")
+var AnsiColorSuffix = []byte("m")
+
+// Color for formatters that support color output.
+type Color uint8
+
+const (
+ NoColor Color = 0
+ Red Color = 31
+ Green Color = 32
+ Yellow Color = 33
+ Blue Color = 34
+ Magenta Color = 35
+ Cyan Color = 36
+ White Color = 37
+)
+
+// LevelID is the unique id of each level.
+type LevelID uint
+
+// Level provides a mechanism to enable/disable specific log lines.
+type Level struct {
+ ID LevelID `json:"id"`
+ Name string `json:"name"`
+ Stacktrace bool `json:"stacktrace,omitempty"`
+ Color Color `json:"color,omitempty"`
+}
+
+// String returns the name of this level.
+func (level Level) String() string {
+ return level.Name
+}
diff --git a/vendor/github.com/mattermost/logr/v2/levelcache.go b/vendor/github.com/mattermost/logr/v2/levelcache.go
new file mode 100644
index 00000000..2cefb61d
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/levelcache.go
@@ -0,0 +1,98 @@
+package logr
+
+import (
+ "fmt"
+ "sync"
+)
+
+// LevelStatus represents whether a level is enabled and
+// requires a stack trace.
+type LevelStatus struct {
+ Enabled bool
+ Stacktrace bool
+ empty bool
+}
+
+type levelCache interface {
+ setup()
+ get(id LevelID) (LevelStatus, bool)
+ put(id LevelID, status LevelStatus) error
+ clear()
+}
+
+// syncMapLevelCache uses sync.Map which may better handle large concurrency
+// scenarios.
+type syncMapLevelCache struct {
+ m sync.Map
+}
+
+func (c *syncMapLevelCache) setup() {
+ c.clear()
+}
+
+func (c *syncMapLevelCache) get(id LevelID) (LevelStatus, bool) {
+ if id > MaxLevelID {
+ return LevelStatus{}, false
+ }
+ s, _ := c.m.Load(id)
+ status := s.(LevelStatus)
+ return status, !status.empty
+}
+
+func (c *syncMapLevelCache) put(id LevelID, status LevelStatus) error {
+ if id > MaxLevelID {
+ return fmt.Errorf("level id cannot exceed MaxLevelID (%d)", MaxLevelID)
+ }
+ c.m.Store(id, status)
+ return nil
+}
+
+func (c *syncMapLevelCache) clear() {
+ var i LevelID
+ for i = 0; i < MaxLevelID; i++ {
+ c.m.Store(i, LevelStatus{empty: true})
+ }
+}
+
+// arrayLevelCache using array and a mutex.
+type arrayLevelCache struct {
+ arr [MaxLevelID + 1]LevelStatus
+ mux sync.RWMutex
+}
+
+func (c *arrayLevelCache) setup() {
+ c.clear()
+}
+
+//var dummy = LevelStatus{}
+
+func (c *arrayLevelCache) get(id LevelID) (LevelStatus, bool) {
+ if id > MaxLevelID {
+ return LevelStatus{}, false
+ }
+ c.mux.RLock()
+ status := c.arr[id]
+ ok := !status.empty
+ c.mux.RUnlock()
+ return status, ok
+}
+
+func (c *arrayLevelCache) put(id LevelID, status LevelStatus) error {
+ if id > MaxLevelID {
+ return fmt.Errorf("level id cannot exceed MaxLevelID (%d)", MaxLevelID)
+ }
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ c.arr[id] = status
+ return nil
+}
+
+func (c *arrayLevelCache) clear() {
+ c.mux.Lock()
+ defer c.mux.Unlock()
+
+ for i := range c.arr {
+ c.arr[i] = LevelStatus{empty: true}
+ }
+}
diff --git a/vendor/github.com/mattermost/logr/v2/logger.go b/vendor/github.com/mattermost/logr/v2/logger.go
new file mode 100644
index 00000000..6ce9c9f0
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/logger.go
@@ -0,0 +1,99 @@
+package logr
+
+import "log"
+
+// Logger provides context for logging via fields.
+type Logger struct {
+ lgr *Logr
+ fields []Field
+}
+
+// Logr returns the `Logr` instance that created this `Logger`.
+func (logger Logger) Logr() *Logr {
+ return logger.lgr
+}
+
+// With creates a new `Logger` with any existing fields plus the new ones.
+func (logger Logger) With(fields ...Field) Logger {
+ l := Logger{lgr: logger.lgr}
+ size := len(logger.fields) + len(fields)
+ if size > 0 {
+ l.fields = make([]Field, 0, size)
+ l.fields = append(l.fields, logger.fields...)
+ l.fields = append(l.fields, fields...)
+ }
+ return l
+}
+
+// StdLogger creates a standard logger backed by this `Logr.Logger` instance.
+// All log records are emitted with the specified log level.
+func (logger Logger) StdLogger(level Level) *log.Logger {
+ return NewStdLogger(level, logger)
+}
+
+// IsLevelEnabled determines if the specified level is enabled for at least
+// one log target.
+func (logger Logger) IsLevelEnabled(level Level) bool {
+ status := logger.Logr().IsLevelEnabled(level)
+ return status.Enabled
+}
+
+// Sugar creates a new `Logger` with a less structured API. Any fields are preserved.
+func (logger Logger) Sugar(fields ...Field) Sugar {
+ return Sugar{
+ logger: logger.With(fields...),
+ }
+}
+
+// Log checks that the level matches one or more targets, and
+// if so, generates a log record that is added to the Logr queue.
+// Arguments are handled in the manner of fmt.Print.
+func (logger Logger) Log(lvl Level, msg string, fields ...Field) {
+ status := logger.lgr.IsLevelEnabled(lvl)
+ if status.Enabled {
+ rec := NewLogRec(lvl, logger, msg, fields, status.Stacktrace)
+ logger.lgr.enqueue(rec)
+ }
+}
+
+// LogM calls `Log` multiple times, one for each level provided.
+func (logger Logger) LogM(levels []Level, msg string, fields ...Field) {
+ for _, lvl := range levels {
+ logger.Log(lvl, msg, fields...)
+ }
+}
+
+// Trace is a convenience method equivalent to `Log(TraceLevel, msg, fields...)`.
+func (logger Logger) Trace(msg string, fields ...Field) {
+ logger.Log(Trace, msg, fields...)
+}
+
+// Debug is a convenience method equivalent to `Log(DebugLevel, msg, fields...)`.
+func (logger Logger) Debug(msg string, fields ...Field) {
+ logger.Log(Debug, msg, fields...)
+}
+
+// Info is a convenience method equivalent to `Log(InfoLevel, msg, fields...)`.
+func (logger Logger) Info(msg string, fields ...Field) {
+ logger.Log(Info, msg, fields...)
+}
+
+// Warn is a convenience method equivalent to `Log(WarnLevel, msg, fields...)`.
+func (logger Logger) Warn(msg string, fields ...Field) {
+ logger.Log(Warn, msg, fields...)
+}
+
+// Error is a convenience method equivalent to `Log(ErrorLevel, msg, fields...)`.
+func (logger Logger) Error(msg string, fields ...Field) {
+ logger.Log(Error, msg, fields...)
+}
+
+// Fatal is a convenience method equivalent to `Log(FatalLevel, msg, fields...)`
+func (logger Logger) Fatal(msg string, fields ...Field) {
+ logger.Log(Fatal, msg, fields...)
+}
+
+// Panic is a convenience method equivalent to `Log(PanicLevel, msg, fields...)`
+func (logger Logger) Panic(msg string, fields ...Field) {
+ logger.Log(Panic, msg, fields...)
+}
diff --git a/vendor/github.com/mattermost/logr/v2/logr.go b/vendor/github.com/mattermost/logr/v2/logr.go
new file mode 100644
index 00000000..82b2a835
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/logr.go
@@ -0,0 +1,471 @@
+package logr
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/wiggin77/merror"
+)
+
+// Logr maintains a list of log targets and accepts incoming
+// log records. Use `New` to create instances.
+type Logr struct {
+ tmux sync.RWMutex // targetHosts mutex
+ targetHosts []*TargetHost
+
+ in chan *LogRec
+ quit chan struct{} // closed by Shutdown to exit read loop
+ done chan struct{} // closed when read loop exited
+ lvlCache levelCache
+ bufferPool sync.Pool
+ options *options
+
+ metricsMux sync.RWMutex
+ metrics *metrics
+
+ shutdown int32
+}
+
+// New creates a new Logr instance with one or more options specified.
+// Some options with invalid values can cause an error to be returned,
+// however `logr.New()` using just defaults never errors.
+func New(opts ...Option) (*Logr, error) {
+ options := &options{
+ maxQueueSize: DefaultMaxQueueSize,
+ enqueueTimeout: DefaultEnqueueTimeout,
+ shutdownTimeout: DefaultShutdownTimeout,
+ flushTimeout: DefaultFlushTimeout,
+ maxPooledBuffer: DefaultMaxPooledBuffer,
+ }
+
+ lgr := &Logr{options: options}
+
+ // apply the options
+ for _, opt := range opts {
+ if err := opt(lgr); err != nil {
+ return nil, err
+ }
+ }
+ pkgName := GetLogrPackageName()
+ if pkgName != "" {
+ opt := StackFilter(pkgName, pkgName+"/targets", pkgName+"/formatters")
+ _ = opt(lgr)
+ }
+
+ lgr.in = make(chan *LogRec, lgr.options.maxQueueSize)
+ lgr.quit = make(chan struct{})
+ lgr.done = make(chan struct{})
+
+ if lgr.options.useSyncMapLevelCache {
+ lgr.lvlCache = &syncMapLevelCache{}
+ } else {
+ lgr.lvlCache = &arrayLevelCache{}
+ }
+ lgr.lvlCache.setup()
+
+ lgr.bufferPool = sync.Pool{
+ New: func() interface{} {
+ return new(bytes.Buffer)
+ },
+ }
+
+ lgr.initMetrics(lgr.options.metricsCollector, lgr.options.metricsUpdateFreqMillis)
+
+ go lgr.start()
+
+ return lgr, nil
+}
+
+// AddTarget adds a target to the logger which will receive
+// log records for outputting.
+func (lgr *Logr) AddTarget(target Target, name string, filter Filter, formatter Formatter, maxQueueSize int) error {
+ if lgr.IsShutdown() {
+ return fmt.Errorf("AddTarget called after Logr shut down")
+ }
+
+ lgr.metricsMux.RLock()
+ metrics := lgr.metrics
+ lgr.metricsMux.RUnlock()
+
+ hostOpts := targetHostOptions{
+ name: name,
+ filter: filter,
+ formatter: formatter,
+ maxQueueSize: maxQueueSize,
+ metrics: metrics,
+ }
+
+ host, err := newTargetHost(target, hostOpts)
+ if err != nil {
+ return err
+ }
+
+ lgr.tmux.Lock()
+ defer lgr.tmux.Unlock()
+
+ lgr.targetHosts = append(lgr.targetHosts, host)
+
+ lgr.ResetLevelCache()
+
+ return nil
+}
+
+// NewLogger creates a Logger using defaults. A `Logger` is light-weight
+// enough to create on-demand, but typically one or more Loggers are
+// created and re-used.
+func (lgr *Logr) NewLogger() Logger {
+ logger := Logger{lgr: lgr}
+ return logger
+}
+
+var levelStatusDisabled = LevelStatus{}
+
+// IsLevelEnabled returns true if at least one target has the specified
+// level enabled. The result is cached so that subsequent checks are fast.
+func (lgr *Logr) IsLevelEnabled(lvl Level) LevelStatus {
+ // No levels enabled after shutdown
+ if atomic.LoadInt32(&lgr.shutdown) != 0 {
+ return levelStatusDisabled
+ }
+
+ // Check cache.
+ status, ok := lgr.lvlCache.get(lvl.ID)
+ if ok {
+ return status
+ }
+
+ status = LevelStatus{}
+
+ // Cache miss; check each target.
+ lgr.tmux.RLock()
+ defer lgr.tmux.RUnlock()
+ for _, host := range lgr.targetHosts {
+ enabled, level := host.IsLevelEnabled(lvl)
+ if enabled {
+ status.Enabled = true
+ if level.Stacktrace || host.formatter.IsStacktraceNeeded() {
+ status.Stacktrace = true
+ break // if both level and stacktrace enabled then no sense checking more targets
+ }
+ }
+ }
+
+ // Cache and return the result.
+ if err := lgr.lvlCache.put(lvl.ID, status); err != nil {
+ lgr.ReportError(err)
+ return LevelStatus{}
+ }
+ return status
+}
+
+// HasTargets returns true only if at least one target exists within the lgr.
+func (lgr *Logr) HasTargets() bool {
+ lgr.tmux.RLock()
+ defer lgr.tmux.RUnlock()
+ return len(lgr.targetHosts) > 0
+}
+
+// TargetInfo provides name and type for a Target.
+type TargetInfo struct {
+ Name string
+ Type string
+}
+
+// TargetInfos enumerates all the targets added to this lgr.
+// The resulting slice represents a snapshot at time of calling.
+func (lgr *Logr) TargetInfos() []TargetInfo {
+ infos := make([]TargetInfo, 0)
+
+ lgr.tmux.RLock()
+ defer lgr.tmux.RUnlock()
+
+ for _, host := range lgr.targetHosts {
+ inf := TargetInfo{
+ Name: host.String(),
+ Type: fmt.Sprintf("%T", host.target),
+ }
+ infos = append(infos, inf)
+ }
+ return infos
+}
+
+// RemoveTargets safely removes one or more targets based on the filtering method.
+// f should return true to delete the target, false to keep it.
+// When removing a target, best effort is made to write any queued log records before
+// closing, with cxt determining how much time can be spent in total.
+// Note, keep the timeout short since this method blocks certain logging operations.
+func (lgr *Logr) RemoveTargets(cxt context.Context, f func(ti TargetInfo) bool) error {
+ errs := merror.New()
+ hosts := make([]*TargetHost, 0)
+
+ lgr.tmux.Lock()
+ defer lgr.tmux.Unlock()
+
+ for _, host := range lgr.targetHosts {
+ inf := TargetInfo{
+ Name: host.String(),
+ Type: fmt.Sprintf("%T", host.target),
+ }
+ if f(inf) {
+ if err := host.Shutdown(cxt); err != nil {
+ errs.Append(err)
+ }
+ } else {
+ hosts = append(hosts, host)
+ }
+ }
+
+ lgr.targetHosts = hosts
+ lgr.ResetLevelCache()
+
+ return errs.ErrorOrNil()
+}
+
+// ResetLevelCache resets the cached results of `IsLevelEnabled`. This is
+// called any time a Target is added or a target's level is changed.
+func (lgr *Logr) ResetLevelCache() {
+ lgr.lvlCache.clear()
+}
+
+// SetMetricsCollector sets (or resets) the metrics collector to be used for gathering
+// metrics for all targets. Only targets added after this call will use the collector.
+//
+// To ensure all targets use a collector, use the `SetMetricsCollector` option when
+// creating the Logr instead, or configure/reconfigure the Logr after calling this method.
+func (lgr *Logr) SetMetricsCollector(collector MetricsCollector, updateFreqMillis int64) {
+ lgr.initMetrics(collector, updateFreqMillis)
+}
+
+// enqueue adds a log record to the logr queue. If the queue is full then
+// this function either blocks or the log record is dropped, depending on
+// the result of calling `OnQueueFull`.
+func (lgr *Logr) enqueue(rec *LogRec) {
+ select {
+ case lgr.in <- rec:
+ default:
+ if lgr.options.onQueueFull != nil && lgr.options.onQueueFull(rec, cap(lgr.in)) {
+ return // drop the record
+ }
+ select {
+ case <-time.After(lgr.options.enqueueTimeout):
+ lgr.ReportError(fmt.Errorf("enqueue timed out for log rec [%v]", rec))
+ case lgr.in <- rec: // block until success or timeout
+ }
+ }
+}
+
+// Flush blocks while flushing the logr queue and all target queues, by
+// writing existing log records to valid targets.
+// Any attempts to add new log records will block until flush is complete.
+// `logr.FlushTimeout` determines how long flush can execute before
+// timing out. Use `IsTimeoutError` to determine if the returned error is
+// due to a timeout.
+func (lgr *Logr) Flush() error {
+ ctx, cancel := context.WithTimeout(context.Background(), lgr.options.flushTimeout)
+ defer cancel()
+ return lgr.FlushWithTimeout(ctx)
+}
+
+// Flush blocks while flushing the logr queue and all target queues, by
+// writing existing log records to valid targets.
+// Any attempts to add new log records will block until flush is complete.
+// Use `IsTimeoutError` to determine if the returned error is
+// due to a timeout.
+func (lgr *Logr) FlushWithTimeout(ctx context.Context) error {
+ if !lgr.HasTargets() {
+ return nil
+ }
+
+ if lgr.IsShutdown() {
+ return errors.New("Flush called on shut down Logr")
+ }
+
+ rec := newFlushLogRec(lgr.NewLogger())
+ lgr.enqueue(rec)
+
+ select {
+ case <-ctx.Done():
+ return newTimeoutError("logr queue flush timeout")
+ case <-rec.flush:
+ }
+ return nil
+}
+
+// IsShutdown returns true if this Logr instance has been shut down.
+// No further log records can be enqueued and no targets added after
+// shutdown.
+func (lgr *Logr) IsShutdown() bool {
+ return atomic.LoadInt32(&lgr.shutdown) != 0
+}
+
+// Shutdown cleanly stops the logging engine after making best efforts
+// to flush all targets. Call this function right before application
+// exit - logr cannot be restarted once shut down.
+// `logr.ShutdownTimeout` determines how long shutdown can execute before
+// timing out. Use `IsTimeoutError` to determine if the returned error is
+// due to a timeout.
+func (lgr *Logr) Shutdown() error {
+ ctx, cancel := context.WithTimeout(context.Background(), lgr.options.shutdownTimeout)
+ defer cancel()
+ return lgr.ShutdownWithTimeout(ctx)
+}
+
+// Shutdown cleanly stops the logging engine after making best efforts
+// to flush all targets. Call this function right before application
+// exit - logr cannot be restarted once shut down.
+// Use `IsTimeoutError` to determine if the returned error is due to a
+// timeout.
+func (lgr *Logr) ShutdownWithTimeout(ctx context.Context) error {
+ if err := lgr.FlushWithTimeout(ctx); err != nil {
+ return err
+ }
+
+ if atomic.SwapInt32(&lgr.shutdown, 1) != 0 {
+ return errors.New("Shutdown called again after shut down")
+ }
+
+ lgr.ResetLevelCache()
+ lgr.stopMetricsUpdater()
+
+ close(lgr.quit)
+
+ errs := merror.New()
+
+ // Wait for read loop to exit
+ select {
+ case <-ctx.Done():
+ errs.Append(newTimeoutError("logr queue shutdown timeout"))
+ case <-lgr.done:
+ }
+
+ // logr.in channel should now be drained to targets and no more log records
+ // can be added.
+ lgr.tmux.RLock()
+ defer lgr.tmux.RUnlock()
+ for _, host := range lgr.targetHosts {
+ err := host.Shutdown(ctx)
+ if err != nil {
+ errs.Append(err)
+ }
+ }
+ return errs.ErrorOrNil()
+}
+
+// ReportError is used to notify the host application of any internal logging errors.
+// If `OnLoggerError` is not nil, it is called with the error, otherwise the error is
+// output to `os.Stderr`.
+func (lgr *Logr) ReportError(err interface{}) {
+ lgr.incErrorCounter()
+
+ if lgr.options.onLoggerError == nil {
+ fmt.Fprintln(os.Stderr, err)
+ return
+ }
+ lgr.options.onLoggerError(fmt.Errorf("%v", err))
+}
+
+// BorrowBuffer borrows a buffer from the pool. Release the buffer to reduce garbage collection.
+func (lgr *Logr) BorrowBuffer() *bytes.Buffer {
+ if lgr.options.disableBufferPool {
+ return &bytes.Buffer{}
+ }
+ return lgr.bufferPool.Get().(*bytes.Buffer)
+}
+
+// ReleaseBuffer returns a buffer to the pool to reduce garbage collection. The buffer is only
+// retained if less than MaxPooledBuffer.
+func (lgr *Logr) ReleaseBuffer(buf *bytes.Buffer) {
+ if !lgr.options.disableBufferPool && buf.Cap() < lgr.options.maxPooledBuffer {
+ buf.Reset()
+ lgr.bufferPool.Put(buf)
+ }
+}
+
+// start selects on incoming log records until shutdown record is received.
+// Incoming log records are fanned out to all log targets.
+func (lgr *Logr) start() {
+ defer func() {
+ if r := recover(); r != nil {
+ lgr.ReportError(r)
+ go lgr.start()
+ } else {
+ close(lgr.done)
+ }
+ }()
+
+ for {
+ var rec *LogRec
+ select {
+ case rec = <-lgr.in:
+ if rec.flush != nil {
+ lgr.flush(rec.flush)
+ } else {
+ rec.prep()
+ lgr.fanout(rec)
+ }
+ case <-lgr.quit:
+ return
+ }
+ }
+}
+
+// fanout pushes a LogRec to all targets.
+func (lgr *Logr) fanout(rec *LogRec) {
+ var host *TargetHost
+ defer func() {
+ if r := recover(); r != nil {
+ lgr.ReportError(fmt.Errorf("fanout failed for target %s, %v", host.String(), r))
+ }
+ }()
+
+ var logged bool
+
+ lgr.tmux.RLock()
+ defer lgr.tmux.RUnlock()
+ for _, host = range lgr.targetHosts {
+ if enabled, _ := host.IsLevelEnabled(rec.Level()); enabled {
+ host.Log(rec)
+ logged = true
+ }
+ }
+
+ if logged {
+ lgr.incLoggedCounter()
+ }
+}
+
+// flush drains the queue and notifies when done.
+func (lgr *Logr) flush(done chan<- struct{}) {
+ // first drain the logr queue.
+loop:
+ for {
+ var rec *LogRec
+ select {
+ case rec = <-lgr.in:
+ if rec.flush == nil {
+ rec.prep()
+ lgr.fanout(rec)
+ }
+ default:
+ break loop
+ }
+ }
+
+ logger := lgr.NewLogger()
+
+ // drain all the targets; block until finished.
+ lgr.tmux.RLock()
+ defer lgr.tmux.RUnlock()
+ for _, host := range lgr.targetHosts {
+ rec := newFlushLogRec(logger)
+ host.Log(rec)
+ <-rec.flush
+ }
+ done <- struct{}{}
+}
diff --git a/vendor/github.com/mattermost/logr/v2/logrec.go b/vendor/github.com/mattermost/logr/v2/logrec.go
new file mode 100644
index 00000000..76d51b9e
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/logrec.go
@@ -0,0 +1,182 @@
+package logr
+
+import (
+ "fmt"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+)
+
+// LogRec collects raw, unformatted data to be logged.
+// TODO: pool these? how to reliably know when targets are done with them? Copy for each target?
+type LogRec struct {
+ mux sync.RWMutex
+ time time.Time
+
+ level Level
+ logger Logger
+
+ msg string
+ newline bool
+ fields []Field
+
+ stackPC []uintptr
+ stackCount int
+
+ // flushes Logr and target queues when not nil.
+ flush chan struct{}
+
+ // remaining fields calculated by `prep`
+ frames []runtime.Frame
+ fieldsAll []Field
+ caller string
+}
+
+// NewLogRec creates a new LogRec with the current time and optional stack trace.
+func NewLogRec(lvl Level, logger Logger, msg string, fields []Field, incStacktrace bool) *LogRec {
+ rec := &LogRec{time: time.Now(), logger: logger, level: lvl, msg: msg, fields: fields}
+ if incStacktrace {
+ rec.stackPC = make([]uintptr, DefaultMaxStackFrames)
+ rec.stackCount = runtime.Callers(2, rec.stackPC)
+ }
+ return rec
+}
+
+// newFlushLogRec creates a LogRec that flushes the Logr queue and
+// any target queues that support flushing.
+func newFlushLogRec(logger Logger) *LogRec {
+ return &LogRec{logger: logger, flush: make(chan struct{})}
+}
+
+// prep resolves stack trace to frames.
+func (rec *LogRec) prep() {
+ rec.mux.Lock()
+ defer rec.mux.Unlock()
+
+ // include log rec fields and logger fields added via "With"
+ rec.fieldsAll = make([]Field, 0, len(rec.fields)+len(rec.logger.fields))
+ rec.fieldsAll = append(rec.fieldsAll, rec.logger.fields...)
+ rec.fieldsAll = append(rec.fieldsAll, rec.fields...)
+
+ filter := rec.logger.lgr.options.stackFilter
+
+ // resolve stack trace
+ if rec.stackCount > 0 {
+ rec.frames = make([]runtime.Frame, 0, rec.stackCount)
+ frames := runtime.CallersFrames(rec.stackPC[:rec.stackCount])
+ for {
+ frame, more := frames.Next()
+
+ // remove all package entries that are in filter.
+ pkg := ResolvePackageName(frame.Function)
+ if _, ok := filter[pkg]; !ok && pkg != "" {
+ rec.frames = append(rec.frames, frame)
+ }
+
+ if !more {
+ break
+ }
+ }
+ }
+
+ // calc caller if stack trace provided
+ if len(rec.frames) > 0 {
+ rec.caller = calcCaller(rec.frames)
+ }
+}
+
+// WithTime returns a shallow copy of the log record while replacing
+// the time. This can be used by targets and formatters to adjust
+// the time, or take ownership of the log record.
+func (rec *LogRec) WithTime(time time.Time) *LogRec {
+ rec.mux.RLock()
+ defer rec.mux.RUnlock()
+
+ return &LogRec{
+ time: time,
+ level: rec.level,
+ logger: rec.logger,
+ msg: rec.msg,
+ newline: rec.newline,
+ fields: rec.fields,
+ stackPC: rec.stackPC,
+ stackCount: rec.stackCount,
+ frames: rec.frames,
+ }
+}
+
+// Logger returns the `Logger` that created this `LogRec`.
+func (rec *LogRec) Logger() Logger {
+ return rec.logger
+}
+
+// Time returns this log record's time stamp.
+func (rec *LogRec) Time() time.Time {
+ // no locking needed as this field is not mutated.
+ return rec.time
+}
+
+// Level returns this log record's Level.
+func (rec *LogRec) Level() Level {
+ // no locking needed as this field is not mutated.
+ return rec.level
+}
+
+// Fields returns this log record's Fields.
+func (rec *LogRec) Fields() []Field {
+ // no locking needed as this field is not mutated.
+ return rec.fieldsAll
+}
+
+// Msg returns this log record's message text.
+func (rec *LogRec) Msg() string {
+ rec.mux.RLock()
+ defer rec.mux.RUnlock()
+ return rec.msg
+}
+
+// StackFrames returns this log record's stack frames or
+// nil if no stack trace was required.
+func (rec *LogRec) StackFrames() []runtime.Frame {
+ rec.mux.RLock()
+ defer rec.mux.RUnlock()
+ return rec.frames
+}
+
+// Caller returns this log record's caller info, meaning the file and line
+// number where this log record was emitted. Returns empty string if no
+// stack trace was provided.
+func (rec *LogRec) Caller() string {
+ rec.mux.RLock()
+ defer rec.mux.RUnlock()
+ return rec.caller
+}
+
+// String returns a string representation of this log record.
+func (rec *LogRec) String() string {
+ if rec.flush != nil {
+ return "[flusher]"
+ }
+
+ f := &DefaultFormatter{}
+ buf := rec.logger.lgr.BorrowBuffer()
+ defer rec.logger.lgr.ReleaseBuffer(buf)
+ buf, _ = f.Format(rec, rec.Level(), buf)
+ return strings.TrimSpace(buf.String())
+}
+
+func calcCaller(frames []runtime.Frame) string {
+ for _, frame := range frames {
+ if frame.File == "" {
+ continue
+ }
+
+ dir, file := filepath.Split(frame.File)
+ base := filepath.Base(dir)
+
+ return fmt.Sprintf("%s/%s:%d", base, file, frame.Line)
+ }
+ return ""
+}
diff --git a/vendor/github.com/mattermost/logr/v2/metrics.go b/vendor/github.com/mattermost/logr/v2/metrics.go
new file mode 100644
index 00000000..f4f4d67f
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/metrics.go
@@ -0,0 +1,140 @@
+package logr
+
+import "time"
+
+const (
+ DefMetricsUpdateFreqMillis = 15000 // 15 seconds
+)
+
+// Counter is a simple metrics sink that can only increment a value.
+// Implementations are external to Logr and provided via `MetricsCollector`.
+type Counter interface {
+ // Inc increments the counter by 1. Use Add to increment it by arbitrary non-negative values.
+ Inc()
+ // Add adds the given value to the counter. It panics if the value is < 0.
+ Add(float64)
+}
+
+// Gauge is a simple metrics sink that can receive values and increase or decrease.
+// Implementations are external to Logr and provided via `MetricsCollector`.
+type Gauge interface {
+ // Set sets the Gauge to an arbitrary value.
+ Set(float64)
+ // Add adds the given value to the Gauge. (The value can be negative, resulting in a decrease of the Gauge.)
+ Add(float64)
+ // Sub subtracts the given value from the Gauge. (The value can be negative, resulting in an increase of the Gauge.)
+ Sub(float64)
+}
+
+// MetricsCollector provides a way for users of this Logr package to have metrics pushed
+// in an efficient way to any backend, e.g. Prometheus.
+// For each target added to Logr, the supplied MetricsCollector will provide a Gauge
+// and Counters that will be called frequently as logging occurs.
+type MetricsCollector interface {
+ // QueueSizeGauge returns a Gauge that will be updated by the named target.
+ QueueSizeGauge(target string) (Gauge, error)
+ // LoggedCounter returns a Counter that will be incremented by the named target.
+ LoggedCounter(target string) (Counter, error)
+ // ErrorCounter returns a Counter that will be incremented by the named target.
+ ErrorCounter(target string) (Counter, error)
+ // DroppedCounter returns a Counter that will be incremented by the named target.
+ DroppedCounter(target string) (Counter, error)
+ // BlockedCounter returns a Counter that will be incremented by the named target.
+ BlockedCounter(target string) (Counter, error)
+}
+
+// TargetWithMetrics is a target that provides metrics.
+type TargetWithMetrics interface {
+ EnableMetrics(collector MetricsCollector, updateFreqMillis int64) error
+}
+
+type metrics struct {
+ collector MetricsCollector
+ updateFreqMillis int64
+ queueSizeGauge Gauge
+ loggedCounter Counter
+ errorCounter Counter
+ done chan struct{}
+}
+
+// initMetrics initializes metrics collection.
+func (lgr *Logr) initMetrics(collector MetricsCollector, updatefreq int64) {
+ lgr.stopMetricsUpdater()
+
+ if collector == nil {
+ lgr.metricsMux.Lock()
+ lgr.metrics = nil
+ lgr.metricsMux.Unlock()
+ return
+ }
+
+ metrics := &metrics{
+ collector: collector,
+ updateFreqMillis: updatefreq,
+ done: make(chan struct{}),
+ }
+ metrics.queueSizeGauge, _ = collector.QueueSizeGauge("_logr")
+ metrics.loggedCounter, _ = collector.LoggedCounter("_logr")
+ metrics.errorCounter, _ = collector.ErrorCounter("_logr")
+
+ lgr.metricsMux.Lock()
+ lgr.metrics = metrics
+ lgr.metricsMux.Unlock()
+
+ go lgr.startMetricsUpdater()
+}
+
+func (lgr *Logr) setQueueSizeGauge(val float64) {
+ lgr.metricsMux.RLock()
+ defer lgr.metricsMux.RUnlock()
+
+ if lgr.metrics != nil {
+ lgr.metrics.queueSizeGauge.Set(val)
+ }
+}
+
+func (lgr *Logr) incLoggedCounter() {
+ lgr.metricsMux.RLock()
+ defer lgr.metricsMux.RUnlock()
+
+ if lgr.metrics != nil {
+ lgr.metrics.loggedCounter.Inc()
+ }
+}
+
+func (lgr *Logr) incErrorCounter() {
+ lgr.metricsMux.RLock()
+ defer lgr.metricsMux.RUnlock()
+
+ if lgr.metrics != nil {
+ lgr.metrics.errorCounter.Inc()
+ }
+}
+
+// startMetricsUpdater updates the metrics for any polled values every `metricsUpdateFreqSecs` seconds until
+// logr is closed.
+func (lgr *Logr) startMetricsUpdater() {
+ for {
+ lgr.metricsMux.RLock()
+ metrics := lgr.metrics
+ c := metrics.done
+ lgr.metricsMux.RUnlock()
+
+ select {
+ case <-c:
+ return
+ case <-time.After(time.Duration(metrics.updateFreqMillis) * time.Millisecond):
+ lgr.setQueueSizeGauge(float64(len(lgr.in)))
+ }
+ }
+}
+
+func (lgr *Logr) stopMetricsUpdater() {
+ lgr.metricsMux.Lock()
+ defer lgr.metricsMux.Unlock()
+
+ if lgr.metrics != nil && lgr.metrics.done != nil {
+ close(lgr.metrics.done)
+ lgr.metrics.done = nil
+ }
+}
diff --git a/vendor/github.com/mattermost/logr/v2/options.go b/vendor/github.com/mattermost/logr/v2/options.go
new file mode 100644
index 00000000..638f638a
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/options.go
@@ -0,0 +1,192 @@
+package logr
+
+import (
+ "errors"
+ "time"
+)
+
+type Option func(*Logr) error
+
+type options struct {
+ maxQueueSize int
+ onLoggerError func(error)
+ onQueueFull func(rec *LogRec, maxQueueSize int) bool
+ onTargetQueueFull func(target Target, rec *LogRec, maxQueueSize int) bool
+ onExit func(code int)
+ onPanic func(err interface{})
+ enqueueTimeout time.Duration
+ shutdownTimeout time.Duration
+ flushTimeout time.Duration
+ useSyncMapLevelCache bool
+ maxPooledBuffer int
+ disableBufferPool bool
+ metricsCollector MetricsCollector
+ metricsUpdateFreqMillis int64
+ stackFilter map[string]struct{}
+}
+
+// MaxQueueSize is the maximum number of log records that can be queued.
+// If exceeded, `OnQueueFull` is called which determines if the log
+// record will be dropped or block until add is successful.
+// Defaults to DefaultMaxQueueSize.
+func MaxQueueSize(size int) Option {
+ return func(l *Logr) error {
+ if size < 0 {
+ return errors.New("size cannot be less than zero")
+ }
+ l.options.maxQueueSize = size
+ return nil
+ }
+}
+
+// OnLoggerError, when not nil, is called any time an internal
+// logging error occurs. For example, this can happen when a
+// target cannot connect to its data sink.
+func OnLoggerError(f func(error)) Option {
+ return func(l *Logr) error {
+ l.options.onLoggerError = f
+ return nil
+ }
+}
+
+// OnQueueFull, when not nil, is called on an attempt to add
+// a log record to a full Logr queue.
+// `MaxQueueSize` can be used to modify the maximum queue size.
+// This function should return quickly, with a bool indicating whether
+// the log record should be dropped (true) or block until the log record
+// is successfully added (false). If nil then blocking (false) is assumed.
+func OnQueueFull(f func(rec *LogRec, maxQueueSize int) bool) Option {
+ return func(l *Logr) error {
+ l.options.onQueueFull = f
+ return nil
+ }
+}
+
+// OnTargetQueueFull, when not nil, is called on an attempt to add
+// a log record to a full target queue provided the target supports reporting
+// this condition.
+// This function should return quickly, with a bool indicating whether
+// the log record should be dropped (true) or block until the log record
+// is successfully added (false). If nil then blocking (false) is assumed.
+func OnTargetQueueFull(f func(target Target, rec *LogRec, maxQueueSize int) bool) Option {
+ return func(l *Logr) error {
+ l.options.onTargetQueueFull = f
+ return nil
+ }
+}
+
+// OnExit, when not nil, is called when a FatalXXX style log API is called.
+// When nil, then the default behavior is to cleanly shut down this Logr and
+// call `os.Exit(code)`.
+func OnExit(f func(code int)) Option {
+ return func(l *Logr) error {
+ l.options.onExit = f
+ return nil
+ }
+}
+
+// OnPanic, when not nil, is called when a PanicXXX style log API is called.
+// When nil, then the default behavior is to cleanly shut down this Logr and
+// call `panic(err)`.
+func OnPanic(f func(err interface{})) Option {
+ return func(l *Logr) error {
+ l.options.onPanic = f
+ return nil
+ }
+}
+
+// EnqueueTimeout is the amount of time a log record can take to be queued.
+// This only applies to blocking enqueue which happen after `logr.OnQueueFull`
+// is called and returns false.
+func EnqueueTimeout(dur time.Duration) Option {
+ return func(l *Logr) error {
+ l.options.enqueueTimeout = dur
+ return nil
+ }
+}
+
+// ShutdownTimeout is the amount of time `logr.Shutdown` can execute before
+// timing out. An alternative is to use `logr.ShutdownWithContext` and supply
+// a timeout.
+func ShutdownTimeout(dur time.Duration) Option {
+ return func(l *Logr) error {
+ l.options.shutdownTimeout = dur
+ return nil
+ }
+}
+
+// FlushTimeout is the amount of time `logr.Flush` can execute before
+// timing out. An alternative is to use `logr.FlushWithContext` and supply
+// a timeout.
+func FlushTimeout(dur time.Duration) Option {
+ return func(l *Logr) error {
+ l.options.flushTimeout = dur
+ return nil
+ }
+}
+
+// UseSyncMapLevelCache can be set to true when high concurrency (e.g. >32 cores)
+// is expected. This may improve performance with large numbers of cores - benchmark
+// for your use case.
+func UseSyncMapLevelCache(use bool) Option {
+ return func(l *Logr) error {
+ l.options.useSyncMapLevelCache = use
+ return nil
+ }
+}
+
+// MaxPooledBufferSize determines the maximum size of a buffer that can be
+// pooled. To reduce allocations, the buffers needed during formatting (etc)
+// are pooled. A very large log item will grow a buffer that could stay in
+// memory indefinitely. This setting lets you control how big a pooled buffer
+// can be - anything larger will be garbage collected after use.
+// Defaults to 1MB.
+func MaxPooledBufferSize(size int) Option {
+ return func(l *Logr) error {
+ l.options.maxPooledBuffer = size
+ return nil
+ }
+}
+
+// DisableBufferPool when true disables the buffer pool. See MaxPooledBuffer.
+func DisableBufferPool(disable bool) Option {
+ return func(l *Logr) error {
+ l.options.disableBufferPool = disable
+ return nil
+ }
+}
+
+// SetMetricsCollector enables metrics collection by supplying a MetricsCollector.
+// The MetricsCollector provides counters and gauges that are updated by log targets.
+// `updateFreqMillis` determines how often polled metrics are updated. Defaults to 15000 (15 seconds)
+// and must be at least 250 so we don't peg the CPU.
+func SetMetricsCollector(collector MetricsCollector, updateFreqMillis int64) Option {
+ return func(l *Logr) error {
+ if collector == nil {
+ return errors.New("collector cannot be nil")
+ }
+ if updateFreqMillis < 250 {
+ return errors.New("updateFreqMillis cannot be less than 250")
+ }
+ l.options.metricsCollector = collector
+ l.options.metricsUpdateFreqMillis = updateFreqMillis
+ return nil
+ }
+}
+
+// StackFilter provides a list of package names to exclude from the top of
+// stack traces. The Logr packages are automatically filtered.
+func StackFilter(pkg ...string) Option {
+ return func(l *Logr) error {
+ if l.options.stackFilter == nil {
+ l.options.stackFilter = make(map[string]struct{})
+ }
+
+ for _, p := range pkg {
+ if p != "" {
+ l.options.stackFilter[p] = struct{}{}
+ }
+ }
+ return nil
+ }
+}
diff --git a/vendor/github.com/mattermost/logr/v2/pkg.go b/vendor/github.com/mattermost/logr/v2/pkg.go
new file mode 100644
index 00000000..873b2e95
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/pkg.go
@@ -0,0 +1,57 @@
+package logr
+
+import (
+ "runtime"
+ "strings"
+ "sync"
+)
+
+const (
+ maximumStackDepth int = 30
+)
+
+var (
+ logrPkg string
+ pkgCalcOnce sync.Once
+)
+
+// GetPackageName returns the root package name of Logr.
+func GetLogrPackageName() string {
+ pkgCalcOnce.Do(func() {
+ logrPkg = GetPackageName("GetLogrPackageName")
+ })
+ return logrPkg
+}
+
+// GetPackageName returns the package name of the caller.
+// `callingFuncName` should be the name of the calling function and
+// should be unique enough not to collide with any runtime methods.
+func GetPackageName(callingFuncName string) string {
+ var pkgName string
+
+ pcs := make([]uintptr, maximumStackDepth)
+ _ = runtime.Callers(0, pcs)
+
+ for _, pc := range pcs {
+ funcName := runtime.FuncForPC(pc).Name()
+ if strings.Contains(funcName, callingFuncName) {
+ pkgName = ResolvePackageName(funcName)
+ break
+ }
+ }
+ return pkgName
+}
+
+// ResolvePackageName reduces a fully qualified function name to the package name
+func ResolvePackageName(f string) string {
+ for {
+ lastPeriod := strings.LastIndex(f, ".")
+ lastSlash := strings.LastIndex(f, "/")
+ if lastPeriod > lastSlash {
+ f = f[:lastPeriod]
+ } else {
+ break
+ }
+ }
+ return f
+}
diff --git a/vendor/github.com/mattermost/logr/v2/stdlogger.go b/vendor/github.com/mattermost/logr/v2/stdlogger.go
new file mode 100644
index 00000000..50171b3d
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/stdlogger.go
@@ -0,0 +1,56 @@
+package logr
+
+import (
+ "log"
+ "os"
+ "strings"
+)
+
+// NewStdLogger creates a standard logger backed by a Logr instance.
+// All log records are emitted with the specified log level.
+func NewStdLogger(level Level, logger Logger) *log.Logger {
+ adapter := newStdLogAdapter(logger, level)
+ return log.New(adapter, "", 0)
+}
+
+// RedirectStdLog redirects output from the standard library's package-global logger
+// to this logger at the specified level and with zero or more Field's. Since Logr already
+// handles caller annotations, timestamps, etc., it automatically disables the standard
+// library's annotations and prefixing.
+// A function is returned that restores the original prefix and flags and resets the standard
+// library's output to os.Stderr.
+func (lgr *Logr) RedirectStdLog(level Level, fields ...Field) func() {
+ flags := log.Flags()
+ prefix := log.Prefix()
+ log.SetFlags(0)
+ log.SetPrefix("")
+
+ logger := lgr.NewLogger().With(fields...)
+ adapter := newStdLogAdapter(logger, level)
+ log.SetOutput(adapter)
+
+ return func() {
+ log.SetFlags(flags)
+ log.SetPrefix(prefix)
+ log.SetOutput(os.Stderr)
+ }
+}
+
+type stdLogAdapter struct {
+ logger Logger
+ level Level
+}
+
+func newStdLogAdapter(logger Logger, level Level) *stdLogAdapter {
+ return &stdLogAdapter{
+ logger: logger,
+ level: level,
+ }
+}
+
+// Write implements io.Writer
+func (a *stdLogAdapter) Write(p []byte) (int, error) {
+ s := strings.TrimSpace(string(p))
+ a.logger.Log(a.level, s)
+ return len(p), nil
+}
diff --git a/vendor/github.com/mattermost/logr/v2/sugar.go b/vendor/github.com/mattermost/logr/v2/sugar.go
new file mode 100644
index 00000000..f4f300ee
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/sugar.go
@@ -0,0 +1,119 @@
+package logr
+
+import (
+ "fmt"
+)
+
+// Sugar provides a less structured API for logging.
+type Sugar struct {
+ logger Logger
+}
+
+func (s Sugar) sugarLog(lvl Level, msg string, args ...interface{}) {
+ if s.logger.IsLevelEnabled(lvl) {
+ fields := make([]Field, 0, len(args))
+ for _, arg := range args {
+ fields = append(fields, Any("", arg))
+ }
+ s.logger.Log(lvl, msg, fields...)
+ }
+}
+
+// Trace is a convenience method equivalent to `Log(TraceLevel, msg, args...)`.
+func (s Sugar) Trace(msg string, args ...interface{}) {
+ s.sugarLog(Trace, msg, args...)
+}
+
+// Debug is a convenience method equivalent to `Log(DebugLevel, msg, args...)`.
+func (s Sugar) Debug(msg string, args ...interface{}) {
+ s.sugarLog(Debug, msg, args...)
+}
+
+// Print ensures compatibility with std lib logger.
+func (s Sugar) Print(msg string, args ...interface{}) {
+ s.Info(msg, args...)
+}
+
+// Info is a convenience method equivalent to `Log(InfoLevel, msg, args...)`.
+func (s Sugar) Info(msg string, args ...interface{}) {
+ s.sugarLog(Info, msg, args...)
+}
+
+// Warn is a convenience method equivalent to `Log(WarnLevel, msg, args...)`.
+func (s Sugar) Warn(msg string, args ...interface{}) {
+ s.sugarLog(Warn, msg, args...)
+}
+
+// Error is a convenience method equivalent to `Log(ErrorLevel, msg, args...)`.
+func (s Sugar) Error(msg string, args ...interface{}) {
+ s.sugarLog(Error, msg, args...)
+}
+
+// Fatal is a convenience method equivalent to `Log(FatalLevel, msg, args...)`
+func (s Sugar) Fatal(msg string, args ...interface{}) {
+ s.sugarLog(Fatal, msg, args...)
+}
+
+// Panic is a convenience method equivalent to `Log(PanicLevel, msg, args...)`
+func (s Sugar) Panic(msg string, args ...interface{}) {
+ s.sugarLog(Panic, msg, args...)
+}
+
+//
+// Printf style
+//
+
+// Logf checks that the level matches one or more targets, and
+// if so, generates a log record that is added to the main
+// queue (channel). Arguments are handled in the manner of fmt.Printf.
+func (s Sugar) Logf(lvl Level, format string, args ...interface{}) {
+ if s.logger.IsLevelEnabled(lvl) {
+ var msg string
+ if format == "" {
+ msg = fmt.Sprint(args...)
+ } else {
+ msg = fmt.Sprintf(format, args...)
+ }
+ s.logger.Log(lvl, msg)
+ }
+}
+
+// Tracef is a convenience method equivalent to `Logf(TraceLevel, args...)`.
+func (s Sugar) Tracef(format string, args ...interface{}) {
+ s.Logf(Trace, format, args...)
+}
+
+// Debugf is a convenience method equivalent to `Logf(DebugLevel, args...)`.
+func (s Sugar) Debugf(format string, args ...interface{}) {
+ s.Logf(Debug, format, args...)
+}
+
+// Infof is a convenience method equivalent to `Logf(InfoLevel, args...)`.
+func (s Sugar) Infof(format string, args ...interface{}) {
+ s.Logf(Info, format, args...)
+}
+
+// Printf ensures compatibility with std lib logger.
+func (s Sugar) Printf(format string, args ...interface{}) {
+ s.Infof(format, args...)
+}
+
+// Warnf is a convenience method equivalent to `Logf(WarnLevel, args...)`.
+func (s Sugar) Warnf(format string, args ...interface{}) {
+ s.Logf(Warn, format, args...)
+}
+
+// Errorf is a convenience method equivalent to `Logf(ErrorLevel, args...)`.
+func (s Sugar) Errorf(format string, args ...interface{}) {
+ s.Logf(Error, format, args...)
+}
+
+// Fatalf is a convenience method equivalent to `Logf(FatalLevel, args...)`
+func (s Sugar) Fatalf(format string, args ...interface{}) {
+ s.Logf(Fatal, format, args...)
+}
+
+// Panicf is a convenience method equivalent to `Logf(PanicLevel, args...)`
+func (s Sugar) Panicf(format string, args ...interface{}) {
+ s.Logf(Panic, format, args...)
+}
diff --git a/vendor/github.com/mattermost/logr/v2/target.go b/vendor/github.com/mattermost/logr/v2/target.go
new file mode 100644
index 00000000..fa0a9320
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/target.go
@@ -0,0 +1,304 @@
+package logr
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "sync/atomic"
+ "time"
+)
+
+// Target represents a destination for log records such as file,
+// database, TCP socket, etc.
+type Target interface {
+ // Init is called once to initialize the target.
+ Init() error
+
+ // Write outputs to this target's destination.
+ Write(p []byte, rec *LogRec) (int, error)
+
+ // Shutdown is called once to free/close any resources.
+ // Target queue is already drained when this is called.
+ Shutdown() error
+}
+
+type targetMetrics struct {
+ queueSizeGauge Gauge
+ loggedCounter Counter
+ errorCounter Counter
+ droppedCounter Counter
+ blockedCounter Counter
+}
+
+type targetHostOptions struct {
+ name string
+ filter Filter
+ formatter Formatter
+ maxQueueSize int
+ metrics *metrics
+}
+
+// TargetHost hosts and manages the lifecycle of a target.
+// Incoming log records are queued and formatted before
+// being passed to the target.
+type TargetHost struct {
+ target Target
+ name string
+
+ filter Filter
+ formatter Formatter
+
+ in chan *LogRec
+ quit chan struct{} // closed by Shutdown to exit read loop
+ done chan struct{} // closed when read loop exited
+ targetMetrics *targetMetrics
+
+ shutdown int32
+}
+
+func newTargetHost(target Target, options targetHostOptions) (*TargetHost, error) {
+ host := &TargetHost{
+ target: target,
+ name: options.name,
+ filter: options.filter,
+ formatter: options.formatter,
+ in: make(chan *LogRec, options.maxQueueSize),
+ quit: make(chan struct{}),
+ done: make(chan struct{}),
+ }
+
+ if host.name == "" {
+ host.name = fmt.Sprintf("%T", target)
+ }
+
+ if host.filter == nil {
+ host.filter = &StdFilter{Lvl: Fatal}
+ }
+ if host.formatter == nil {
+ host.formatter = &DefaultFormatter{}
+ }
+
+ err := host.initMetrics(options.metrics)
+ if err != nil {
+ return nil, err
+ }
+
+ err = target.Init()
+ if err != nil {
+ return nil, err
+ }
+
+ go host.start()
+
+ return host, nil
+}
+
+func (h *TargetHost) initMetrics(metrics *metrics) error {
+ if metrics == nil {
+ return nil
+ }
+
+ var err error
+ tmetrics := &targetMetrics{}
+
+ if tmetrics.queueSizeGauge, err = metrics.collector.QueueSizeGauge(h.name); err != nil {
+ return err
+ }
+ if tmetrics.loggedCounter, err = metrics.collector.LoggedCounter(h.name); err != nil {
+ return err
+ }
+ if tmetrics.errorCounter, err = metrics.collector.ErrorCounter(h.name); err != nil {
+ return err
+ }
+ if tmetrics.droppedCounter, err = metrics.collector.DroppedCounter(h.name); err != nil {
+ return err
+ }
+ if tmetrics.blockedCounter, err = metrics.collector.BlockedCounter(h.name); err != nil {
+ return err
+ }
+ h.targetMetrics = tmetrics
+
+ updateFreqMillis := metrics.updateFreqMillis
+ if updateFreqMillis == 0 {
+ updateFreqMillis = DefMetricsUpdateFreqMillis
+ }
+ if updateFreqMillis < 250 {
+ updateFreqMillis = 250 // don't peg the CPU
+ }
+
+ go h.startMetricsUpdater(updateFreqMillis)
+ return nil
+}
+
+// IsLevelEnabled returns true if this target should emit logs for the specified level.
+func (h *TargetHost) IsLevelEnabled(lvl Level) (enabled bool, level Level) {
+ level, enabled = h.filter.GetEnabledLevel(lvl)
+ return enabled, level
+}
+
+// Shutdown stops processing log records after making best
+// effort to flush queue.
+func (h *TargetHost) Shutdown(ctx context.Context) error {
+ if atomic.SwapInt32(&h.shutdown, 1) != 0 {
+ return errors.New("targetHost shutdown called more than once")
+ }
+
+ close(h.quit)
+
+ // No more records can be accepted; now wait for read loop to exit.
+ select {
+ case <-ctx.Done():
+ case <-h.done:
+ }
+
+ // b.in channel should now be drained.
+ return h.target.Shutdown()
+}
+
+// Log queues a log record to be output to this target's destination.
+func (h *TargetHost) Log(rec *LogRec) {
+ if atomic.LoadInt32(&h.shutdown) != 0 {
+ return
+ }
+
+ lgr := rec.Logger().Logr()
+ select {
+ case h.in <- rec:
+ default:
+ handler := lgr.options.onTargetQueueFull
+ if handler != nil && handler(h.target, rec, cap(h.in)) {
+ h.incDroppedCounter()
+ return // drop the record
+ }
+ h.incBlockedCounter()
+
+ select {
+ case <-time.After(lgr.options.enqueueTimeout):
+ lgr.ReportError(fmt.Errorf("target enqueue timeout for log rec [%v]", rec))
+ case h.in <- rec: // block until success or timeout
+ }
+ }
+}
+
+func (h *TargetHost) setQueueSizeGauge(val float64) {
+ if h.targetMetrics != nil {
+ h.targetMetrics.queueSizeGauge.Set(val)
+ }
+}
+
+func (h *TargetHost) incLoggedCounter() {
+ if h.targetMetrics != nil {
+ h.targetMetrics.loggedCounter.Inc()
+ }
+}
+
+func (h *TargetHost) incErrorCounter() {
+ if h.targetMetrics != nil {
+ h.targetMetrics.errorCounter.Inc()
+ }
+}
+
+func (h *TargetHost) incDroppedCounter() {
+ if h.targetMetrics != nil {
+ h.targetMetrics.droppedCounter.Inc()
+ }
+}
+
+func (h *TargetHost) incBlockedCounter() {
+ if h.targetMetrics != nil {
+ h.targetMetrics.blockedCounter.Inc()
+ }
+}
+
+// String returns a name for this target.
+func (h *TargetHost) String() string {
+ return h.name
+}
+
+// start accepts log records via In channel and writes to the
+// supplied target, until Done channel signaled.
+func (h *TargetHost) start() {
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Fprintln(os.Stderr, "TargetHost.start -- ", r)
+ go h.start()
+ } else {
+ close(h.done)
+ }
+ }()
+
+ for {
+ var rec *LogRec
+ select {
+ case rec = <-h.in:
+ if rec.flush != nil {
+ h.flush(rec.flush)
+ } else {
+ err := h.writeRec(rec)
+ if err != nil {
+ h.incErrorCounter()
+ rec.Logger().Logr().ReportError(err)
+ } else {
+ h.incLoggedCounter()
+ }
+ }
+ case <-h.quit:
+ return
+ }
+ }
+}
+
+func (h *TargetHost) writeRec(rec *LogRec) error {
+ level, enabled := h.filter.GetEnabledLevel(rec.Level())
+ if !enabled {
+ // how did we get here?
+ return fmt.Errorf("level %s not enabled for target %s", rec.Level().Name, h.name)
+ }
+
+ buf := rec.logger.lgr.BorrowBuffer()
+ defer rec.logger.lgr.ReleaseBuffer(buf)
+
+ buf, err := h.formatter.Format(rec, level, buf)
+ if err != nil {
+ return err
+ }
+
+ _, err = h.target.Write(buf.Bytes(), rec)
+ return err
+}
+
+// startMetricsUpdater updates the metrics for any polled values every `updateFreqMillis` seconds until
+// target is shut down.
+func (h *TargetHost) startMetricsUpdater(updateFreqMillis int64) {
+ for {
+ select {
+ case <-h.done:
+ return
+ case <-time.After(time.Duration(updateFreqMillis) * time.Millisecond):
+ h.setQueueSizeGauge(float64(len(h.in)))
+ }
+ }
+}
+
+// flush drains the queue and notifies when done.
+func (h *TargetHost) flush(done chan<- struct{}) {
+ for {
+ var rec *LogRec
+ var err error
+ select {
+ case rec = <-h.in:
+ // ignore any redundant flush records.
+ if rec.flush == nil {
+ err = h.writeRec(rec)
+ if err != nil {
+ h.incErrorCounter()
+ rec.Logger().Logr().ReportError(err)
+ }
+ }
+ default:
+ done <- struct{}{}
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/mattermost/logr/v2/targets/file.go b/vendor/github.com/mattermost/logr/v2/targets/file.go
new file mode 100644
index 00000000..71133fac
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/file.go
@@ -0,0 +1,78 @@
+package targets
+
+import (
+ "errors"
+ "io"
+
+ "github.com/mattermost/logr/v2"
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+type FileOptions struct {
+ // Filename is the file to write logs to. Backup log files will be retained
+ // in the same directory. It uses <processname>-lumberjack.log in
+ // os.TempDir() if empty.
+ Filename string `json:"filename"`
+
+ // MaxSize is the maximum size in megabytes of the log file before it gets
+ // rotated. It defaults to 100 megabytes.
+ MaxSize int `json:"max_size"`
+
+ // MaxAge is the maximum number of days to retain old log files based on the
+ // timestamp encoded in their filename. Note that a day is defined as 24
+ // hours and may not exactly correspond to calendar days due to daylight
+ // savings, leap seconds, etc. The default is not to remove old log files
+ // based on age.
+ MaxAge int `json:"max_age"`
+
+ // MaxBackups is the maximum number of old log files to retain. The default
+ // is to retain all old log files (though MaxAge may still cause them to get
+ // deleted.)
+ MaxBackups int `json:"max_backups"`
+
+ // Compress determines if the rotated log files should be compressed
+ // using gzip. The default is not to perform compression.
+ Compress bool `json:"compress"`
+}
+
+func (fo FileOptions) CheckValid() error {
+ if fo.Filename == "" {
+ return errors.New("filename cannot be empty")
+ }
+ return nil
+}
+
+// File outputs log records to a file which can be log rotated based on size or age.
+// Uses `https://github.com/natefinch/lumberjack` for rotation.
+type File struct {
+ out io.WriteCloser
+}
+
+// NewFileTarget creates a target capable of outputting log records to a rotated file.
+func NewFileTarget(opts FileOptions) *File {
+ lumber := &lumberjack.Logger{
+ Filename: opts.Filename,
+ MaxSize: opts.MaxSize,
+ MaxBackups: opts.MaxBackups,
+ MaxAge: opts.MaxAge,
+ Compress: opts.Compress,
+ }
+ f := &File{out: lumber}
+ return f
+}
+
+// Init is called once to initialize the target.
+func (f *File) Init() error {
+ return nil
+}
+
+// Write outputs bytes to this file target.
+func (f *File) Write(p []byte, rec *logr.LogRec) (int, error) {
+ return f.out.Write(p)
+}
+
+// Shutdown is called once to free/close any resources.
+// Target queue is already drained when this is called.
+func (f *File) Shutdown() error {
+ return f.out.Close()
+}
diff --git a/vendor/github.com/mattermost/logr/v2/targets/syslog.go b/vendor/github.com/mattermost/logr/v2/targets/syslog.go
new file mode 100644
index 00000000..fc3fcc5f
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/syslog.go
@@ -0,0 +1,112 @@
+// +build !windows,!nacl,!plan9
+
+package targets
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+
+ "github.com/mattermost/logr/v2"
+ syslog "github.com/wiggin77/srslog"
+)
+
+// Syslog outputs log records to local or remote syslog.
+type Syslog struct {
+ params *SyslogOptions
+ writer *syslog.Writer
+}
+
+// SyslogOptions provides parameters for dialing a syslog daemon.
+type SyslogOptions struct {
+ IP string `json:"ip,omitempty"` // deprecated
+ Host string `json:"host"`
+ Port int `json:"port"`
+ TLS bool `json:"tls"`
+ Cert string `json:"cert"`
+ Insecure bool `json:"insecure"`
+ Tag string `json:"tag"`
+}
+
+func (so SyslogOptions) CheckValid() error {
+ if so.Host == "" && so.IP == "" {
+ return errors.New("missing host")
+ }
+ if so.Port == 0 {
+ return errors.New("missing port")
+ }
+ return nil
+}
+
+// NewSyslogTarget creates a target capable of outputting log records to remote or local syslog, with or without TLS.
+func NewSyslogTarget(params *SyslogOptions) (*Syslog, error) {
+ if params == nil {
+ return nil, errors.New("params cannot be nil")
+ }
+
+ s := &Syslog{
+ params: params,
+ }
+ return s, nil
+}
+
+// Init is called once to initialize the target.
+func (s *Syslog) Init() error {
+ network := "tcp"
+ var config *tls.Config
+
+ if s.params.TLS {
+ network = "tcp+tls"
+ config = &tls.Config{InsecureSkipVerify: s.params.Insecure}
+ if s.params.Cert != "" {
+ pool, err := GetCertPool(s.params.Cert)
+ if err != nil {
+ return err
+ }
+ config.RootCAs = pool
+ }
+ }
+ raddr := fmt.Sprintf("%s:%d", s.params.IP, s.params.Port)
+ if raddr == ":0" {
+ // If no IP:port provided then connect to local syslog.
+ raddr = ""
+ network = ""
+ }
+
+ var err error
+ s.writer, err = syslog.DialWithTLSConfig(network, raddr, syslog.LOG_INFO, s.params.Tag, config)
+ return err
+}
+
+// Write outputs bytes to this file target.
+func (s *Syslog) Write(p []byte, rec *logr.LogRec) (int, error) {
+ txt := string(p)
+ n := len(txt)
+ var err error
+
+ switch rec.Level() {
+ case logr.Panic, logr.Fatal:
+ err = s.writer.Crit(txt)
+ case logr.Error:
+ err = s.writer.Err(txt)
+ case logr.Warn:
+ err = s.writer.Warning(txt)
+ case logr.Debug, logr.Trace:
+ err = s.writer.Debug(txt)
+ default:
+ // logr.Info plus all custom levels.
+ err = s.writer.Info(txt)
+ }
+
+ if err != nil {
+ n = 0
+ // syslog writer will try to reconnect.
+ }
+ return n, err
+}
+
+// Shutdown is called once to free/close any resources.
+// Target queue is already drained when this is called.
+func (s *Syslog) Shutdown() error {
+ return s.writer.Close()
+}
diff --git a/vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go b/vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go
new file mode 100644
index 00000000..e4086e96
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/syslog_unsupported.go
@@ -0,0 +1,56 @@
+// +build windows nacl plan9
+
+package targets
+
+import (
+ "errors"
+
+ "github.com/mattermost/logr/v2"
+ syslog "github.com/wiggin77/srslog"
+)
+
+const (
+ unsupported = "Syslog target is not supported on this platform."
+)
+
+// Syslog outputs log records to local or remote syslog.
+type Syslog struct {
+ params *SyslogOptions
+ writer *syslog.Writer
+}
+
+// SyslogOptions provides parameters for dialing a syslog daemon.
+type SyslogOptions struct {
+ IP string `json:"ip,omitempty"` // deprecated
+ Host string `json:"host"`
+ Port int `json:"port"`
+ TLS bool `json:"tls"`
+ Cert string `json:"cert"`
+ Insecure bool `json:"insecure"`
+ Tag string `json:"tag"`
+}
+
+func (so SyslogOptions) CheckValid() error {
+ return errors.New(unsupported)
+}
+
+// NewSyslogTarget creates a target capable of outputting log records to remote or local syslog, with or without TLS.
+func NewSyslogTarget(params *SyslogOptions) (*Syslog, error) {
+ return nil, errors.New(unsupported)
+}
+
+// Init is called once to initialize the target.
+func (s *Syslog) Init() error {
+ return errors.New(unsupported)
+}
+
+// Write outputs bytes to this file target.
+func (s *Syslog) Write(p []byte, rec *logr.LogRec) (int, error) {
+ return 0, errors.New(unsupported)
+}
+
+// Shutdown is called once to free/close any resources.
+// Target queue is already drained when this is called.
+func (s *Syslog) Shutdown() error {
+ return errors.New(unsupported)
+}
diff --git a/vendor/github.com/mattermost/logr/v2/targets/tcp.go b/vendor/github.com/mattermost/logr/v2/targets/tcp.go
new file mode 100644
index 00000000..ce73e034
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/tcp.go
@@ -0,0 +1,251 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package targets
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "net"
+ "sync"
+ "time"
+
+ "github.com/mattermost/logr/v2"
+)
+
+const (
+ DialTimeoutSecs = 30
+ WriteTimeoutSecs = 30
+ RetryBackoffMillis int64 = 100
+ MaxRetryBackoffMillis int64 = 30 * 1000 // 30 seconds
+)
+
+// Tcp outputs log records to raw socket server.
+type Tcp struct {
+ options *TcpOptions
+ addy string
+
+ mutex sync.Mutex
+ conn net.Conn
+ monitor chan struct{}
+ shutdown chan struct{}
+}
+
+// TcpOptions provides parameters for dialing a socket server.
+type TcpOptions struct {
+ IP string `json:"ip,omitempty"` // deprecated
+ Host string `json:"host"`
+ Port int `json:"port"`
+ TLS bool `json:"tls"`
+ Cert string `json:"cert"`
+ Insecure bool `json:"insecure"`
+}
+
+func (to TcpOptions) CheckValid() error {
+ if to.Host == "" && to.IP == "" {
+ return errors.New("missing host")
+ }
+ if to.Port == 0 {
+ return errors.New("missing port")
+ }
+ return nil
+}
+
+// NewTcpTarget creates a target capable of outputting log records to a raw socket, with or without TLS.
+func NewTcpTarget(options *TcpOptions) *Tcp {
+ tcp := &Tcp{
+ options: options,
+ addy: fmt.Sprintf("%s:%d", options.IP, options.Port),
+ monitor: make(chan struct{}),
+ shutdown: make(chan struct{}),
+ }
+ return tcp
+}
+
+// Init is called once to initialize the target.
+func (tcp *Tcp) Init() error {
+ return nil
+}
+
+// getConn provides a net.Conn. If a connection already exists, it is returned immediately,
+// otherwise this method blocks until a new connection is created, timeout or shutdown.
+func (tcp *Tcp) getConn(reporter func(err interface{})) (net.Conn, error) {
+ tcp.mutex.Lock()
+ defer tcp.mutex.Unlock()
+
+ if tcp.conn != nil {
+ return tcp.conn, nil
+ }
+
+ type result struct {
+ conn net.Conn
+ err error
+ }
+
+ connChan := make(chan result)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*DialTimeoutSecs)
+ defer cancel()
+
+ go func(ctx context.Context, ch chan result) {
+ conn, err := tcp.dial(ctx)
+ if err != nil {
+ reporter(fmt.Errorf("log target %s connection error: %w", tcp.String(), err))
+ return
+ }
+ tcp.conn = conn
+ tcp.monitor = make(chan struct{})
+ go monitor(tcp.conn, tcp.monitor)
+ ch <- result{conn: conn, err: err}
+ }(ctx, connChan)
+
+ select {
+ case <-tcp.shutdown:
+ return nil, errors.New("shutdown")
+ case res := <-connChan:
+ return res.conn, res.err
+ }
+}
+
+// dial connects to a TCP socket, and optionally performs a TLS handshake.
+// A non-nil context must be provided which can cancel the dial.
+func (tcp *Tcp) dial(ctx context.Context) (net.Conn, error) {
+ var dialer net.Dialer
+ dialer.Timeout = time.Second * DialTimeoutSecs
+ conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", tcp.options.IP, tcp.options.Port))
+ if err != nil {
+ return nil, err
+ }
+
+ if !tcp.options.TLS {
+ return conn, nil
+ }
+
+ tlsconfig := &tls.Config{
+ ServerName: tcp.options.IP,
+ InsecureSkipVerify: tcp.options.Insecure,
+ }
+ if tcp.options.Cert != "" {
+ pool, err := GetCertPool(tcp.options.Cert)
+ if err != nil {
+ return nil, err
+ }
+ tlsconfig.RootCAs = pool
+ }
+
+ tlsConn := tls.Client(conn, tlsconfig)
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, err
+ }
+ return tlsConn, nil
+}
+
+func (tcp *Tcp) close() error {
+ tcp.mutex.Lock()
+ defer tcp.mutex.Unlock()
+
+ var err error
+ if tcp.conn != nil {
+ close(tcp.monitor)
+ err = tcp.conn.Close()
+ tcp.conn = nil
+ }
+ return err
+}
+
+// Shutdown stops processing log records after making best effort to flush queue.
+func (tcp *Tcp) Shutdown() error {
+ err := tcp.close()
+ close(tcp.shutdown)
+ return err
+}
+
+// Write converts the log record to bytes, via the Formatter, and outputs to the socket.
+// Called by dedicated target goroutine and will block until success or shutdown.
+func (tcp *Tcp) Write(p []byte, rec *logr.LogRec) (int, error) {
+ try := 1
+ backoff := RetryBackoffMillis
+ for {
+ select {
+ case <-tcp.shutdown:
+ return 0, nil
+ default:
+ }
+
+ reporter := rec.Logger().Logr().ReportError
+
+ conn, err := tcp.getConn(reporter)
+ if err != nil {
+ reporter(fmt.Errorf("log target %s connection error: %w", tcp.String(), err))
+ backoff = tcp.sleep(backoff)
+ continue
+ }
+
+ err = conn.SetWriteDeadline(time.Now().Add(time.Second * WriteTimeoutSecs))
+ if err != nil {
+ reporter(fmt.Errorf("log target %s set write deadline error: %w", tcp.String(), err))
+ }
+
+ count, err := conn.Write(p)
+ if err == nil {
+ return count, nil
+ }
+
+ reporter(fmt.Errorf("log target %s write error: %w", tcp.String(), err))
+
+ _ = tcp.close()
+
+ backoff = tcp.sleep(backoff)
+ try++
+ }
+}
+
+// monitor continuously tries to read from the connection to detect socket close.
+// This is needed because TCP target uses a write only socket and Linux systems
+// take a long time to detect a loss of connectivity on a socket when only writing;
+// the writes simply fail without an error returned.
+func monitor(conn net.Conn, done <-chan struct{}) {
+ buf := make([]byte, 1)
+ for {
+ select {
+ case <-done:
+ return
+ case <-time.After(1 * time.Second):
+ }
+
+ err := conn.SetReadDeadline(time.Now().Add(time.Second * 30))
+ if err != nil {
+ continue
+ }
+
+ _, err = conn.Read(buf)
+
+ if errt, ok := err.(net.Error); ok && errt.Timeout() {
+ // read timeout is expected, keep looping.
+ continue
+ }
+
+ // Any other error closes the connection, forcing a reconnect.
+ conn.Close()
+ return
+ }
+}
+
+// String returns a string representation of this target.
+func (tcp *Tcp) String() string {
+ return fmt.Sprintf("TcpTarget[%s:%d]", tcp.options.IP, tcp.options.Port)
+}
+
+func (tcp *Tcp) sleep(backoff int64) int64 {
+ select {
+ case <-tcp.shutdown:
+ case <-time.After(time.Millisecond * time.Duration(backoff)):
+ }
+
+ nextBackoff := backoff + (backoff >> 1)
+ if nextBackoff > MaxRetryBackoffMillis {
+ nextBackoff = MaxRetryBackoffMillis
+ }
+ return nextBackoff
+}
diff --git a/vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem b/vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem
new file mode 100644
index 00000000..6ce8d042
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/test-tls-client-cert.pem
@@ -0,0 +1,43 @@
+-----BEGIN CERTIFICATE-----
+MIIDjzCCAnegAwIBAgIRAPYfRSwdzKopBKxYxKqslJUwDQYJKoZIhvcNAQELBQAw
+JzElMCMGA1UEAwwcTWF0dGVybW9zdCwgSW5jLiBJbnRlcm5hbCBDQTAeFw0xOTAz
+MjIwMDE0MTVaFw0yMjAzMDYwMDE0MTVaMDsxOTA3BgNVBAMTME1hdHRlcm1vc3Qs
+IEluYy4gSW50ZXJuYWwgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMjliRdmvnNL4u/Jr/M2dPwQmTJXEBY/Vq9Q
+vAU52X3tRMCPxcaFz+x6ftuvdO2NdohXGAmtx9QU5LZcvFeTDpoVEBo9A+4jtLvD
+DZYaTNLpJmoSoJHaDbdWX+OAOqyDiWS741LuiMKWHhew9QOisat2ZINPxjmAd9wE
+xthTMgzsv7MUqnMer8U5OGQ0Qy7wAmNRc+2K3qPwkxe2RUvcte50DUFNgxEginsh
+vrkOXR383vUCZfu72qu8oggjiQpyTllu5je2Ap6JLjYLkEMiMqrYADuWor/ZHwa6
+WrFqVETxWfAV5u9Eh0wZM/KKYwRQuw9y+Nans77FmUl1tVWWNN8CAwEAAaOBoTCB
+njAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBQY4Uqswyr2hO/HetZt2RDxJdTIPjBi
+BgNVHSMEWzBZgBRFZXVg2Z5tNIsWeWjBLEy2yzKbMKErpCkwJzElMCMGA1UEAwwc
+TWF0dGVybW9zdCwgSW5jLiBJbnRlcm5hbCBDQYIUEifGUOM+bIFZo1tkjZB5YGBr
+0xEwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQAEdexL30Q0zBHmPAH8
+LhdK7dbzW1CmILbxRZlKAwRN+hKRXiMW3MHIkhNuoV9Aev602Q+ja4lWsRi/ktOL
+ni1FWx5gSScgdG8JGj47dOmoT3vXKX7+umiv4rQLPDl9/DKMuv204OYJq6VT+uNU
+6C6kL157jGJEO76H4fMZ8oYsD7Sq0zjiNKtuCYii0ngH3j3gB1jACLqRgveU7MdT
+pqOV2KfY31+h8VBtkUvljNztQ9xNY8Fjmt0SMf7E3FaUcaar3ZCr70G5aU3dKbe7
+47vGOBa5tCqw4YK0jgDKid3IJQul9a3J1mSsH8Wy3to9cAV4KGZBQLnzCX15a/+v
+3yVh
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDfjCCAmagAwIBAgIUEifGUOM+bIFZo1tkjZB5YGBr0xEwDQYJKoZIhvcNAQEL
+BQAwJzElMCMGA1UEAwwcTWF0dGVybW9zdCwgSW5jLiBJbnRlcm5hbCBDQTAeFw0x
+OTAzMjEyMTI4NDNaFw0yOTAzMTgyMTI4NDNaMCcxJTAjBgNVBAMMHE1hdHRlcm1v
+c3QsIEluYy4gSW50ZXJuYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDH0Xq5rMBGpKOVWTpb5MnaJIWFP/vOtvEk+7hVrfOfe1/5x0Kk3UgAHj85
+otaEZD1Lhn/JLkEqCiE/UXMJFwJDlNcO4CkdKBSpYX4bKAqy5q/X3QwioMSNpJG1
++YYrNGBH0sgKcKjyCaLhmqYLD0xZDVOmWIYBU9jUPyXw5U0tnsVrTqGMxVkm1xCY
+krCWN1ZoUrLvL0MCZc5qpxoPTopr9UO9cqSBSuy6BVWVuEWBZhpqHt+ul8VxhzzY
+q1k4l7r2qw+/wm1iJBedTeBVeWNag8JaVfLgu+/W7oJVlPO32Po7pnvHp8iJ3b4K
+zXyVHaTX4S6Em+6LV8855TYrShzlAgMBAAGjgaEwgZ4wHQYDVR0OBBYEFEVldWDZ
+nm00ixZ5aMEsTLbLMpswMGIGA1UdIwRbMFmAFEVldWDZnm00ixZ5aMEsTLbLMpsw
+oSukKTAnMSUwIwYDVQQDDBxNYXR0ZXJtb3N0LCBJbmMuIEludGVybmFsIENBghQS
+J8ZQ4z5sgVmjW2SNkHlgYGvTETAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjAN
+BgkqhkiG9w0BAQsFAAOCAQEAPiCWFmopyAkY2T3Zyo4yaRPhX1+VOTMKJtY6EUhq
+/GHz6kzEyvCUBf0N892cibGxekrEoItY9NqO6RQRfowg+Gn5kc13z4NyL2W8/eoT
+Xy0ZvfaQbU++fQ6pVtWtMblDMU9xiYd7/MDvJpO328l1Vhcdp8kEi+lCvpy0sCRc
+PxzPhbgCMAbZEGx+4TMQd4SZKzlRxW/2fflpReh6v1Dv0VDUSYQWwsUnaLpdKHfh
+a5k0vuySYcszE4YKlY0zakeFlJfp7fBp1xTwcdW8aTfw15EicPMwTc6xxA4JJUJx
+cddu817n1nayK5u6r9Qh1oIVkr0nC9YELMMy4dpPgJ88SA==
+-----END CERTIFICATE-----
diff --git a/vendor/github.com/mattermost/logr/v2/targets/utils.go b/vendor/github.com/mattermost/logr/v2/targets/utils.go
new file mode 100644
index 00000000..6e605af2
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/utils.go
@@ -0,0 +1,33 @@
+package targets
+
+import (
+ "crypto/x509"
+ "encoding/base64"
+ "errors"
+ "io/ioutil"
+)
+
+// GetCertPool returns a x509.CertPool containing the cert(s)
+// from `cert`, which can be a path to a .pem or .crt file,
+// or a base64 encoded cert.
+func GetCertPool(cert string) (*x509.CertPool, error) {
+ if cert == "" {
+ return nil, errors.New("no cert provided")
+ }
+
+ // first treat as a file and try to read.
+ serverCert, err := ioutil.ReadFile(cert)
+ if err != nil {
+ // maybe it's a base64 encoded cert
+ serverCert, err = base64.StdEncoding.DecodeString(cert)
+ if err != nil {
+ return nil, errors.New("cert cannot be read")
+ }
+ }
+
+ pool := x509.NewCertPool()
+ if ok := pool.AppendCertsFromPEM(serverCert); ok {
+ return pool, nil
+ }
+ return nil, errors.New("cannot parse cert")
+}
diff --git a/vendor/github.com/mattermost/logr/v2/targets/writer.go b/vendor/github.com/mattermost/logr/v2/targets/writer.go
new file mode 100644
index 00000000..d9f64d76
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/targets/writer.go
@@ -0,0 +1,38 @@
+package targets
+
+import (
+ "io"
+ "io/ioutil"
+
+ "github.com/mattermost/logr/v2"
+)
+
+// Writer outputs log records to any `io.Writer`.
+type Writer struct {
+ out io.Writer
+}
+
+// NewWriterTarget creates a target capable of outputting log records to an io.Writer.
+func NewWriterTarget(out io.Writer) *Writer {
+ if out == nil {
+ out = ioutil.Discard
+ }
+ w := &Writer{out: out}
+ return w
+}
+
+// Init is called once to initialize the target.
+func (w *Writer) Init() error {
+ return nil
+}
+
+// Write outputs bytes to this file target.
+func (w *Writer) Write(p []byte, rec *logr.LogRec) (int, error) {
+ return w.out.Write(p)
+}
+
+// Shutdown is called once to free/close any resources.
+// Target queue is already drained when this is called.
+func (w *Writer) Shutdown() error {
+ return nil
+}
diff --git a/vendor/github.com/mattermost/logr/v2/timeout.go b/vendor/github.com/mattermost/logr/v2/timeout.go
new file mode 100644
index 00000000..37737bcf
--- /dev/null
+++ b/vendor/github.com/mattermost/logr/v2/timeout.go
@@ -0,0 +1,34 @@
+package logr
+
+import "github.com/wiggin77/merror"
+
+// timeoutError is returned from functions that can timeout.
+type timeoutError struct {
+ text string
+}
+
+// newTimeoutError returns a TimeoutError.
+func newTimeoutError(text string) timeoutError {
+ return timeoutError{text: text}
+}
+
+// IsTimeoutError returns true if err is a TimeoutError.
+func IsTimeoutError(err error) bool {
+ if _, ok := err.(timeoutError); ok {
+ return true
+ }
+ // if a multi-error, return true if any of the errors
+ // are TimeoutError
+ if merr, ok := err.(*merror.MError); ok {
+ for _, e := range merr.Errors() {
+ if IsTimeoutError(e) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func (err timeoutError) Error() string {
+ return err.text
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/LICENSE.txt b/vendor/github.com/mattermost/mattermost-server/v6/LICENSE.txt
new file mode 100644
index 00000000..8382687d
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/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/default.json, i18n/, 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. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/NOTICE.txt b/vendor/github.com/mattermost/mattermost-server/v6/NOTICE.txt
new file mode 100644
index 00000000..0d3d8dd9
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/NOTICE.txt
@@ -0,0 +1,4844 @@
+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.
+
+-----
+
+## Go
+
+This product uses the Go programming language by the Go authors.
+
+* HOMEPAGE:
+ * https://golang.org
+
+* LICENSE: BSD-style
+
+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.
+
+---
+
+## Masterminds/squirrel
+
+This product contains 'squirrel' by GitHub user "Masterminds".
+
+Fluent SQL generation for golang
+
+* HOMEPAGE:
+ * https://github.com/Masterminds/squirrel
+
+* LICENSE: MIT
+
+Squirrel
+The Masterminds
+Copyright (C) 2014-2015, Lann Martin
+Copyright (C) 2015-2016, Google
+Copyright (C) 2015, Matt Farina and Matt Butcher
+
+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.
+
+---
+
+## NYTimes/gziphandler
+
+This product contains 'gziphandler' by The New York Times.
+
+Go 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.
+
+---
+
+## blang/semver
+
+This product contains 'semver' by Benedikt Lang.
+
+Semantic Versioning (semver) library written in golang
+
+* HOMEPAGE:
+ * https://github.com/blang/semver
+
+* LICENSE: MIT
+
+The MIT License
+
+Copyright (c) 2014 Benedikt Lang <github at benediktlang.de>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+---
+
+## 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 2019 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 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 <james@bitmechanic.com>
+
+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 Object Storage for AI.
+
+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 Mattermost, modified (forked) from original GitHub repo 'nicksnyder/go-i18n' owned by Nick Snyder.
+
+Translate your Go program into multiple languages.
+
+* HOMEPAGE:
+ * https://github.com/mattermost/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 <dave@cheney.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---
+
+## 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 <rs@dailymotion.com>
+
+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.
+
+---
+
+## sirupsen/logrus
+
+This product contains 'logrus' by Simon Eskildsen.
+
+Structured, pluggable logging for Go.
+
+* HOMEPAGE:
+ * https://github.com/sirupsen/logrus
+
+* LICENSE: MIT
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+---
+
+## 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.
+
+---
+
+## jmoiron/sqlx
+
+This product contains 'sqlx' by Jason Moiron.
+
+general purpose extensions to golang's database/sql
+
+* HOMEPAGE:
+ * https://github.com/jmoiron/sqlx
+
+* LICENSE: MIT
+
+ Copyright (c) 2013, Jason Moiron
+
+ 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.
+
+---
+
+## 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
+
+MIT License
+
+Copyright (c) 2012-2018 Mat Ryer and 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.
+
+---
+
+## 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.
+
+---
+
+## x/text
+
+This product contains 'text' by The Go Authors.
+
+[mirror] Go text processing support
+
+* HOMEPAGE:
+ * https://github.com/golang/text
+
+* 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.
+
+---
+
+## mail
+
+This product contains 'mail' by GitHub user "go-mail", modified (forked) from original GitHub repo 'go-gomail/gomail' owned by Gomail.
+
+Actively maintained fork of gomail. The best way to send emails in Go.
+
+* HOMEPAGE:
+ * https://github.com/go-mail/mail
+
+* 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.
+
+---
+
+## go/imageproxy
+
+This product contains 'imageproxy' by Will Norris.
+
+A caching, resizing image proxy written in Go
+
+* HOMEPAGE:
+ * https://github.com/willnorris/imageproxy
+
+* 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
+
+---
+
+## oov/psd
+
+This product contains 'psd' by oov.
+
+A PSD/PSB file reader for go
+
+* HOMEPAGE:
+ * https://github.com/oov/psd
+
+* LICENSE: MIT
+
+MIT License
+
+Copyright (c) 2016 oov
+
+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.
+
+---
+
+## gopherjs
+
+This product contains 'gopherjs' by Richard Musiol.
+
+A Go code to javascript code compiler.
+
+* HOMEPAGE:
+ * https://github.com/gopherjs/gopherjs
+
+* LICENSE:
+
+Copyright (c) 2013 Richard Musiol. 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.
+
+## AWS SDK for Go
+
+This product contains 'aws-sdk' by Amazon.
+
+AWS-SDK support for the Go language.
+
+* HOMEPAGE:
+ * https://github.com/aws/aws-sdk-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
+
+ 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.
+
+
+---
+
+## semver
+
+This product contains 'semver' by Masterminds.
+
+The semver package provides the ability to work with Semantic Versions in Go.
+
+* HOMEPAGE:
+ * https://github.com/Masterminds/semver
+
+* LICENSE:
+
+Copyright (C) 2014-2019, Matt Butcher and Matt Farina
+
+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.
+
+---
+
+## Date Constraints
+
+This product contains 'dateconstraints' by Eli Yukelzon.
+
+Go library to validate a date against constraints
+
+* HOMEPAGE:
+ * https://github.com/reflog/dateconstraints
+
+* LICENSE:
+
+MIT License
+
+Copyright (c) 2020 Eli Yukelzon
+
+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.
+
+---
+
+## Archiver
+
+This product contains 'archiver' by Matthew Holt
+
+A library to handle direferen archive files (zip, rar, tar.gz...)
+
+* HOMEPAGE:
+ * https://github.com/mholt/archiver
+
+* LICENSE:
+
+MIT License
+
+Copyright (c) 2016 Matthew Holt
+
+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.
+
+---
+
+## PDF Reader library
+
+This product contains 'pdf' by the Go team and modified by Thuc Le
+
+A library to provide pdf reading support
+
+* HOMEPAGE:
+ * https://github.com/ledongthuc/pdf
+
+* 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.
+
+---
+
+## GoOse
+
+This product contains 'GoOse' by Antonio Linari
+
+A library to provide html text extraction support
+
+* HOMEPAGE:
+ * https://github.com/advancedlogic/GoOse
+
+* 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 {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.
+
+---
+
+## Docconv
+
+This product contains 'docconv' by Sajari Pty Ltd
+
+A library to provide text extraction support for different documents
+
+* HOMEPAGE:
+ * https://github.com/sajari/docconv
+
+* LICENSE:
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Sajari Pty Ltd
+
+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.
+
+---
+
+## JWT-Go
+
+This product contains `jwt-go` by Dave Grijalva
+
+* HOMEPAGE:
+ * https://github.com/dgrijalva/jwt-go
+
+* LICENSE:
+
+The MIT License (MIT)
+
+Copyright (c) 2012 Dave Grijalva
+
+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/mattermost-server/v6/model/access.go b/vendor/github.com/mattermost/mattermost-server/v6/model/access.go
new file mode 100644
index 00000000..f17c8fbe
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/access.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+)
+
+const (
+ AccessTokenGrantType = "authorization_code"
+ AccessTokenType = "bearer"
+ RefreshTokenGrantType = "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"`
+ IdToken string `json:"id_token"`
+}
+
+// IsValid validates the AccessData and returns an error if it isn't configured
+// correctly.
+func (ad *AccessData) IsValid() *AppError {
+ if ad.ClientId == "" || len(ad.ClientId) > 26 {
+ return NewAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if ad.UserId == "" || 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 ad.RedirectUri == "" || 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 (ad *AccessData) IsExpired() bool {
+
+ if ad.ExpiresAt <= 0 {
+ return false
+ }
+
+ if GetMillis() > ad.ExpiresAt {
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/analytics_row.go b/vendor/github.com/mattermost/mattermost-server/v6/model/analytics_row.go
new file mode 100644
index 00000000..72ba3a09
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/analytics_row.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type AnalyticsRow struct {
+ Name string `json:"name"`
+ Value float64 `json:"value"`
+}
+
+type AnalyticsRows []*AnalyticsRow
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/audit.go b/vendor/github.com/mattermost/mattermost-server/v6/model/audit.go
new file mode 100644
index 00000000..3e8345c7
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/audit.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/auditconv.go b/vendor/github.com/mattermost/mattermost-server/v6/model/auditconv.go
new file mode 100644
index 00000000..38b4381e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/auditconv.go
@@ -0,0 +1,713 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "github.com/francoispqt/gojay"
+)
+
+// AuditModelTypeConv converts key model types to something better suited for audit output.
+func AuditModelTypeConv(val interface{}) (newVal interface{}, converted bool) {
+ if val == nil {
+ return nil, false
+ }
+ switch v := val.(type) {
+ case *Channel:
+ return newAuditChannel(v), true
+ case *Team:
+ return newAuditTeam(v), true
+ case *User:
+ return newAuditUser(v), true
+ case *Command:
+ return newAuditCommand(v), true
+ case *CommandArgs:
+ return newAuditCommandArgs(v), true
+ case *Bot:
+ return newAuditBot(v), true
+ case *ChannelModerationPatch:
+ return newAuditChannelModerationPatch(v), true
+ case *Emoji:
+ return newAuditEmoji(v), true
+ case *FileInfo:
+ return newAuditFileInfo(v), true
+ case *Group:
+ return newAuditGroup(v), true
+ case *Job:
+ return newAuditJob(v), true
+ case *OAuthApp:
+ return newAuditOAuthApp(v), true
+ case *Post:
+ return newAuditPost(v), true
+ case *Role:
+ return newAuditRole(v), true
+ case *Scheme:
+ return newAuditScheme(v), true
+ case *SchemeRoles:
+ return newAuditSchemeRoles(v), true
+ case *Session:
+ return newAuditSession(v), true
+ case *IncomingWebhook:
+ return newAuditIncomingWebhook(v), true
+ case *OutgoingWebhook:
+ return newAuditOutgoingWebhook(v), true
+ case *RemoteCluster:
+ return newRemoteCluster(v), true
+ }
+ return val, false
+}
+
+type auditChannel struct {
+ ID string
+ Name string
+ Type ChannelType
+}
+
+// newAuditChannel creates a simplified representation of Channel for output to audit log.
+func newAuditChannel(c *Channel) auditChannel {
+ var channel auditChannel
+ if c != nil {
+ channel.ID = c.Id
+ channel.Name = c.Name
+ channel.Type = c.Type
+ }
+ return channel
+}
+
+func (c auditChannel) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", c.ID)
+ enc.StringKey("name", c.Name)
+ enc.StringKey("type", string(c.Type))
+}
+
+func (c auditChannel) IsNil() bool {
+ return false
+}
+
+type auditTeam struct {
+ ID string
+ Name string
+ Type string
+}
+
+// newAuditTeam creates a simplified representation of Team for output to audit log.
+func newAuditTeam(t *Team) auditTeam {
+ var team auditTeam
+ if t != nil {
+ team.ID = t.Id
+ team.Name = t.Name
+ team.Type = t.Type
+ }
+ return team
+}
+
+func (t auditTeam) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", t.ID)
+ enc.StringKey("name", t.Name)
+ enc.StringKey("type", t.Type)
+}
+
+func (t auditTeam) IsNil() bool {
+ return false
+}
+
+type auditUser struct {
+ ID string
+ Name string
+ Roles string
+}
+
+// newAuditUser creates a simplified representation of User for output to audit log.
+func newAuditUser(u *User) auditUser {
+ var user auditUser
+ if u != nil {
+ user.ID = u.Id
+ user.Name = u.Username
+ user.Roles = u.Roles
+ }
+ return user
+}
+
+func (u auditUser) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", u.ID)
+ enc.StringKey("name", u.Name)
+ enc.StringKey("roles", u.Roles)
+}
+
+func (u auditUser) IsNil() bool {
+ return false
+}
+
+type auditCommand struct {
+ ID string
+ CreatorID string
+ TeamID string
+ Trigger string
+ Method string
+ Username string
+ IconURL string
+ AutoComplete bool
+ AutoCompleteDesc string
+ AutoCompleteHint string
+ DisplayName string
+ Description string
+ URL string
+}
+
+// newAuditCommand creates a simplified representation of Command for output to audit log.
+func newAuditCommand(c *Command) auditCommand {
+ var cmd auditCommand
+ if c != nil {
+ cmd.ID = c.Id
+ cmd.CreatorID = c.CreatorId
+ cmd.TeamID = c.TeamId
+ cmd.Trigger = c.Trigger
+ cmd.Method = c.Method
+ cmd.Username = c.Username
+ cmd.IconURL = c.IconURL
+ cmd.AutoComplete = c.AutoComplete
+ cmd.AutoCompleteDesc = c.AutoCompleteDesc
+ cmd.AutoCompleteHint = c.AutoCompleteHint
+ cmd.DisplayName = c.DisplayName
+ cmd.Description = c.Description
+ cmd.URL = c.URL
+ }
+ return cmd
+}
+
+func (cmd auditCommand) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", cmd.ID)
+ enc.StringKey("creator_id", cmd.CreatorID)
+ enc.StringKey("team_id", cmd.TeamID)
+ enc.StringKey("trigger", cmd.Trigger)
+ enc.StringKey("method", cmd.Method)
+ enc.StringKey("username", cmd.Username)
+ enc.StringKey("icon_url", cmd.IconURL)
+ enc.BoolKey("auto_complete", cmd.AutoComplete)
+ enc.StringKey("auto_complete_desc", cmd.AutoCompleteDesc)
+ enc.StringKey("auto_complete_hint", cmd.AutoCompleteHint)
+ enc.StringKey("display", cmd.DisplayName)
+ enc.StringKey("desc", cmd.Description)
+ enc.StringKey("url", cmd.URL)
+}
+
+func (cmd auditCommand) IsNil() bool {
+ return false
+}
+
+type auditCommandArgs struct {
+ ChannelID string
+ TeamID string
+ TriggerID string
+ Command string
+}
+
+// newAuditCommandArgs creates a simplified representation of CommandArgs for output to audit log.
+func newAuditCommandArgs(ca *CommandArgs) auditCommandArgs {
+ var cmdargs auditCommandArgs
+ if ca != nil {
+ cmdargs.ChannelID = ca.ChannelId
+ cmdargs.TeamID = ca.TeamId
+ cmdargs.TriggerID = ca.TriggerId
+ cmdargs.Command = ca.Command
+ }
+ return cmdargs
+}
+
+func (ca auditCommandArgs) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("channel_id", ca.ChannelID)
+ enc.StringKey("team_id", ca.TriggerID)
+ enc.StringKey("trigger_id", ca.TeamID)
+ enc.StringKey("command", ca.Command)
+}
+
+func (ca auditCommandArgs) IsNil() bool {
+ return false
+}
+
+type auditBot struct {
+ UserID string
+ Username string
+ Displayname string
+}
+
+// newAuditBot creates a simplified representation of Bot for output to audit log.
+func newAuditBot(b *Bot) auditBot {
+ var bot auditBot
+ if b != nil {
+ bot.UserID = b.UserId
+ bot.Username = b.Username
+ bot.Displayname = b.DisplayName
+ }
+ return bot
+}
+
+func (b auditBot) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("user_id", b.UserID)
+ enc.StringKey("username", b.Username)
+ enc.StringKey("display", b.Displayname)
+}
+
+func (b auditBot) IsNil() bool {
+ return false
+}
+
+type auditChannelModerationPatch struct {
+ Name string
+ RoleGuests bool
+ RoleMembers bool
+}
+
+// newAuditChannelModerationPatch creates a simplified representation of ChannelModerationPatch for output to audit log.
+func newAuditChannelModerationPatch(p *ChannelModerationPatch) auditChannelModerationPatch {
+ var patch auditChannelModerationPatch
+ if p != nil {
+ if p.Name != nil {
+ patch.Name = *p.Name
+ }
+ if p.Roles.Guests != nil {
+ patch.RoleGuests = *p.Roles.Guests
+ }
+ if p.Roles.Members != nil {
+ patch.RoleMembers = *p.Roles.Members
+ }
+ }
+ return patch
+}
+
+func (p auditChannelModerationPatch) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("name", p.Name)
+ enc.BoolKey("role_guests", p.RoleGuests)
+ enc.BoolKey("role_members", p.RoleMembers)
+}
+
+func (p auditChannelModerationPatch) IsNil() bool {
+ return false
+}
+
+type auditEmoji struct {
+ ID string
+ Name string
+}
+
+// newAuditEmoji creates a simplified representation of Emoji for output to audit log.
+func newAuditEmoji(e *Emoji) auditEmoji {
+ var emoji auditEmoji
+ if e != nil {
+ emoji.ID = e.Id
+ emoji.Name = e.Name
+ }
+ return emoji
+}
+
+func (e auditEmoji) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", e.ID)
+ enc.StringKey("name", e.Name)
+}
+
+func (e auditEmoji) IsNil() bool {
+ return false
+}
+
+type auditFileInfo struct {
+ ID string
+ PostID string
+ Path string
+ Name string
+ Extension string
+ Size int64
+}
+
+// newAuditFileInfo creates a simplified representation of FileInfo for output to audit log.
+func newAuditFileInfo(f *FileInfo) auditFileInfo {
+ var fi auditFileInfo
+ if f != nil {
+ fi.ID = f.Id
+ fi.PostID = f.PostId
+ fi.Path = f.Path
+ fi.Name = f.Name
+ fi.Extension = f.Extension
+ fi.Size = f.Size
+ }
+ return fi
+}
+
+func (fi auditFileInfo) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", fi.ID)
+ enc.StringKey("post_id", fi.PostID)
+ enc.StringKey("path", fi.Path)
+ enc.StringKey("name", fi.Name)
+ enc.StringKey("ext", fi.Extension)
+ enc.Int64Key("size", fi.Size)
+}
+
+func (fi auditFileInfo) IsNil() bool {
+ return false
+}
+
+type auditGroup struct {
+ ID string
+ Name string
+ DisplayName string
+ Description string
+}
+
+// newAuditGroup creates a simplified representation of Group for output to audit log.
+func newAuditGroup(g *Group) auditGroup {
+ var group auditGroup
+ if g != nil {
+ group.ID = g.Id
+ if g.Name == nil {
+ group.Name = ""
+ } else {
+ group.Name = *g.Name
+ }
+ group.DisplayName = g.DisplayName
+ group.Description = g.Description
+ }
+ return group
+}
+
+func (g auditGroup) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", g.ID)
+ enc.StringKey("name", g.Name)
+ enc.StringKey("display", g.DisplayName)
+ enc.StringKey("desc", g.Description)
+}
+
+func (g auditGroup) IsNil() bool {
+ return false
+}
+
+type auditJob struct {
+ ID string
+ Type string
+ Priority int64
+ StartAt int64
+}
+
+// newAuditJob creates a simplified representation of Job for output to audit log.
+func newAuditJob(j *Job) auditJob {
+ var job auditJob
+ if j != nil {
+ job.ID = j.Id
+ job.Type = j.Type
+ job.Priority = j.Priority
+ job.StartAt = j.StartAt
+ }
+ return job
+}
+
+func (j auditJob) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", j.ID)
+ enc.StringKey("type", j.Type)
+ enc.Int64Key("priority", j.Priority)
+ enc.Int64Key("start_at", j.StartAt)
+}
+
+func (j auditJob) IsNil() bool {
+ return false
+}
+
+type auditOAuthApp struct {
+ ID string
+ CreatorID string
+ Name string
+ Description string
+ IsTrusted bool
+}
+
+// newAuditOAuthApp creates a simplified representation of OAuthApp for output to audit log.
+func newAuditOAuthApp(o *OAuthApp) auditOAuthApp {
+ var oauth auditOAuthApp
+ if o != nil {
+ oauth.ID = o.Id
+ oauth.CreatorID = o.CreatorId
+ oauth.Name = o.Name
+ oauth.Description = o.Description
+ oauth.IsTrusted = o.IsTrusted
+ }
+ return oauth
+}
+
+func (o auditOAuthApp) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", o.ID)
+ enc.StringKey("creator_id", o.CreatorID)
+ enc.StringKey("name", o.Name)
+ enc.StringKey("desc", o.Description)
+ enc.BoolKey("trusted", o.IsTrusted)
+}
+
+func (o auditOAuthApp) IsNil() bool {
+ return false
+}
+
+type auditPost struct {
+ ID string
+ ChannelID string
+ Type string
+ IsPinned bool
+}
+
+// newAuditPost creates a simplified representation of Post for output to audit log.
+func newAuditPost(p *Post) auditPost {
+ var post auditPost
+ if p != nil {
+ post.ID = p.Id
+ post.ChannelID = p.ChannelId
+ post.Type = p.Type
+ post.IsPinned = p.IsPinned
+ }
+ return post
+}
+
+func (p auditPost) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", p.ID)
+ enc.StringKey("channel_id", p.ChannelID)
+ enc.StringKey("type", p.Type)
+ enc.BoolKey("pinned", p.IsPinned)
+}
+
+func (p auditPost) IsNil() bool {
+ return false
+}
+
+type auditRole struct {
+ ID string
+ Name string
+ DisplayName string
+ Permissions []string
+ SchemeManaged bool
+ BuiltIn bool
+}
+
+// newAuditRole creates a simplified representation of Role for output to audit log.
+func newAuditRole(r *Role) auditRole {
+ var role auditRole
+ if r != nil {
+ role.ID = r.Id
+ role.Name = r.Name
+ role.DisplayName = r.DisplayName
+ role.Permissions = append(role.Permissions, r.Permissions...)
+ role.SchemeManaged = r.SchemeManaged
+ role.BuiltIn = r.BuiltIn
+ }
+ return role
+}
+
+func (r auditRole) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", r.ID)
+ enc.StringKey("name", r.Name)
+ enc.StringKey("display", r.DisplayName)
+ enc.SliceStringKey("perms", r.Permissions)
+ enc.BoolKey("schemeManaged", r.SchemeManaged)
+ enc.BoolKey("builtin", r.BuiltIn)
+}
+
+func (r auditRole) IsNil() bool {
+ return false
+}
+
+type auditScheme struct {
+ ID string
+ Name string
+ DisplayName string
+ Scope string
+}
+
+// newAuditScheme creates a simplified representation of Scheme for output to audit log.
+func newAuditScheme(s *Scheme) auditScheme {
+ var scheme auditScheme
+ if s != nil {
+ scheme.ID = s.Id
+ scheme.Name = s.Name
+ scheme.DisplayName = s.DisplayName
+ scheme.Scope = s.Scope
+ }
+ return scheme
+}
+
+func (s auditScheme) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", s.ID)
+ enc.StringKey("name", s.Name)
+ enc.StringKey("display", s.DisplayName)
+ enc.StringKey("scope", s.Scope)
+}
+
+func (s auditScheme) IsNil() bool {
+ return false
+}
+
+type auditSchemeRoles struct {
+ SchemeAdmin bool
+ SchemeUser bool
+ SchemeGuest bool
+}
+
+// newAuditSchemeRoles creates a simplified representation of SchemeRoles for output to audit log.
+func newAuditSchemeRoles(s *SchemeRoles) auditSchemeRoles {
+ var roles auditSchemeRoles
+ if s != nil {
+ roles.SchemeAdmin = s.SchemeAdmin
+ roles.SchemeUser = s.SchemeUser
+ roles.SchemeGuest = s.SchemeGuest
+ }
+ return roles
+}
+
+func (s auditSchemeRoles) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.BoolKey("admin", s.SchemeAdmin)
+ enc.BoolKey("user", s.SchemeUser)
+ enc.BoolKey("guest", s.SchemeGuest)
+}
+
+func (s auditSchemeRoles) IsNil() bool {
+ return false
+}
+
+type auditSession struct {
+ ID string
+ UserId string
+ DeviceId string
+}
+
+// newAuditSession creates a simplified representation of Session for output to audit log.
+func newAuditSession(s *Session) auditSession {
+ var session auditSession
+ if s != nil {
+ session.ID = s.Id
+ session.UserId = s.UserId
+ session.DeviceId = s.DeviceId
+ }
+ return session
+}
+
+func (s auditSession) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", s.ID)
+ enc.StringKey("user_id", s.UserId)
+ enc.StringKey("device_id", s.DeviceId)
+}
+
+func (s auditSession) IsNil() bool {
+ return false
+}
+
+type auditIncomingWebhook struct {
+ ID string
+ ChannelID string
+ TeamId string
+ DisplayName string
+ Description string
+}
+
+// newAuditIncomingWebhook creates a simplified representation of IncomingWebhook for output to audit log.
+func newAuditIncomingWebhook(h *IncomingWebhook) auditIncomingWebhook {
+ var hook auditIncomingWebhook
+ if h != nil {
+ hook.ID = h.Id
+ hook.ChannelID = h.ChannelId
+ hook.TeamId = h.TeamId
+ hook.DisplayName = h.DisplayName
+ hook.Description = h.Description
+ }
+ return hook
+}
+
+func (h auditIncomingWebhook) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", h.ID)
+ enc.StringKey("channel_id", h.ChannelID)
+ enc.StringKey("team_id", h.TeamId)
+ enc.StringKey("display", h.DisplayName)
+ enc.StringKey("desc", h.Description)
+}
+
+func (h auditIncomingWebhook) IsNil() bool {
+ return false
+}
+
+type auditOutgoingWebhook struct {
+ ID string
+ ChannelID string
+ TeamID string
+ TriggerWords StringArray
+ TriggerWhen int
+ DisplayName string
+ Description string
+ ContentType string
+ Username string
+}
+
+// newAuditOutgoingWebhook creates a simplified representation of OutgoingWebhook for output to audit log.
+func newAuditOutgoingWebhook(h *OutgoingWebhook) auditOutgoingWebhook {
+ var hook auditOutgoingWebhook
+ if h != nil {
+ hook.ID = h.Id
+ hook.ChannelID = h.ChannelId
+ hook.TeamID = h.TeamId
+ hook.TriggerWords = h.TriggerWords
+ hook.TriggerWhen = h.TriggerWhen
+ hook.DisplayName = h.DisplayName
+ hook.Description = h.Description
+ hook.ContentType = h.ContentType
+ hook.Username = h.Username
+ }
+ return hook
+}
+
+func (h auditOutgoingWebhook) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("id", h.ID)
+ enc.StringKey("channel_id", h.ChannelID)
+ enc.StringKey("team_id", h.TeamID)
+ enc.SliceStringKey("trigger_words", h.TriggerWords)
+ enc.IntKey("trigger_when", h.TriggerWhen)
+ enc.StringKey("display", h.DisplayName)
+ enc.StringKey("desc", h.Description)
+ enc.StringKey("content_type", h.ContentType)
+ enc.StringKey("username", h.Username)
+}
+
+func (h auditOutgoingWebhook) IsNil() bool {
+ return false
+}
+
+type auditRemoteCluster struct {
+ RemoteId string
+ RemoteTeamId string
+ Name string
+ DisplayName string
+ SiteURL string
+ CreateAt int64
+ LastPingAt int64
+ CreatorId string
+}
+
+// newRemoteCluster creates a simplified representation of RemoteCluster for output to audit log.
+func newRemoteCluster(r *RemoteCluster) auditRemoteCluster {
+ var rc auditRemoteCluster
+ if r != nil {
+ rc.RemoteId = r.RemoteId
+ rc.RemoteTeamId = r.RemoteTeamId
+ rc.Name = r.Name
+ rc.DisplayName = r.DisplayName
+ rc.SiteURL = r.SiteURL
+ rc.CreateAt = r.CreateAt
+ rc.LastPingAt = r.LastPingAt
+ rc.CreatorId = r.CreatorId
+ }
+ return rc
+}
+
+func (r auditRemoteCluster) MarshalJSONObject(enc *gojay.Encoder) {
+ enc.StringKey("remote_id", r.RemoteId)
+ enc.StringKey("remote_team_id", r.RemoteTeamId)
+ enc.StringKey("name", r.Name)
+ enc.StringKey("display_name", r.DisplayName)
+ enc.StringKey("site_url", r.SiteURL)
+ enc.Int64Key("create_at", r.CreateAt)
+ enc.Int64Key("last_ping_at", r.LastPingAt)
+ enc.StringKey("creator_id", r.CreatorId)
+}
+
+func (r auditRemoteCluster) IsNil() bool {
+ return false
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/audits.go b/vendor/github.com/mattermost/mattermost-server/v6/model/audits.go
new file mode 100644
index 00000000..1c547c89
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/audits.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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)
+ }
+ return ""
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/authorize.go b/vendor/github.com/mattermost/mattermost-server/v6/model/authorize.go
new file mode 100644
index 00000000..1a767e3c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/authorize.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+)
+
+const (
+ AuthCodeExpireTime = 60 * 10 // 10 minutes
+ AuthCodeResponseType = "code"
+ ImplicitResponseType = "token"
+ DefaultScope = "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 !IsValidId(ad.ClientId) {
+ return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(ad.UserId) {
+ return NewAppError("AuthData.IsValid", "model.authorize.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if ad.Code == "" || 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 !IsValidId(ar.ClientId) {
+ return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if ar.ResponseType == "" {
+ return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if ar.RedirectURI == "" || 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 = AuthCodeExpireTime
+ }
+
+ if ad.CreateAt == 0 {
+ ad.CreateAt = GetMillis()
+ }
+
+ if ad.Scope == "" {
+ ad.Scope = DefaultScope
+ }
+}
+
+func (ad *AuthData) IsExpired() bool {
+ return GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/bot.go b/vendor/github.com/mattermost/mattermost-server/v6/model/bot.go
new file mode 100644
index 00000000..fe9b9078
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/bot.go
@@ -0,0 +1,204 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "unicode/utf8"
+)
+
+const (
+ BotDisplayNameMaxRunes = UserFirstNameMaxRunes
+ BotDescriptionMaxRunes = 1024
+ BotCreatorIdMaxRunes = KeyValuePluginIdMaxRunes // UserId or PluginId
+ BotWarnMetricBotUsername = "mattermost-advisor"
+ BotSystemBotUsername = "system-bot"
+)
+
+// Bot is a special type of User meant for programmatic interactions.
+// Note that the primary key of a bot is the UserId, and matches the primary key of the
+// corresponding user.
+type Bot struct {
+ UserId string `json:"user_id"`
+ Username string `json:"username"`
+ DisplayName string `json:"display_name,omitempty"`
+ Description string `json:"description,omitempty"`
+ OwnerId string `json:"owner_id"`
+ LastIconUpdate int64 `json:"last_icon_update,omitempty"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+}
+
+// BotPatch is a description of what fields to update on an existing bot.
+type BotPatch struct {
+ Username *string `json:"username"`
+ DisplayName *string `json:"display_name"`
+ Description *string `json:"description"`
+}
+
+// BotGetOptions acts as a filter on bulk bot fetching queries.
+type BotGetOptions struct {
+ OwnerId string
+ IncludeDeleted bool
+ OnlyOrphaned bool
+ Page int
+ PerPage int
+}
+
+// BotList is a list of bots.
+type BotList []*Bot
+
+// Trace describes the minimum information required to identify a bot for the purpose of logging.
+func (b *Bot) Trace() map[string]interface{} {
+ return map[string]interface{}{"user_id": b.UserId}
+}
+
+// Clone returns a shallow copy of the bot.
+func (b *Bot) Clone() *Bot {
+ copy := *b
+ return &copy
+}
+
+// IsValid validates the bot and returns an error if it isn't configured correctly.
+func (b *Bot) IsValid() *AppError {
+ if !IsValidId(b.UserId) {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ if !IsValidUsername(b.Username) {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.username.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(b.DisplayName) > BotDisplayNameMaxRunes {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(b.Description) > BotDescriptionMaxRunes {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.description.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ if b.OwnerId == "" || utf8.RuneCountInString(b.OwnerId) > BotCreatorIdMaxRunes {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.creator_id.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ if b.CreateAt == 0 {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.create_at.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ if b.UpdateAt == 0 {
+ return NewAppError("Bot.IsValid", "model.bot.is_valid.update_at.app_error", b.Trace(), "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+// PreSave should be run before saving a new bot to the database.
+func (b *Bot) PreSave() {
+ b.CreateAt = GetMillis()
+ b.UpdateAt = b.CreateAt
+ b.DeleteAt = 0
+}
+
+// PreUpdate should be run before saving an updated bot to the database.
+func (b *Bot) PreUpdate() {
+ b.UpdateAt = GetMillis()
+}
+
+// Etag generates an etag for caching.
+func (b *Bot) Etag() string {
+ return Etag(b.UserId, b.UpdateAt)
+}
+
+// Patch modifies an existing bot with optional fields from the given patch.
+// TODO 6.0: consider returning a boolean to indicate whether or not the patch
+// applied any changes.
+func (b *Bot) Patch(patch *BotPatch) {
+ if patch.Username != nil {
+ b.Username = *patch.Username
+ }
+
+ if patch.DisplayName != nil {
+ b.DisplayName = *patch.DisplayName
+ }
+
+ if patch.Description != nil {
+ b.Description = *patch.Description
+ }
+}
+
+// WouldPatch returns whether or not the given patch would be applied or not.
+func (b *Bot) WouldPatch(patch *BotPatch) bool {
+ if patch == nil {
+ return false
+ }
+ if patch.Username != nil && *patch.Username != b.Username {
+ return true
+ }
+ if patch.DisplayName != nil && *patch.DisplayName != b.DisplayName {
+ return true
+ }
+ if patch.Description != nil && *patch.Description != b.Description {
+ return true
+ }
+ return false
+}
+
+// UserFromBot returns a user model describing the bot fields stored in the User store.
+func UserFromBot(b *Bot) *User {
+ return &User{
+ Id: b.UserId,
+ Username: b.Username,
+ Email: NormalizeEmail(fmt.Sprintf("%s@localhost", b.Username)),
+ FirstName: b.DisplayName,
+ Roles: SystemUserRoleId,
+ }
+}
+
+// BotFromUser returns a bot model given a user model
+func BotFromUser(u *User) *Bot {
+ return &Bot{
+ OwnerId: u.Id,
+ UserId: u.Id,
+ Username: u.Username,
+ DisplayName: u.GetDisplayName(ShowUsername),
+ }
+}
+
+// Etag computes the etag for a list of bots.
+func (l *BotList) Etag() string {
+ id := "0"
+ var t int64 = 0
+ var delta int64 = 0
+
+ for _, v := range *l {
+ if v.UpdateAt > t {
+ t = v.UpdateAt
+ id = v.UserId
+ }
+
+ }
+
+ return Etag(id, t, delta, len(*l))
+}
+
+// MakeBotNotFoundError creates the error returned when a bot does not exist, or when the user isn't allowed to query the bot.
+// The errors must the same in both cases to avoid leaking that a user is a bot.
+func MakeBotNotFoundError(userId string) *AppError {
+ return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound)
+}
+
+func IsBotDMChannel(channel *Channel, botUserID string) bool {
+ if channel.Type != ChannelTypeDirect {
+ return false
+ }
+
+ if !strings.HasPrefix(channel.Name, botUserID+"__") && !strings.HasSuffix(channel.Name, "__"+botUserID) {
+ return false
+ }
+
+ return true
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/builtin.go b/vendor/github.com/mattermost/mattermost-server/v6/model/builtin.go
new file mode 100644
index 00000000..38e01f8b
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/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/v6/model/bundle_info.go b/vendor/github.com/mattermost/mattermost-server/v6/model/bundle_info.go
new file mode 100644
index 00000000..63969de7
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/bundle_info.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "github.com/mattermost/mattermost-server/v6/shared/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/v6/model/channel.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel.go
new file mode 100644
index 00000000..75fc2680
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel.go
@@ -0,0 +1,338 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto/sha1"
+ "encoding/hex"
+ "io"
+ "net/http"
+ "sort"
+ "strings"
+ "unicode/utf8"
+)
+
+type ChannelType string
+
+const (
+ ChannelTypeOpen ChannelType = "O"
+ ChannelTypePrivate ChannelType = "P"
+ ChannelTypeDirect ChannelType = "D"
+ ChannelTypeGroup ChannelType = "G"
+
+ ChannelGroupMaxUsers = 8
+ ChannelGroupMinUsers = 3
+ DefaultChannelName = "town-square"
+ ChannelDisplayNameMaxRunes = 64
+ ChannelNameMinLength = 2
+ ChannelNameMaxLength = 64
+ ChannelHeaderMaxRunes = 1024
+ ChannelPurposeMaxRunes = 250
+ ChannelCacheSize = 25000
+
+ ChannelSortByUsername = "username"
+ ChannelSortByStatus = "status"
+)
+
+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 ChannelType `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:"-"`
+ GroupConstrained *bool `json:"group_constrained"`
+ Shared *bool `json:"shared"`
+ TotalMsgCountRoot int64 `json:"total_msg_count_root"`
+ PolicyID *string `json:"policy_id" db:"-"`
+}
+
+type ChannelWithTeamData struct {
+ Channel
+ TeamDisplayName string `json:"team_display_name"`
+ TeamName string `json:"team_name"`
+ TeamUpdateAt int64 `json:"team_update_at"`
+}
+
+type ChannelsWithCount struct {
+ Channels ChannelListWithTeamData `json:"channels"`
+ TotalCount int64 `json:"total_count"`
+}
+
+type ChannelPatch struct {
+ DisplayName *string `json:"display_name"`
+ Name *string `json:"name"`
+ Header *string `json:"header"`
+ Purpose *string `json:"purpose"`
+ GroupConstrained *bool `json:"group_constrained"`
+}
+
+type ChannelForExport struct {
+ Channel
+ TeamName string
+ SchemeName *string
+}
+
+type DirectChannelForExport struct {
+ Channel
+ Members *[]string
+}
+
+type ChannelModeration struct {
+ Name string `json:"name"`
+ Roles *ChannelModeratedRoles `json:"roles"`
+}
+
+type ChannelModeratedRoles struct {
+ Guests *ChannelModeratedRole `json:"guests"`
+ Members *ChannelModeratedRole `json:"members"`
+}
+
+type ChannelModeratedRole struct {
+ Value bool `json:"value"`
+ Enabled bool `json:"enabled"`
+}
+
+type ChannelModerationPatch struct {
+ Name *string `json:"name"`
+ Roles *ChannelModeratedRolesPatch `json:"roles"`
+}
+
+type ChannelModeratedRolesPatch struct {
+ Guests *bool `json:"guests"`
+ Members *bool `json:"members"`
+}
+
+// ChannelSearchOpts contains options for searching channels.
+//
+// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
+// ExcludeDefaultChannels will exclude the configured default channels (ex 'town-square' and 'off-topic').
+// IncludeDeleted will include channel records where DeleteAt != 0.
+// ExcludeChannelNames will exclude channels from the results by name.
+// Paginate whether to paginate the results.
+// Page page requested, if results are paginated.
+// PerPage number of results per page, if paginated.
+//
+type ChannelSearchOpts struct {
+ NotAssociatedToGroup string
+ ExcludeDefaultChannels bool
+ IncludeDeleted bool
+ Deleted bool
+ ExcludeChannelNames []string
+ TeamIds []string
+ GroupConstrained bool
+ ExcludeGroupConstrained bool
+ PolicyID string
+ ExcludePolicyConstrained bool
+ IncludePolicyID bool
+ Public bool
+ Private bool
+ Page *int
+ PerPage *int
+}
+
+type ChannelMemberCountByGroup struct {
+ GroupId string `db:"-" json:"group_id"`
+ ChannelMemberCount int64 `db:"-" json:"channel_member_count"`
+ ChannelMemberTimezonesCount int64 `db:"-" json:"channel_member_timezones_count"`
+}
+
+type ChannelOption func(channel *Channel)
+
+func WithID(ID string) ChannelOption {
+ return func(channel *Channel) {
+ channel.Id = ID
+ }
+}
+
+func (o *Channel) DeepCopy() *Channel {
+ copy := *o
+ if copy.SchemeId != nil {
+ copy.SchemeId = NewString(*o.SchemeId)
+ }
+ return &copy
+}
+
+func (o *Channel) Etag() string {
+ return Etag(o.Id, o.UpdateAt)
+}
+
+func (o *Channel) IsValid() *AppError {
+ if !IsValidId(o.Id) {
+ 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) > ChannelDisplayNameMaxRunes {
+ 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 == ChannelTypeOpen || o.Type == ChannelTypePrivate || o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup) {
+ return NewAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(o.Header) > ChannelHeaderMaxRunes {
+ return NewAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(o.Purpose) > ChannelPurposeMaxRunes {
+ 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)
+ }
+
+ userIds := strings.Split(o.Name, "__")
+ if o.Type != ChannelTypeDirect && len(userIds) == 2 && IsValidId(userIds[0]) && IsValidId(userIds[1]) {
+ return NewAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (o *Channel) PreSave() {
+ if o.Id == "" {
+ o.Id = NewId()
+ }
+
+ o.Name = SanitizeUnicode(o.Name)
+ o.DisplayName = SanitizeUnicode(o.DisplayName)
+
+ o.CreateAt = GetMillis()
+ o.UpdateAt = o.CreateAt
+ o.ExtraUpdateAt = 0
+}
+
+func (o *Channel) PreUpdate() {
+ o.UpdateAt = GetMillis()
+ o.Name = SanitizeUnicode(o.Name)
+ o.DisplayName = SanitizeUnicode(o.DisplayName)
+}
+
+func (o *Channel) IsGroupOrDirect() bool {
+ return o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup
+}
+
+func (o *Channel) IsOpen() bool {
+ return o.Type == ChannelTypeOpen
+}
+
+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
+ }
+
+ if patch.GroupConstrained != nil {
+ o.GroupConstrained = patch.GroupConstrained
+ }
+}
+
+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 (o *Channel) IsGroupConstrained() bool {
+ return o.GroupConstrained != nil && *o.GroupConstrained
+}
+
+func (o *Channel) IsShared() bool {
+ return o.Shared != nil && *o.Shared
+}
+
+func (o *Channel) GetOtherUserIdForDM(userId string) string {
+ if o.Type != ChannelTypeDirect {
+ return ""
+ }
+
+ userIds := strings.Split(o.Name, "__")
+
+ var otherUserId string
+
+ if userIds[0] != userIds[1] {
+ if userIds[0] == userId {
+ otherUserId = userIds[1]
+ } else {
+ otherUserId = userIds[0]
+ }
+ }
+
+ return otherUserId
+}
+
+func GetDMNameFromIds(userId1, userId2 string) string {
+ if userId1 > userId2 {
+ return userId2 + "__" + userId1
+ }
+ 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) > ChannelNameMaxLength {
+ name = name[:ChannelNameMaxLength]
+ }
+
+ 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/v6/model/channel_count.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_count.go
new file mode 100644
index 00000000..25329a15
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_count.go
@@ -0,0 +1,42 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto/md5"
+ "fmt"
+ "sort"
+ "strconv"
+)
+
+type ChannelCounts struct {
+ Counts map[string]int64 `json:"counts"`
+ CountsRoot map[string]int64 `json:"counts_root"`
+ UpdateTimes map[string]int64 `json:"update_times"`
+}
+
+func (o *ChannelCounts) Etag() string {
+ // we don't include CountsRoot in ETag calculation, since it's a deriviative
+ 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)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_data.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_data.go
new file mode 100644
index 00000000..083a3f44
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_data.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_list.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_list.go
new file mode 100644
index 00000000..7b86b560
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_list.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type ChannelList []*Channel
+
+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))
+}
+
+type ChannelListWithTeamData []*ChannelWithTeamData
+
+func (o *ChannelListWithTeamData) 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
+ }
+
+ if v.TeamUpdateAt > t {
+ t = v.TeamUpdateAt
+ id = v.Id
+ }
+ }
+
+ return Etag(id, t, delta, len(*o))
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member.go
new file mode 100644
index 00000000..82bc84ae
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member.go
@@ -0,0 +1,163 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "strings"
+)
+
+const (
+ ChannelNotifyDefault = "default"
+ ChannelNotifyAll = "all"
+ ChannelNotifyMention = "mention"
+ ChannelNotifyNone = "none"
+ ChannelMarkUnreadAll = "all"
+ ChannelMarkUnreadMention = "mention"
+ IgnoreChannelMentionsDefault = "default"
+ IgnoreChannelMentionsOff = "off"
+ IgnoreChannelMentionsOn = "on"
+ IgnoreChannelMentionsNotifyProp = "ignore_channel_mentions"
+)
+
+type ChannelUnread struct {
+ TeamId string `json:"team_id"`
+ ChannelId string `json:"channel_id"`
+ MsgCount int64 `json:"msg_count"`
+ MentionCount int64 `json:"mention_count"`
+ MentionCountRoot int64 `json:"mention_count_root"`
+ MsgCountRoot int64 `json:"msg_count_root"`
+ NotifyProps StringMap `json:"-"`
+}
+
+type ChannelUnreadAt struct {
+ TeamId string `json:"team_id"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ MsgCount int64 `json:"msg_count"`
+ MentionCount int64 `json:"mention_count"`
+ MentionCountRoot int64 `json:"mention_count_root"`
+ MsgCountRoot int64 `json:"msg_count_root"`
+ LastViewedAt int64 `json:"last_viewed_at"`
+ 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"`
+ MentionCountRoot int64 `json:"mention_count_root"`
+ MsgCountRoot int64 `json:"msg_count_root"`
+ NotifyProps StringMap `json:"notify_props"`
+ LastUpdateAt int64 `json:"last_update_at"`
+ SchemeGuest bool `json:"scheme_guest"`
+ SchemeUser bool `json:"scheme_user"`
+ SchemeAdmin bool `json:"scheme_admin"`
+ ExplicitRoles string `json:"explicit_roles"`
+}
+
+type ChannelMembers []ChannelMember
+
+type ChannelMemberForExport struct {
+ ChannelMember
+ ChannelName string
+ Username string
+}
+
+func (o *ChannelMember) IsValid() *AppError {
+
+ if !IsValidId(o.ChannelId) {
+ return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.UserId) {
+ return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ notifyLevel := o.NotifyProps[DesktopNotifyProp]
+ 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[MarkUnreadNotifyProp]
+ 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[PushNotifyProp]; 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[EmailNotifyProp]; 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)
+ }
+ }
+
+ if ignoreChannelMentions, ok := o.NotifyProps[IgnoreChannelMentionsNotifyProp]; ok {
+ if len(ignoreChannelMentions) > 40 || !IsIgnoreChannelMentionsValid(ignoreChannelMentions) {
+ return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.ignore_channel_mentions_value.app_error", nil, "ignore_channel_mentions="+ignoreChannelMentions, 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 (o *ChannelMember) SetChannelMuted(muted bool) {
+ if o.IsChannelMuted() {
+ o.NotifyProps[MarkUnreadNotifyProp] = ChannelMarkUnreadAll
+ } else {
+ o.NotifyProps[MarkUnreadNotifyProp] = ChannelMarkUnreadMention
+ }
+}
+
+func (o *ChannelMember) IsChannelMuted() bool {
+ return o.NotifyProps[MarkUnreadNotifyProp] == ChannelMarkUnreadMention
+}
+
+func IsChannelNotifyLevelValid(notifyLevel string) bool {
+ return notifyLevel == ChannelNotifyDefault ||
+ notifyLevel == ChannelNotifyAll ||
+ notifyLevel == ChannelNotifyMention ||
+ notifyLevel == ChannelNotifyNone
+}
+
+func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool {
+ return markUnreadLevel == ChannelMarkUnreadAll || markUnreadLevel == ChannelMarkUnreadMention
+}
+
+func IsSendEmailValid(sendEmail string) bool {
+ return sendEmail == ChannelNotifyDefault || sendEmail == "true" || sendEmail == "false"
+}
+
+func IsIgnoreChannelMentionsValid(ignoreChannelMentions string) bool {
+ return ignoreChannelMentions == IgnoreChannelMentionsOn || ignoreChannelMentions == IgnoreChannelMentionsOff || ignoreChannelMentions == IgnoreChannelMentionsDefault
+}
+
+func GetDefaultChannelNotifyProps() StringMap {
+ return StringMap{
+ DesktopNotifyProp: ChannelNotifyDefault,
+ MarkUnreadNotifyProp: ChannelMarkUnreadAll,
+ PushNotifyProp: ChannelNotifyDefault,
+ EmailNotifyProp: ChannelNotifyDefault,
+ IgnoreChannelMentionsNotifyProp: IgnoreChannelMentionsDefault,
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history.go
new file mode 100644
index 00000000..b77e0ff9
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2015-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/v6/model/channel_member_history_result.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history_result.go
new file mode 100644
index 00000000..8f43ca4e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_member_history_result.go
@@ -0,0 +1,17 @@
+// Copyright (c) 2015-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
+ IsBot bool
+ UserDeleteAt int64
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_mentions.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_mentions.go
new file mode 100644
index 00000000..eb14e8ed
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/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/v6/model/channel_search.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_search.go
new file mode 100644
index 00000000..530deb20
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_search.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const ChannelSearchDefaultLimit = 50
+
+type ChannelSearch struct {
+ Term string `json:"term"`
+ ExcludeDefaultChannels bool `json:"exclude_default_channels"`
+ NotAssociatedToGroup string `json:"not_associated_to_group"`
+ TeamIds []string `json:"team_ids"`
+ GroupConstrained bool `json:"group_constrained"`
+ ExcludeGroupConstrained bool `json:"exclude_group_constrained"`
+ ExcludePolicyConstrained bool `json:"exclude_policy_constrained"`
+ Public bool `json:"public"`
+ Private bool `json:"private"`
+ IncludeDeleted bool `json:"include_deleted"`
+ Deleted bool `json:"deleted"`
+ Page *int `json:"page,omitempty"`
+ PerPage *int `json:"per_page,omitempty"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_sidebar.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_sidebar.go
new file mode 100644
index 00000000..fd4d44ca
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_sidebar.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "regexp"
+)
+
+type SidebarCategoryType string
+type SidebarCategorySorting string
+
+const (
+ // Each sidebar category has a 'type'. System categories are Channels, Favorites and DMs
+ // All user-created categories will have type Custom
+ SidebarCategoryChannels SidebarCategoryType = "channels"
+ SidebarCategoryDirectMessages SidebarCategoryType = "direct_messages"
+ SidebarCategoryFavorites SidebarCategoryType = "favorites"
+ SidebarCategoryCustom SidebarCategoryType = "custom"
+ // Increment to use when adding/reordering things in the sidebar
+ MinimalSidebarSortDistance = 10
+ // Default Sort Orders for categories
+ DefaultSidebarSortOrderFavorites = 0
+ DefaultSidebarSortOrderChannels = DefaultSidebarSortOrderFavorites + MinimalSidebarSortDistance
+ DefaultSidebarSortOrderDMs = DefaultSidebarSortOrderChannels + MinimalSidebarSortDistance
+ // Sorting modes
+ // default for all categories except DMs (behaves like manual)
+ SidebarCategorySortDefault SidebarCategorySorting = ""
+ // sort manually
+ SidebarCategorySortManual SidebarCategorySorting = "manual"
+ // sort by recency (default for DMs)
+ SidebarCategorySortRecent SidebarCategorySorting = "recent"
+ // sort by display name alphabetically
+ SidebarCategorySortAlphabetical SidebarCategorySorting = "alpha"
+)
+
+// SidebarCategory represents the corresponding DB table
+// SortOrder is never returned to the user and only used for queries
+type SidebarCategory struct {
+ Id string `json:"id"`
+ UserId string `json:"user_id"`
+ TeamId string `json:"team_id"`
+ SortOrder int64 `json:"-"`
+ Sorting SidebarCategorySorting `json:"sorting"`
+ Type SidebarCategoryType `json:"type"`
+ DisplayName string `json:"display_name"`
+ Muted bool `json:"muted"`
+ Collapsed bool `json:"collapsed"`
+}
+
+// SidebarCategoryWithChannels combines data from SidebarCategory table with the Channel IDs that belong to that category
+type SidebarCategoryWithChannels struct {
+ SidebarCategory
+ Channels []string `json:"channel_ids"`
+}
+
+type SidebarCategoryOrder []string
+
+// OrderedSidebarCategories combines categories, their channel IDs and an array of Category IDs, sorted
+type OrderedSidebarCategories struct {
+ Categories SidebarCategoriesWithChannels `json:"categories"`
+ Order SidebarCategoryOrder `json:"order"`
+}
+
+type SidebarChannel struct {
+ ChannelId string `json:"channel_id"`
+ UserId string `json:"user_id"`
+ CategoryId string `json:"category_id"`
+ SortOrder int64 `json:"-"`
+}
+
+type SidebarChannels []*SidebarChannel
+type SidebarCategoriesWithChannels []*SidebarCategoryWithChannels
+
+var categoryIdPattern = regexp.MustCompile("(favorites|channels|direct_messages)_[a-z0-9]{26}_[a-z0-9]{26}")
+
+func IsValidCategoryId(s string) bool {
+ // Category IDs can either be regular IDs
+ if IsValidId(s) {
+ return true
+ }
+
+ // Or default categories can follow the pattern {type}_{userID}_{teamID}
+ return categoryIdPattern.MatchString(s)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_stats.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_stats.go
new file mode 100644
index 00000000..cf44d541
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_stats.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type ChannelStats struct {
+ ChannelId string `json:"channel_id"`
+ MemberCount int64 `json:"member_count"`
+ GuestCount int64 `json:"guest_count"`
+ PinnedPostCount int64 `json:"pinnedpost_count"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/channel_view.go b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_view.go
new file mode 100644
index 00000000..c34c438d
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/channel_view.go
@@ -0,0 +1,15 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type ChannelView struct {
+ ChannelId string `json:"channel_id"`
+ PrevChannelId string `json:"prev_channel_id"`
+ CollapsedThreadsSupported bool `json:"collapsed_threads_supported"`
+}
+
+type ChannelViewResponse struct {
+ Status string `json:"status"`
+ LastViewedAtTimes map[string]int64 `json:"last_viewed_at_times"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/client4.go b/vendor/github.com/mattermost/mattermost-server/v6/model/client4.go
new file mode 100644
index 00000000..615b1264
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/client4.go
@@ -0,0 +1,7690 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+const (
+ HeaderRequestId = "X-Request-ID"
+ HeaderVersionId = "X-Version-ID"
+ HeaderClusterId = "X-Cluster-ID"
+ HeaderEtagServer = "ETag"
+ HeaderEtagClient = "If-None-Match"
+ HeaderForwarded = "X-Forwarded-For"
+ HeaderRealIP = "X-Real-IP"
+ HeaderForwardedProto = "X-Forwarded-Proto"
+ HeaderToken = "token"
+ HeaderCsrfToken = "X-CSRF-Token"
+ HeaderBearer = "BEARER"
+ HeaderAuth = "Authorization"
+ HeaderCloudToken = "X-Cloud-Token"
+ HeaderRemoteclusterToken = "X-RemoteCluster-Token"
+ HeaderRemoteclusterId = "X-RemoteCluster-Id"
+ HeaderRequestedWith = "X-Requested-With"
+ HeaderRequestedWithXML = "XMLHttpRequest"
+ HeaderRange = "Range"
+ STATUS = "status"
+ StatusOk = "OK"
+ StatusFail = "FAIL"
+ StatusUnhealthy = "UNHEALTHY"
+ StatusRemove = "REMOVE"
+
+ ClientDir = "client"
+
+ APIURLSuffixV1 = "/api/v1"
+ APIURLSuffixV4 = "/api/v4"
+ APIURLSuffix = APIURLSuffixV4
+)
+
+type Response struct {
+ StatusCode int
+ 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
+
+ // TrueString is the string value sent to the server for true boolean query parameters.
+ trueString string
+
+ // FalseString is the string value sent to the server for false boolean query parameters.
+ falseString string
+}
+
+// SetBoolString is a helper method for overriding how true and false query string parameters are
+// sent to the server.
+//
+// This method is only exposed for testing. It is never necessary to configure these values
+// in production.
+func (c *Client4) SetBoolString(value bool, valueStr string) {
+ if value {
+ c.trueString = valueStr
+ } else {
+ c.falseString = valueStr
+ }
+}
+
+// boolString builds the query string parameter for boolean values.
+func (c *Client4) boolString(value bool) string {
+ if value && c.trueString != "" {
+ return c.trueString
+ } else if !value && c.falseString != "" {
+ return c.falseString
+ }
+
+ if value {
+ return "true"
+ }
+ return "false"
+}
+
+func closeBody(r *http.Response) {
+ if r.Body != nil {
+ _, _ = io.Copy(ioutil.Discard, r.Body)
+ _ = r.Body.Close()
+ }
+}
+
+func NewAPIv4Client(url string) *Client4 {
+ url = strings.TrimRight(url, "/")
+ return &Client4{url, url + APIURLSuffix, &http.Client{}, "", "", map[string]string{}, "", ""}
+}
+
+func NewAPIv4SocketClient(socketPath string) *Client4 {
+ tr := &http.Transport{
+ Dial: func(network, addr string) (net.Conn, error) {
+ return net.Dial("unix", socketPath)
+ },
+ }
+
+ client := NewAPIv4Client("http://_")
+ client.HTTPClient = &http.Client{Transport: tr}
+
+ return client
+}
+
+func BuildResponse(r *http.Response) *Response {
+ if r == nil {
+ return nil
+ }
+
+ return &Response{
+ StatusCode: r.StatusCode,
+ RequestId: r.Header.Get(HeaderRequestId),
+ Etag: r.Header.Get(HeaderEtagServer),
+ ServerVersion: r.Header.Get(HeaderVersionId),
+ Header: r.Header,
+ }
+}
+
+func (c *Client4) SetToken(token string) {
+ c.AuthToken = token
+ c.AuthType = HeaderBearer
+}
+
+// MockSession is deprecated in favour of SetToken
+func (c *Client4) MockSession(token string) {
+ c.SetToken(token)
+}
+
+func (c *Client4) SetOAuthToken(token string) {
+ c.AuthToken = token
+ c.AuthType = HeaderToken
+}
+
+func (c *Client4) ClearOAuthToken() {
+ c.AuthToken = ""
+ c.AuthType = HeaderBearer
+}
+
+func (c *Client4) usersRoute() string {
+ return "/users"
+}
+
+func (c *Client4) userRoute(userId string) string {
+ return fmt.Sprintf(c.usersRoute()+"/%v", userId)
+}
+
+func (c *Client4) userThreadsRoute(userID, teamID string) string {
+ return c.userRoute(userID) + c.teamRoute(teamID) + "/threads"
+}
+
+func (c *Client4) userThreadRoute(userId, teamId, threadId string) string {
+ return c.userThreadsRoute(userId, teamId) + "/" + threadId
+}
+
+func (c *Client4) userCategoryRoute(userID, teamID string) string {
+ return c.userRoute(userID) + c.teamRoute(teamID) + "/channels/categories"
+}
+
+func (c *Client4) userAccessTokensRoute() string {
+ return fmt.Sprintf(c.usersRoute() + "/tokens")
+}
+
+func (c *Client4) userAccessTokenRoute(tokenId string) string {
+ return fmt.Sprintf(c.usersRoute()+"/tokens/%v", tokenId)
+}
+
+func (c *Client4) userByUsernameRoute(userName string) string {
+ return fmt.Sprintf(c.usersRoute()+"/username/%v", userName)
+}
+
+func (c *Client4) userByEmailRoute(email string) string {
+ return fmt.Sprintf(c.usersRoute()+"/email/%v", email)
+}
+
+func (c *Client4) botsRoute() string {
+ return "/bots"
+}
+
+func (c *Client4) botRoute(botUserId string) string {
+ return fmt.Sprintf("%s/%s", c.botsRoute(), botUserId)
+}
+
+func (c *Client4) teamsRoute() string {
+ return "/teams"
+}
+
+func (c *Client4) teamRoute(teamId string) string {
+ return fmt.Sprintf(c.teamsRoute()+"/%v", teamId)
+}
+
+func (c *Client4) teamAutoCompleteCommandsRoute(teamId string) string {
+ return fmt.Sprintf(c.teamsRoute()+"/%v/commands/autocomplete", teamId)
+}
+
+func (c *Client4) teamByNameRoute(teamName string) string {
+ return fmt.Sprintf(c.teamsRoute()+"/name/%v", teamName)
+}
+
+func (c *Client4) teamMemberRoute(teamId, userId string) string {
+ return fmt.Sprintf(c.teamRoute(teamId)+"/members/%v", userId)
+}
+
+func (c *Client4) teamMembersRoute(teamId string) string {
+ return fmt.Sprintf(c.teamRoute(teamId) + "/members")
+}
+
+func (c *Client4) teamStatsRoute(teamId string) string {
+ return fmt.Sprintf(c.teamRoute(teamId) + "/stats")
+}
+
+func (c *Client4) teamImportRoute(teamId string) string {
+ return fmt.Sprintf(c.teamRoute(teamId) + "/import")
+}
+
+func (c *Client4) channelsRoute() string {
+ return "/channels"
+}
+
+func (c *Client4) channelsForTeamRoute(teamId string) string {
+ return fmt.Sprintf(c.teamRoute(teamId) + "/channels")
+}
+
+func (c *Client4) channelRoute(channelId string) string {
+ return fmt.Sprintf(c.channelsRoute()+"/%v", channelId)
+}
+
+func (c *Client4) channelByNameRoute(channelName, teamId string) string {
+ return fmt.Sprintf(c.teamRoute(teamId)+"/channels/name/%v", channelName)
+}
+
+func (c *Client4) channelsForTeamForUserRoute(teamId, userId string, includeDeleted bool) string {
+ route := fmt.Sprintf(c.userRoute(userId) + c.teamRoute(teamId) + "/channels")
+ if includeDeleted {
+ query := fmt.Sprintf("?include_deleted=%v", includeDeleted)
+ return route + query
+ }
+ return route
+}
+
+func (c *Client4) channelByNameForTeamNameRoute(channelName, teamName string) string {
+ return fmt.Sprintf(c.teamByNameRoute(teamName)+"/channels/name/%v", channelName)
+}
+
+func (c *Client4) channelMembersRoute(channelId string) string {
+ return fmt.Sprintf(c.channelRoute(channelId) + "/members")
+}
+
+func (c *Client4) channelMemberRoute(channelId, userId string) string {
+ return fmt.Sprintf(c.channelMembersRoute(channelId)+"/%v", userId)
+}
+
+func (c *Client4) postsRoute() string {
+ return "/posts"
+}
+
+func (c *Client4) postsEphemeralRoute() string {
+ return "/posts/ephemeral"
+}
+
+func (c *Client4) configRoute() string {
+ return "/config"
+}
+
+func (c *Client4) licenseRoute() string {
+ return "/license"
+}
+
+func (c *Client4) postRoute(postId string) string {
+ return fmt.Sprintf(c.postsRoute()+"/%v", postId)
+}
+
+func (c *Client4) filesRoute() string {
+ return "/files"
+}
+
+func (c *Client4) fileRoute(fileId string) string {
+ return fmt.Sprintf(c.filesRoute()+"/%v", fileId)
+}
+
+func (c *Client4) uploadsRoute() string {
+ return "/uploads"
+}
+
+func (c *Client4) uploadRoute(uploadId string) string {
+ return fmt.Sprintf("%s/%s", c.uploadsRoute(), uploadId)
+}
+
+func (c *Client4) pluginsRoute() string {
+ return "/plugins"
+}
+
+func (c *Client4) pluginRoute(pluginId string) string {
+ return fmt.Sprintf(c.pluginsRoute()+"/%v", pluginId)
+}
+
+func (c *Client4) systemRoute() string {
+ return "/system"
+}
+
+func (c *Client4) cloudRoute() string {
+ return "/cloud"
+}
+
+func (c *Client4) testEmailRoute() string {
+ return "/email/test"
+}
+
+func (c *Client4) testSiteURLRoute() string {
+ return "/site_url/test"
+}
+
+func (c *Client4) testS3Route() string {
+ return "/file/s3_test"
+}
+
+func (c *Client4) databaseRoute() string {
+ return "/database"
+}
+
+func (c *Client4) cacheRoute() string {
+ return "/caches"
+}
+
+func (c *Client4) clusterRoute() string {
+ return "/cluster"
+}
+
+func (c *Client4) incomingWebhooksRoute() string {
+ return "/hooks/incoming"
+}
+
+func (c *Client4) incomingWebhookRoute(hookID string) string {
+ return fmt.Sprintf(c.incomingWebhooksRoute()+"/%v", hookID)
+}
+
+func (c *Client4) complianceReportsRoute() string {
+ return "/compliance/reports"
+}
+
+func (c *Client4) complianceReportRoute(reportId string) string {
+ return fmt.Sprintf("%s/%s", c.complianceReportsRoute(), reportId)
+}
+
+func (c *Client4) complianceReportDownloadRoute(reportId string) string {
+ return fmt.Sprintf("%s/%s/download", c.complianceReportsRoute(), reportId)
+}
+
+func (c *Client4) outgoingWebhooksRoute() string {
+ return "/hooks/outgoing"
+}
+
+func (c *Client4) outgoingWebhookRoute(hookID string) string {
+ return fmt.Sprintf(c.outgoingWebhooksRoute()+"/%v", hookID)
+}
+
+func (c *Client4) preferencesRoute(userId string) string {
+ return fmt.Sprintf(c.userRoute(userId) + "/preferences")
+}
+
+func (c *Client4) userStatusRoute(userId string) string {
+ return fmt.Sprintf(c.userRoute(userId) + "/status")
+}
+
+func (c *Client4) userStatusesRoute() string {
+ return fmt.Sprintf(c.usersRoute() + "/status")
+}
+
+func (c *Client4) samlRoute() string {
+ return "/saml"
+}
+
+func (c *Client4) ldapRoute() string {
+ return "/ldap"
+}
+
+func (c *Client4) brandRoute() string {
+ return "/brand"
+}
+
+func (c *Client4) dataRetentionRoute() string {
+ return "/data_retention"
+}
+
+func (c *Client4) dataRetentionPolicyRoute(policyID string) string {
+ return fmt.Sprintf(c.dataRetentionRoute()+"/policies/%v", policyID)
+}
+
+func (c *Client4) elasticsearchRoute() string {
+ return "/elasticsearch"
+}
+
+func (c *Client4) bleveRoute() string {
+ return "/bleve"
+}
+
+func (c *Client4) commandsRoute() string {
+ return "/commands"
+}
+
+func (c *Client4) commandRoute(commandId string) string {
+ return fmt.Sprintf(c.commandsRoute()+"/%v", commandId)
+}
+
+func (c *Client4) commandMoveRoute(commandId string) string {
+ return fmt.Sprintf(c.commandsRoute()+"/%v/move", commandId)
+}
+
+func (c *Client4) emojisRoute() string {
+ return "/emoji"
+}
+
+func (c *Client4) emojiRoute(emojiId string) string {
+ return fmt.Sprintf(c.emojisRoute()+"/%v", emojiId)
+}
+
+func (c *Client4) emojiByNameRoute(name string) string {
+ return fmt.Sprintf(c.emojisRoute()+"/name/%v", name)
+}
+
+func (c *Client4) reactionsRoute() string {
+ return "/reactions"
+}
+
+func (c *Client4) oAuthAppsRoute() string {
+ return "/oauth/apps"
+}
+
+func (c *Client4) oAuthAppRoute(appId string) string {
+ return fmt.Sprintf("/oauth/apps/%v", appId)
+}
+
+func (c *Client4) openGraphRoute() string {
+ return "/opengraph"
+}
+
+func (c *Client4) jobsRoute() string {
+ return "/jobs"
+}
+
+func (c *Client4) rolesRoute() string {
+ return "/roles"
+}
+
+func (c *Client4) schemesRoute() string {
+ return "/schemes"
+}
+
+func (c *Client4) schemeRoute(id string) string {
+ return c.schemesRoute() + fmt.Sprintf("/%v", id)
+}
+
+func (c *Client4) analyticsRoute() string {
+ return "/analytics"
+}
+
+func (c *Client4) timezonesRoute() string {
+ return fmt.Sprintf(c.systemRoute() + "/timezones")
+}
+
+func (c *Client4) channelSchemeRoute(channelId string) string {
+ return fmt.Sprintf(c.channelsRoute()+"/%v/scheme", channelId)
+}
+
+func (c *Client4) teamSchemeRoute(teamId string) string {
+ return fmt.Sprintf(c.teamsRoute()+"/%v/scheme", teamId)
+}
+
+func (c *Client4) totalUsersStatsRoute() string {
+ return fmt.Sprintf(c.usersRoute() + "/stats")
+}
+
+func (c *Client4) redirectLocationRoute() string {
+ return "/redirect_location"
+}
+
+func (c *Client4) serverBusyRoute() string {
+ return "/server_busy"
+}
+
+func (c *Client4) userTermsOfServiceRoute(userId string) string {
+ return c.userRoute(userId) + "/terms_of_service"
+}
+
+func (c *Client4) termsOfServiceRoute() string {
+ return "/terms_of_service"
+}
+
+func (c *Client4) groupsRoute() string {
+ return "/groups"
+}
+
+func (c *Client4) publishUserTypingRoute(userId string) string {
+ return c.userRoute(userId) + "/typing"
+}
+
+func (c *Client4) groupRoute(groupID string) string {
+ return fmt.Sprintf("%s/%s", c.groupsRoute(), groupID)
+}
+
+func (c *Client4) groupSyncableRoute(groupID, syncableID string, syncableType GroupSyncableType) string {
+ return fmt.Sprintf("%s/%ss/%s", c.groupRoute(groupID), strings.ToLower(syncableType.String()), syncableID)
+}
+
+func (c *Client4) groupSyncablesRoute(groupID string, syncableType GroupSyncableType) string {
+ return fmt.Sprintf("%s/%ss", c.groupRoute(groupID), strings.ToLower(syncableType.String()))
+}
+
+func (c *Client4) importsRoute() string {
+ return "/imports"
+}
+
+func (c *Client4) exportsRoute() string {
+ return "/exports"
+}
+
+func (c *Client4) exportRoute(name string) string {
+ return fmt.Sprintf(c.exportsRoute()+"/%v", name)
+}
+
+func (c *Client4) sharedChannelsRoute() string {
+ return "/sharedchannels"
+}
+
+func (c *Client4) permissionsRoute() string {
+ return "/permissions"
+}
+
+func (c *Client4) DoAPIGet(url string, etag string) (*http.Response, error) {
+ return c.DoAPIRequest(http.MethodGet, c.APIURL+url, "", etag)
+}
+
+func (c *Client4) DoAPIPost(url string, data string) (*http.Response, error) {
+ return c.DoAPIRequest(http.MethodPost, c.APIURL+url, data, "")
+}
+
+func (c *Client4) DoAPIDeleteBytes(url string, data []byte) (*http.Response, error) {
+ return c.DoAPIRequestBytes(http.MethodDelete, c.APIURL+url, data, "")
+}
+
+func (c *Client4) DoAPIPatchBytes(url string, data []byte) (*http.Response, error) {
+ return c.DoAPIRequestBytes(http.MethodPatch, c.APIURL+url, data, "")
+}
+
+func (c *Client4) DoAPIPostBytes(url string, data []byte) (*http.Response, error) {
+ return c.DoAPIRequestBytes(http.MethodPost, c.APIURL+url, data, "")
+}
+
+func (c *Client4) DoAPIPut(url string, data string) (*http.Response, error) {
+ return c.DoAPIRequest(http.MethodPut, c.APIURL+url, data, "")
+}
+
+func (c *Client4) DoAPIPutBytes(url string, data []byte) (*http.Response, error) {
+ return c.DoAPIRequestBytes(http.MethodPut, c.APIURL+url, data, "")
+}
+
+func (c *Client4) DoAPIDelete(url string) (*http.Response, error) {
+ return c.DoAPIRequest(http.MethodDelete, c.APIURL+url, "", "")
+}
+
+func (c *Client4) DoAPIRequest(method, url, data, etag string) (*http.Response, error) {
+ return c.DoAPIRequestReader(method, url, strings.NewReader(data), map[string]string{HeaderEtagClient: etag})
+}
+
+func (c *Client4) DoAPIRequestWithHeaders(method, url, data string, headers map[string]string) (*http.Response, error) {
+ return c.DoAPIRequestReader(method, url, strings.NewReader(data), headers)
+}
+
+func (c *Client4) DoAPIRequestBytes(method, url string, data []byte, etag string) (*http.Response, error) {
+ return c.DoAPIRequestReader(method, url, bytes.NewReader(data), map[string]string{HeaderEtagClient: etag})
+}
+
+func (c *Client4) DoAPIRequestReader(method, url string, data io.Reader, headers map[string]string) (*http.Response, error) {
+ rq, err := http.NewRequest(method, url, data)
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range headers {
+ rq.Header.Set(k, v)
+ }
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ if c.HTTPHeader != nil && len(c.HTTPHeader) > 0 {
+ for k, v := range c.HTTPHeader {
+ rq.Header.Set(k, v)
+ }
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return rp, err
+ }
+
+ if rp.StatusCode == 304 {
+ return rp, nil
+ }
+
+ if rp.StatusCode >= 300 {
+ defer closeBody(rp)
+ return rp, AppErrorFromJSON(rp.Body)
+ }
+
+ return rp, nil
+}
+
+func (c *Client4) DoUploadFile(url string, data []byte, contentType string) (*FileUploadResponse, *Response, error) {
+ return c.doUploadFile(url, bytes.NewReader(data), contentType, 0)
+}
+
+func (c *Client4) doUploadFile(url string, body io.Reader, contentType string, contentLength int64) (*FileUploadResponse, *Response, error) {
+ rq, err := http.NewRequest("POST", c.APIURL+url, body)
+ if err != nil {
+ return nil, nil, err
+ }
+ if contentLength != 0 {
+ rq.ContentLength = contentLength
+ }
+ rq.Header.Set("Content-Type", contentType)
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return nil, BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ var res FileUploadResponse
+ if jsonErr := json.NewDecoder(rp.Body).Decode(&res); jsonErr != nil {
+ return nil, nil, NewAppError("doUploadFile", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &res, BuildResponse(rp), nil
+}
+
+func (c *Client4) DoEmojiUploadFile(url string, data []byte, contentType string) (*Emoji, *Response, error) {
+ rq, err := http.NewRequest("POST", c.APIURL+url, bytes.NewReader(data))
+ if err != nil {
+ return nil, nil, err
+ }
+ rq.Header.Set("Content-Type", contentType)
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return nil, BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ var e Emoji
+ if jsonErr := json.NewDecoder(rp.Body).Decode(&e); jsonErr != nil {
+ return nil, nil, NewAppError("DoEmojiUploadFile", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &e, BuildResponse(rp), nil
+}
+
+func (c *Client4) DoUploadImportTeam(url string, data []byte, contentType string) (map[string]string, *Response, error) {
+ rq, err := http.NewRequest("POST", c.APIURL+url, bytes.NewReader(data))
+ if err != nil {
+ return nil, nil, err
+ }
+ rq.Header.Set("Content-Type", contentType)
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return nil, BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ return MapFromJSON(rp.Body), BuildResponse(rp), nil
+}
+
+// Authentication Section
+
+// LoginById authenticates a user by user id and password.
+func (c *Client4) LoginById(id string, password string) (*User, *Response, error) {
+ 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, error) {
+ 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, error) {
+ m := make(map[string]string)
+ m["login_id"] = loginId
+ m["password"] = password
+ m["ldap_only"] = c.boolString(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, error) {
+ m := make(map[string]string)
+ m["login_id"] = loginId
+ m["password"] = password
+ m["device_id"] = deviceId
+ return c.login(m)
+}
+
+// LoginWithMFA logs a user in with a MFA token
+func (c *Client4) LoginWithMFA(loginId, password, mfaToken string) (*User, *Response, error) {
+ m := make(map[string]string)
+ m["login_id"] = loginId
+ m["password"] = password
+ m["token"] = mfaToken
+ return c.login(m)
+}
+
+func (c *Client4) login(m map[string]string) (*User, *Response, error) {
+ r, err := c.DoAPIPost("/users/login", MapToJSON(m))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ c.AuthToken = r.Header.Get(HeaderToken)
+ c.AuthType = HeaderBearer
+
+ var user User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&user); jsonErr != nil {
+ return nil, nil, NewAppError("login", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &user, BuildResponse(r), nil
+}
+
+// Logout terminates the current user's session.
+func (c *Client4) Logout() (*Response, error) {
+ r, err := c.DoAPIPost("/users/logout", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ c.AuthToken = ""
+ c.AuthType = HeaderBearer
+ return BuildResponse(r), nil
+}
+
+// SwitchAccountType changes a user's login type from one type to another.
+func (c *Client4) SwitchAccountType(switchRequest *SwitchRequest) (string, *Response, error) {
+ buf, err := json.Marshal(switchRequest)
+ if err != nil {
+ return "", BuildResponse(nil), NewAppError("SwitchAccountType", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.usersRoute()+"/login/switch", buf)
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["follow_link"], BuildResponse(r), nil
+}
+
+// User Section
+
+// CreateUser creates a user in the system based on the provided user struct.
+func (c *Client4) CreateUser(user *User) (*User, *Response, error) {
+ userJSON, jsonErr := json.Marshal(user)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("CreateUser", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+
+ r, err := c.DoAPIPost(c.usersRoute(), string(userJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("CreateUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// CreateUserWithToken creates a user in the system based on the provided tokenId.
+func (c *Client4) CreateUserWithToken(user *User, tokenId string) (*User, *Response, error) {
+ if tokenId == "" {
+ return nil, nil, NewAppError("MissingHashOrData", "api.user.create_user.missing_token.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ query := fmt.Sprintf("?t=%v", tokenId)
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return nil, nil, NewAppError("CreateUserWithToken", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.usersRoute()+query, buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("CreateUserWithToken", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// CreateUserWithInviteId creates a user in the system based on the provided invited id.
+func (c *Client4) CreateUserWithInviteId(user *User, inviteId string) (*User, *Response, error) {
+ if inviteId == "" {
+ return nil, nil, NewAppError("MissingInviteId", "api.user.create_user.missing_invite_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ query := fmt.Sprintf("?iid=%v", url.QueryEscape(inviteId))
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return nil, nil, NewAppError("CreateUserWithInviteId", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.usersRoute()+query, buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("CreateUserWithInviteId", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// GetMe returns the logged in user.
+func (c *Client4) GetMe(etag string) (*User, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(Me), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("GetMe", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// GetUser returns a user based on the provided user id string.
+func (c *Client4) GetUser(userId, etag string) (*User, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("GetUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// GetUserByUsername returns a user based on the provided user name string.
+func (c *Client4) GetUserByUsername(userName, etag string) (*User, *Response, error) {
+ r, err := c.DoAPIGet(c.userByUsernameRoute(userName), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserByUsername", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// GetUserByEmail returns a user based on the provided user email string.
+func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response, error) {
+ r, err := c.DoAPIGet(c.userByEmailRoute(email), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserByEmail", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// AutocompleteUsersInTeam returns the users on a team based on search term.
+func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, limit int, etag string) (*UserAutocomplete, *Response, error) {
+ query := fmt.Sprintf("?in_team=%v&name=%v&limit=%d", teamId, username, limit)
+ r, err := c.DoAPIGet(c.usersRoute()+"/autocomplete"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u UserAutocomplete
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("AutocompleteUsersInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// AutocompleteUsersInChannel returns the users in a channel based on search term.
+func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, limit int, etag string) (*UserAutocomplete, *Response, error) {
+ query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v&limit=%d", teamId, channelId, username, limit)
+ r, err := c.DoAPIGet(c.usersRoute()+"/autocomplete"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u UserAutocomplete
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("AutocompleteUsersInChannel", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// AutocompleteUsers returns the users in the system based on search term.
+func (c *Client4) AutocompleteUsers(username string, limit int, etag string) (*UserAutocomplete, *Response, error) {
+ query := fmt.Sprintf("?name=%v&limit=%d", username, limit)
+ r, err := c.DoAPIGet(c.usersRoute()+"/autocomplete"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u UserAutocomplete
+ if r.StatusCode == http.StatusNotModified {
+ return &u, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("AutocompleteUsers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// GetDefaultProfileImage gets the default user's profile image. Must be logged in.
+func (c *Client4) GetDefaultProfileImage(userId string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/image/default", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetDefaultProfileImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+
+ return data, BuildResponse(r), nil
+}
+
+// GetProfileImage gets user's profile image. Must be logged in.
+func (c *Client4) GetProfileImage(userId, etag string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/image", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetProfileImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?sort=create_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetNewUsersInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?sort=last_activity_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetRecentlyActiveUsersInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetActiveUsersInTeam returns a page of users on a team. Page counting starts at 0.
+func (c *Client4) GetActiveUsersInTeam(teamId string, page int, perPage int, etag string) ([]*User, *Response, error) {
+ query := fmt.Sprintf("?active=true&in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetActiveUsersInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?not_in_team=%v&page=%v&per_page=%v", teamId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersNotInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersInChannel", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetUsersInChannelByStatus 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, error) {
+ query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v&sort=status", channelId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersInChannelByStatus", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?in_team=%v&not_in_channel=%v&page=%v&per_page=%v", teamId, channelId, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersNotInChannel", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?without_team=1&page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersWithoutTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetUsersInGroup returns a page of users in a group. Page counting starts at 0.
+func (c *Client4) GetUsersInGroup(groupID string, page int, perPage int, etag string) ([]*User, *Response, error) {
+ query := fmt.Sprintf("?in_group=%v&page=%v&per_page=%v", groupID, page, perPage)
+ r, err := c.DoAPIGet(c.usersRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersInGroup", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetUsersByIds returns a list of users based on the provided user ids.
+func (c *Client4) GetUsersByIds(userIds []string) ([]*User, *Response, error) {
+ r, err := c.DoAPIPost(c.usersRoute()+"/ids", ArrayToJSON(userIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersByIds", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetUsersByIds returns a list of users based on the provided user ids.
+func (c *Client4) GetUsersByIdsWithOptions(userIds []string, options *UserGetByIdsOptions) ([]*User, *Response, error) {
+ v := url.Values{}
+ if options.Since != 0 {
+ v.Set("since", fmt.Sprintf("%d", options.Since))
+ }
+
+ url := c.usersRoute() + "/ids"
+ if len(v) > 0 {
+ url += "?" + v.Encode()
+ }
+
+ r, err := c.DoAPIPost(url, ArrayToJSON(userIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersByIdsWithOptions", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetUsersByUsernames returns a list of users based on the provided usernames.
+func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response, error) {
+ r, err := c.DoAPIPost(c.usersRoute()+"/usernames", ArrayToJSON(usernames))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersByUsernames", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetUsersByGroupChannelIds returns a map with channel ids as keys
+// and a list of users as values based on the provided user ids.
+func (c *Client4) GetUsersByGroupChannelIds(groupChannelIds []string) (map[string][]*User, *Response, error) {
+ r, err := c.DoAPIPost(c.usersRoute()+"/group_channels", ArrayToJSON(groupChannelIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ usersByChannelId := map[string][]*User{}
+ json.NewDecoder(r.Body).Decode(&usersByChannelId)
+ return usersByChannelId, BuildResponse(r), nil
+}
+
+// SearchUsers returns a list of users based on some search criteria.
+func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response, error) {
+ buf, err := json.Marshal(search)
+ if err != nil {
+ return nil, nil, NewAppError("SearchUsers", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.usersRoute()+"/search", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("SearchUsers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// UpdateUser updates a user in the system based on the provided user struct.
+func (c *Client4) UpdateUser(user *User) (*User, *Response, error) {
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateUser", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.userRoute(user.Id), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// PatchUser partially updates a user in the system. Any missing fields are not updated.
+func (c *Client4) PatchUser(userId string, patch *UserPatch) (*User, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchUser", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.userRoute(userId)+"/patch", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("PatchUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// UpdateUserAuth updates a user AuthData (uthData, authService and password) in the system.
+func (c *Client4) UpdateUserAuth(userId string, userAuth *UserAuth) (*UserAuth, *Response, error) {
+ buf, err := json.Marshal(userAuth)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateUserAuth", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.userRoute(userId)+"/auth", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ua UserAuth
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ua); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateUserAuth", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &ua, BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := make(map[string]interface{})
+ requestBody["activate"] = activate
+ requestBody["code"] = code
+
+ r, err := c.DoAPIPut(c.userRoute(userId)+"/mfa", StringInterfaceToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIPost(c.userRoute(userId)+"/mfa/generate", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var secret MfaSecret
+ if jsonErr := json.NewDecoder(r.Body).Decode(&secret); jsonErr != nil {
+ return nil, nil, NewAppError("GenerateMfaSecret", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &secret, BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword}
+ r, err := c.DoAPIPut(c.userRoute(userId)+"/password", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateUserHashedPassword updates a user's password with an already-hashed password. Must be a system administrator.
+func (c *Client4) UpdateUserHashedPassword(userId, newHashedPassword string) (*Response, error) {
+ requestBody := map[string]string{"already_hashed": "true", "new_password": newHashedPassword}
+ r, err := c.DoAPIPut(c.userRoute(userId)+"/password", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PromoteGuestToUser convert a guest into a regular user
+func (c *Client4) PromoteGuestToUser(guestId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.userRoute(guestId)+"/promote", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DemoteUserToGuest convert a regular user into a guest
+func (c *Client4) DemoteUserToGuest(guestId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.userRoute(guestId)+"/demote", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := map[string]string{"roles": roles}
+ r, err := c.DoAPIPut(c.userRoute(userId)+"/roles", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateUserActive updates status of a user whether active or not.
+func (c *Client4) UpdateUserActive(userId string, active bool) (*Response, error) {
+ requestBody := make(map[string]interface{})
+ requestBody["active"] = active
+ r, err := c.DoAPIPut(c.userRoute(userId)+"/active", StringInterfaceToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ return BuildResponse(r), nil
+}
+
+// DeleteUser deactivates a user in the system based on the provided user id string.
+func (c *Client4) DeleteUser(userId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.userRoute(userId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PermanentDeleteUser deletes a user in the system based on the provided user id string.
+func (c *Client4) PermanentDeleteUser(userId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.userRoute(userId) + "?permanent=" + c.boolString(true))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// ConvertUserToBot converts a user to a bot user.
+func (c *Client4) ConvertUserToBot(userId string) (*Bot, *Response, error) {
+ r, err := c.DoAPIPost(c.userRoute(userId)+"/convert_to_bot", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("ConvertUserToBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return bot, BuildResponse(r), nil
+}
+
+// ConvertBotToUser converts a bot user to a user.
+func (c *Client4) ConvertBotToUser(userId string, userPatch *UserPatch, setSystemAdmin bool) (*User, *Response, error) {
+ var query string
+ if setSystemAdmin {
+ query = "?set_system_admin=true"
+ }
+ buf, err := json.Marshal(userPatch)
+ if err != nil {
+ return nil, nil, NewAppError("ConvertBotToUser", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.botRoute(userId)+"/convert_to_user"+query, buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("ConvertBotToUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// PermanentDeleteAll permanently deletes all users in the system. This is a local only endpoint
+func (c *Client4) PermanentDeleteAllUsers() (*Response, error) {
+ r, err := c.DoAPIDelete(c.usersRoute())
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// SendPasswordResetEmail will send a link for password resetting to a user with the
+// provided email.
+func (c *Client4) SendPasswordResetEmail(email string) (*Response, error) {
+ requestBody := map[string]string{"email": email}
+ r, err := c.DoAPIPost(c.usersRoute()+"/password/reset/send", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// ResetPassword uses a recovery code to update reset a user's password.
+func (c *Client4) ResetPassword(token, newPassword string) (*Response, error) {
+ requestBody := map[string]string{"token": token, "new_password": newPassword}
+ r, err := c.DoAPIPost(c.usersRoute()+"/password/reset", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetSessions returns a list of sessions based on the provided user id string.
+func (c *Client4) GetSessions(userId, etag string) ([]*Session, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/sessions", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Session
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetSessions", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// RevokeSession revokes a user session based on the provided user id and session id strings.
+func (c *Client4) RevokeSession(userId, sessionId string) (*Response, error) {
+ requestBody := map[string]string{"session_id": sessionId}
+ r, err := c.DoAPIPost(c.userRoute(userId)+"/sessions/revoke", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// RevokeAllSessions revokes all sessions for the provided user id string.
+func (c *Client4) RevokeAllSessions(userId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.userRoute(userId)+"/sessions/revoke/all", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// RevokeAllSessions revokes all sessions for all the users.
+func (c *Client4) RevokeSessionsFromAllUsers() (*Response, error) {
+ r, err := c.DoAPIPost(c.usersRoute()+"/sessions/revoke/all", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// AttachDeviceId attaches a mobile device ID to the current session.
+func (c *Client4) AttachDeviceId(deviceId string) (*Response, error) {
+ requestBody := map[string]string{"device_id": deviceId}
+ r, err := c.DoAPIPut(c.usersRoute()+"/sessions/device", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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.
+// An optional boolean can be set to include collapsed thread unreads. Must be authenticated.
+func (c *Client4) GetTeamsUnreadForUser(userId, teamIdToExclude string, includeCollapsedThreads bool) ([]*TeamUnread, *Response, error) {
+ query := url.Values{}
+
+ if teamIdToExclude != "" {
+ query.Set("exclude_team", teamIdToExclude)
+ }
+
+ if includeCollapsedThreads {
+ query.Set("include_collapsed_threads", "true")
+ }
+
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/teams/unread?"+query.Encode(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list []*TeamUnread
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamsUnreadForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/audits"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var audits Audits
+ err = json.NewDecoder(r.Body).Decode(&audits)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetUserAudits", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return audits, BuildResponse(r), nil
+}
+
+// VerifyUserEmail will verify a user's email using the supplied token.
+func (c *Client4) VerifyUserEmail(token string) (*Response, error) {
+ requestBody := map[string]string{"token": token}
+ r, err := c.DoAPIPost(c.usersRoute()+"/email/verify", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// VerifyUserEmailWithoutToken will verify a user's email by its Id. (Requires manage system role)
+func (c *Client4) VerifyUserEmailWithoutToken(userId string) (*User, *Response, error) {
+ r, err := c.DoAPIPost(c.userRoute(userId)+"/email/verify/member", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u User
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("VerifyUserEmailWithoutToken", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := map[string]string{"email": email}
+ r, err := c.DoAPIPost(c.usersRoute()+"/email/verify/send", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// SetDefaultProfileImage resets the profile image to a default generated one.
+func (c *Client4) SetDefaultProfileImage(userId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.userRoute(userId) + "/image")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ return BuildResponse(r), nil
+}
+
+// SetProfileImage sets profile image of the user.
+func (c *Client4) SetProfileImage(userId string, data []byte) (*Response, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("image", "profile.png")
+ if err != nil {
+ return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if err = writer.Close(); err != nil {
+ return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ rq, err := http.NewRequest("POST", c.APIURL+c.userRoute(userId)+"/image", bytes.NewReader(body.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+ rq.Header.Set("Content-Type", writer.FormDataContentType())
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ return BuildResponse(rp), nil
+}
+
+// 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, error) {
+ requestBody := map[string]string{"description": description}
+ r, err := c.DoAPIPost(c.userRoute(userId)+"/tokens", MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var uat UserAccessToken
+ if jsonErr := json.NewDecoder(r.Body).Decode(&uat); jsonErr != nil {
+ return nil, nil, NewAppError("CreateUserAccessToken", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &uat, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.userAccessTokensRoute()+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*UserAccessToken
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserAccessTokens", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.userAccessTokenRoute(tokenId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var uat UserAccessToken
+ if jsonErr := json.NewDecoder(r.Body).Decode(&uat); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserAccessToken", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &uat, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/tokens"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*UserAccessToken
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserAccessTokensForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := map[string]string{"token_id": tokenId}
+ r, err := c.DoAPIPost(c.usersRoute()+"/tokens/revoke", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// SearchUserAccessTokens returns user access tokens matching the provided search term.
+func (c *Client4) SearchUserAccessTokens(search *UserAccessTokenSearch) ([]*UserAccessToken, *Response, error) {
+ buf, err := json.Marshal(search)
+ if err != nil {
+ return nil, nil, NewAppError("SearchUserAccessTokens", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.usersRoute()+"/tokens/search", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*UserAccessToken
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("SearchUserAccessTokens", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := map[string]string{"token_id": tokenId}
+ r, err := c.DoAPIPost(c.usersRoute()+"/tokens/disable", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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) (*Response, error) {
+ requestBody := map[string]string{"token_id": tokenId}
+ r, err := c.DoAPIPost(c.usersRoute()+"/tokens/enable", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Bots section
+
+// CreateBot creates a bot in the system based on the provided bot struct.
+func (c *Client4) CreateBot(bot *Bot) (*Bot, *Response, error) {
+ buf, err := json.Marshal(bot)
+ if err != nil {
+ return nil, nil, NewAppError("CreateBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.botsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var resp *Bot
+ err = json.NewDecoder(r.Body).Decode(&resp)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("CreateBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return resp, BuildResponse(r), nil
+}
+
+// PatchBot partially updates a bot. Any missing fields are not updated.
+func (c *Client4) PatchBot(userId string, patch *BotPatch) (*Bot, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.botRoute(userId), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("PatchBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return bot, BuildResponse(r), nil
+}
+
+// GetBot fetches the given, undeleted bot.
+func (c *Client4) GetBot(userId string, etag string) (*Bot, *Response, error) {
+ r, err := c.DoAPIGet(c.botRoute(userId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return bot, BuildResponse(r), nil
+}
+
+// GetBotIncludeDeleted fetches the given bot, even if it is deleted.
+func (c *Client4) GetBotIncludeDeleted(userId string, etag string) (*Bot, *Response, error) {
+ r, err := c.DoAPIGet(c.botRoute(userId)+"?include_deleted="+c.boolString(true), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetBotIncludeDeleted", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return bot, BuildResponse(r), nil
+}
+
+// GetBots fetches the given page of bots, excluding deleted.
+func (c *Client4) GetBots(page, perPage int, etag string) ([]*Bot, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.botsRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bots BotList
+ err = json.NewDecoder(r.Body).Decode(&bots)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetBots", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return bots, BuildResponse(r), nil
+}
+
+// GetBotsIncludeDeleted fetches the given page of bots, including deleted.
+func (c *Client4) GetBotsIncludeDeleted(page, perPage int, etag string) ([]*Bot, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted="+c.boolString(true), page, perPage)
+ r, err := c.DoAPIGet(c.botsRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bots BotList
+ err = json.NewDecoder(r.Body).Decode(&bots)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetBotsIncludeDeleted", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return bots, BuildResponse(r), nil
+}
+
+// GetBotsOrphaned fetches the given page of bots, only including orphanded bots.
+func (c *Client4) GetBotsOrphaned(page, perPage int, etag string) ([]*Bot, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&only_orphaned="+c.boolString(true), page, perPage)
+ r, err := c.DoAPIGet(c.botsRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bots BotList
+ err = json.NewDecoder(r.Body).Decode(&bots)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetBotsOrphaned", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return bots, BuildResponse(r), nil
+}
+
+// DisableBot disables the given bot in the system.
+func (c *Client4) DisableBot(botUserId string) (*Bot, *Response, error) {
+ r, err := c.DoAPIPostBytes(c.botRoute(botUserId)+"/disable", nil)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("DisableBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return bot, BuildResponse(r), nil
+}
+
+// EnableBot disables the given bot in the system.
+func (c *Client4) EnableBot(botUserId string) (*Bot, *Response, error) {
+ r, err := c.DoAPIPostBytes(c.botRoute(botUserId)+"/enable", nil)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("EnableBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return bot, BuildResponse(r), nil
+}
+
+// AssignBot assigns the given bot to the given user
+func (c *Client4) AssignBot(botUserId, newOwnerId string) (*Bot, *Response, error) {
+ r, err := c.DoAPIPostBytes(c.botRoute(botUserId)+"/assign/"+newOwnerId, nil)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var bot *Bot
+ err = json.NewDecoder(r.Body).Decode(&bot)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("AssignBot", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return bot, BuildResponse(r), nil
+}
+
+// Team Section
+
+// CreateTeam creates a team in the system based on the provided team struct.
+func (c *Client4) CreateTeam(team *Team) (*Team, *Response, error) {
+ buf, err := json.Marshal(team)
+ if err != nil {
+ return nil, nil, NewAppError("CreateTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.teamsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("CreateTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// GetTeam returns a team based on the provided team id string.
+func (c *Client4) GetTeam(teamId, etag string) (*Team, *Response, error) {
+ r, err := c.DoAPIGet(c.teamRoute(teamId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// GetAllTeams returns all teams based on permissions.
+func (c *Client4) GetAllTeams(etag string, page int, perPage int) ([]*Team, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.teamsRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetAllTeams", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetAllTeamsWithTotalCount returns all teams based on permissions.
+func (c *Client4) GetAllTeamsWithTotalCount(etag string, page int, perPage int) ([]*Team, int64, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&include_total_count="+c.boolString(true), page, perPage)
+ r, err := c.DoAPIGet(c.teamsRoute()+query, etag)
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var listWithCount TeamsWithCount
+ if jsonErr := json.NewDecoder(r.Body).Decode(&listWithCount); jsonErr != nil {
+ return nil, 0, nil, NewAppError("GetAllTeamsWithTotalCount", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return listWithCount.Teams, listWithCount.TotalCount, BuildResponse(r), nil
+}
+
+// GetAllTeamsExcludePolicyConstrained returns all teams which are not part of a data retention policy.
+// Must be a system administrator.
+func (c *Client4) GetAllTeamsExcludePolicyConstrained(etag string, page int, perPage int) ([]*Team, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&exclude_policy_constrained=%v", page, perPage, true)
+ r, err := c.DoAPIGet(c.teamsRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetAllTeamsExcludePolicyConstrained", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetTeamByName returns a team based on the provided team name string.
+func (c *Client4) GetTeamByName(name, etag string) (*Team, *Response, error) {
+ r, err := c.DoAPIGet(c.teamByNameRoute(name), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamByName", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// SearchTeams returns teams matching the provided search term.
+func (c *Client4) SearchTeams(search *TeamSearch) ([]*Team, *Response, error) {
+ buf, err := json.Marshal(search)
+ if err != nil {
+ return nil, nil, NewAppError("SearchTeams", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.teamsRoute()+"/search", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("SearchTeams", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// SearchTeamsPaged returns a page of teams and the total count matching the provided search term.
+func (c *Client4) SearchTeamsPaged(search *TeamSearch) ([]*Team, int64, *Response, error) {
+ if search.Page == nil {
+ search.Page = NewInt(0)
+ }
+ if search.PerPage == nil {
+ search.PerPage = NewInt(100)
+ }
+ buf, err := json.Marshal(search)
+ if err != nil {
+ return nil, 0, BuildResponse(nil), NewAppError("SearchTeamsPaged", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.teamsRoute()+"/search", buf)
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var listWithCount TeamsWithCount
+ if jsonErr := json.NewDecoder(r.Body).Decode(&listWithCount); jsonErr != nil {
+ return nil, 0, nil, NewAppError("GetAllTeamsWithTotalCount", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return listWithCount.Teams, listWithCount.TotalCount, BuildResponse(r), nil
+}
+
+// TeamExists returns true or false if the team exist or not.
+func (c *Client4) TeamExists(name, etag string) (bool, *Response, error) {
+ r, err := c.DoAPIGet(c.teamByNameRoute(name)+"/exists", etag)
+ if err != nil {
+ return false, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapBoolFromJSON(r.Body)["exists"], BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/teams", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamsForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetTeamMember returns a team member based on the provided team and user id strings.
+func (c *Client4) GetTeamMember(teamId, userId, etag string) (*TeamMember, *Response, error) {
+ r, err := c.DoAPIGet(c.teamMemberRoute(teamId, userId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tm TeamMember
+ if r.StatusCode == http.StatusNotModified {
+ return &tm, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tm); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamMember", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &tm, BuildResponse(r), nil
+}
+
+// UpdateTeamMemberRoles will update the roles on a team for a user.
+func (c *Client4) UpdateTeamMemberRoles(teamId, userId, newRoles string) (*Response, error) {
+ requestBody := map[string]string{"roles": newRoles}
+ r, err := c.DoAPIPut(c.teamMemberRoute(teamId, userId)+"/roles", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateTeamMemberSchemeRoles will update the scheme-derived roles on a team for a user.
+func (c *Client4) UpdateTeamMemberSchemeRoles(teamId string, userId string, schemeRoles *SchemeRoles) (*Response, error) {
+ buf, err := json.Marshal(schemeRoles)
+ if err != nil {
+ return nil, NewAppError("UpdateTeamMemberSchemeRoles", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.teamMemberRoute(teamId, userId)+"/schemeRoles", buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateTeam will update a team.
+func (c *Client4) UpdateTeam(team *Team) (*Team, *Response, error) {
+ buf, err := json.Marshal(team)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.teamRoute(team.Id), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// PatchTeam partially updates a team. Any missing fields are not updated.
+func (c *Client4) PatchTeam(teamId string, patch *TeamPatch) (*Team, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.teamRoute(teamId)+"/patch", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("PatchTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// RestoreTeam restores a previously deleted team.
+func (c *Client4) RestoreTeam(teamId string) (*Team, *Response, error) {
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/restore", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("RestoreTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// RegenerateTeamInviteId requests a new invite ID to be generated.
+func (c *Client4) RegenerateTeamInviteId(teamId string) (*Team, *Response, error) {
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/regenerate_invite_id", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("RegenerateTeamInviteId", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// SoftDeleteTeam deletes the team softly (archive only, not permanent delete).
+func (c *Client4) SoftDeleteTeam(teamId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.teamRoute(teamId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PermanentDeleteTeam deletes the team, should only be used when needed for
+// compliance and the like.
+func (c *Client4) PermanentDeleteTeam(teamId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.teamRoute(teamId) + "?permanent=" + c.boolString(true))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateTeamPrivacy modifies the team type (model.TeamOpen <--> model.TeamInvite) and sets
+// the corresponding AllowOpenInvite appropriately.
+func (c *Client4) UpdateTeamPrivacy(teamId string, privacy string) (*Team, *Response, error) {
+ requestBody := map[string]string{"privacy": privacy}
+ r, err := c.DoAPIPut(c.teamRoute(teamId)+"/privacy", MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateTeamPrivacy", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.teamMembersRoute(teamId)+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tms []*TeamMember
+ if r.StatusCode == http.StatusNotModified {
+ return tms, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tms); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamMembers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return tms, BuildResponse(r), nil
+}
+
+// GetTeamMembersWithoutDeletedUsers returns team members based on the provided team id string. Additional parameters of sort and exclude_deleted_users accepted as well
+// Could not add it to above function due to it be a breaking change.
+func (c *Client4) GetTeamMembersSortAndWithoutDeletedUsers(teamId string, page int, perPage int, sort string, excludeDeletedUsers bool, etag string) ([]*TeamMember, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&sort=%v&exclude_deleted_users=%v", page, perPage, sort, excludeDeletedUsers)
+ r, err := c.DoAPIGet(c.teamMembersRoute(teamId)+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tms []*TeamMember
+ if r.StatusCode == http.StatusNotModified {
+ return tms, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tms); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamMembersSortAndWithoutDeletedUsers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return tms, BuildResponse(r), nil
+}
+
+// GetTeamMembersForUser returns the team members for a user.
+func (c *Client4) GetTeamMembersForUser(userId string, etag string) ([]*TeamMember, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/teams/members", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tms []*TeamMember
+ if r.StatusCode == http.StatusNotModified {
+ return tms, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tms); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamMembersForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return tms, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJSON(userIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tms []*TeamMember
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tms); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamMembersByIds", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return tms, BuildResponse(r), nil
+}
+
+// AddTeamMember adds user to a team and return a team member.
+func (c *Client4) AddTeamMember(teamId, userId string) (*TeamMember, *Response, error) {
+ member := &TeamMember{TeamId: teamId, UserId: userId}
+ buf, err := json.Marshal(member)
+ if err != nil {
+ return nil, nil, NewAppError("AddTeamMember", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.teamMembersRoute(teamId), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tm TeamMember
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tm); jsonErr != nil {
+ return nil, nil, NewAppError("AddTeamMember", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &tm, BuildResponse(r), nil
+}
+
+// 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, error) {
+ var query string
+
+ if inviteId != "" {
+ query += fmt.Sprintf("?invite_id=%v", inviteId)
+ }
+
+ if token != "" {
+ query += fmt.Sprintf("?token=%v", token)
+ }
+
+ r, err := c.DoAPIPost(c.teamsRoute()+"/members/invite"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tm TeamMember
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tm); jsonErr != nil {
+ return nil, nil, NewAppError("AddTeamMemberFromInvite", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &tm, BuildResponse(r), nil
+}
+
+// AddTeamMembers adds a number of users to a team and returns the team members.
+func (c *Client4) AddTeamMembers(teamId string, userIds []string) ([]*TeamMember, *Response, error) {
+ var members []*TeamMember
+ for _, userId := range userIds {
+ member := &TeamMember{TeamId: teamId, UserId: userId}
+ members = append(members, member)
+ }
+ js, jsonErr := json.Marshal(members)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("AddTeamMembers", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.teamMembersRoute(teamId)+"/batch", string(js))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tms []*TeamMember
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tms); jsonErr != nil {
+ return nil, nil, NewAppError("AddTeamMembers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return tms, BuildResponse(r), nil
+}
+
+// AddTeamMembers adds a number of users to a team and returns the team members.
+func (c *Client4) AddTeamMembersGracefully(teamId string, userIds []string) ([]*TeamMemberWithError, *Response, error) {
+ var members []*TeamMember
+ for _, userId := range userIds {
+ member := &TeamMember{TeamId: teamId, UserId: userId}
+ members = append(members, member)
+ }
+ js, jsonErr := json.Marshal(members)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("AddTeamMembersGracefully", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+
+ r, err := c.DoAPIPost(c.teamMembersRoute(teamId)+"/batch?graceful="+c.boolString(true), string(js))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tms []*TeamMemberWithError
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tms); jsonErr != nil {
+ return nil, nil, NewAppError("AddTeamMembersGracefully", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return tms, BuildResponse(r), nil
+}
+
+// RemoveTeamMember will remove a user from a team.
+func (c *Client4) RemoveTeamMember(teamId, userId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.teamMemberRoute(teamId, userId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetTeamStats returns a team stats based on the team id string.
+// Must be authenticated.
+func (c *Client4) GetTeamStats(teamId, etag string) (*TeamStats, *Response, error) {
+ r, err := c.DoAPIGet(c.teamStatsRoute(teamId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ts TeamStats
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ts); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamStats", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &ts, BuildResponse(r), nil
+}
+
+// GetTotalUsersStats returns a total system user stats.
+// Must be authenticated.
+func (c *Client4) GetTotalUsersStats(etag string) (*UsersStats, *Response, error) {
+ r, err := c.DoAPIGet(c.totalUsersStatsRoute(), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var stats UsersStats
+ if jsonErr := json.NewDecoder(r.Body).Decode(&stats); jsonErr != nil {
+ return nil, nil, NewAppError("GetTotalUsersStats", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &stats, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+c.teamRoute(teamId)+"/unread", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tu TeamUnread
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tu); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamUnread", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &tu, BuildResponse(r), nil
+}
+
+// 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, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("file", filename)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return nil, nil, err
+ }
+
+ part, err = writer.CreateFormField("filesize")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if _, err = io.Copy(part, strings.NewReader(strconv.Itoa(filesize))); err != nil {
+ return nil, nil, err
+ }
+
+ part, err = writer.CreateFormField("importFrom")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if _, err := io.Copy(part, strings.NewReader(importFrom)); err != nil {
+ return nil, nil, err
+ }
+
+ if err := writer.Close(); err != nil {
+ return nil, nil, err
+ }
+
+ return c.DoUploadImportTeam(c.teamImportRoute(teamId), body.Bytes(), writer.FormDataContentType())
+}
+
+// InviteUsersToTeam invite users by email to the team.
+func (c *Client4) InviteUsersToTeam(teamId string, userEmails []string) (*Response, error) {
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/invite/email", ArrayToJSON(userEmails))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// InviteGuestsToTeam invite guest by email to some channels in a team.
+func (c *Client4) InviteGuestsToTeam(teamId string, userEmails []string, channels []string, message string) (*Response, error) {
+ guestsInvite := GuestsInvite{
+ Emails: userEmails,
+ Channels: channels,
+ Message: message,
+ }
+ buf, err := json.Marshal(guestsInvite)
+ if err != nil {
+ return nil, NewAppError("InviteGuestsToTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.teamRoute(teamId)+"/invite-guests/email", buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// InviteUsersToTeam invite users by email to the team.
+func (c *Client4) InviteUsersToTeamGracefully(teamId string, userEmails []string) ([]*EmailInviteWithError, *Response, error) {
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/invite/email?graceful="+c.boolString(true), ArrayToJSON(userEmails))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*EmailInviteWithError
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("InviteUsersToTeamGracefully", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// InviteGuestsToTeam invite guest by email to some channels in a team.
+func (c *Client4) InviteGuestsToTeamGracefully(teamId string, userEmails []string, channels []string, message string) ([]*EmailInviteWithError, *Response, error) {
+ guestsInvite := GuestsInvite{
+ Emails: userEmails,
+ Channels: channels,
+ Message: message,
+ }
+ buf, err := json.Marshal(guestsInvite)
+ if err != nil {
+ return nil, nil, NewAppError("InviteGuestsToTeamGracefully", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.teamRoute(teamId)+"/invite-guests/email?graceful="+c.boolString(true), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*EmailInviteWithError
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("InviteGuestsToTeamGracefully", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// InvalidateEmailInvites will invalidate active email invitations that have not been accepted by the user.
+func (c *Client4) InvalidateEmailInvites() (*Response, error) {
+ r, err := c.DoAPIDelete(c.teamsRoute() + "/invites/email")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetTeamInviteInfo returns a team object from an invite id containing sanitized information.
+func (c *Client4) GetTeamInviteInfo(inviteId string) (*Team, *Response, error) {
+ r, err := c.DoAPIGet(c.teamsRoute()+"/invite/"+inviteId, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var t Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&t); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamInviteInfo", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &t, BuildResponse(r), nil
+}
+
+// SetTeamIcon sets team icon of the team.
+func (c *Client4) SetTeamIcon(teamId string, data []byte) (*Response, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("image", "teamIcon.png")
+ if err != nil {
+ return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if err = writer.Close(); err != nil {
+ return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ rq, err := http.NewRequest("POST", c.APIURL+c.teamRoute(teamId)+"/image", bytes.NewReader(body.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+ rq.Header.Set("Content-Type", writer.FormDataContentType())
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ return BuildResponse(rp), nil
+}
+
+// GetTeamIcon gets the team icon of the team.
+func (c *Client4) GetTeamIcon(teamId, etag string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.teamRoute(teamId)+"/image", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetTeamIcon", "model.client.get_team_icon.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// RemoveTeamIcon updates LastTeamIconUpdate to 0 which indicates team icon is removed.
+func (c *Client4) RemoveTeamIcon(teamId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.teamRoute(teamId) + "/image")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Channel Section
+
+// GetAllChannels get all the channels. Must be a system administrator.
+func (c *Client4) GetAllChannels(page int, perPage int, etag string) (ChannelListWithTeamData, *Response, error) {
+ return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{})
+}
+
+// GetAllChannelsIncludeDeleted get all the channels. Must be a system administrator.
+func (c *Client4) GetAllChannelsIncludeDeleted(page int, perPage int, etag string) (ChannelListWithTeamData, *Response, error) {
+ return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{IncludeDeleted: true})
+}
+
+// GetAllChannelsExcludePolicyConstrained gets all channels which are not part of a data retention policy.
+// Must be a system administrator.
+func (c *Client4) GetAllChannelsExcludePolicyConstrained(page, perPage int, etag string) (ChannelListWithTeamData, *Response, error) {
+ return c.getAllChannels(page, perPage, etag, ChannelSearchOpts{ExcludePolicyConstrained: true})
+}
+
+func (c *Client4) getAllChannels(page int, perPage int, etag string, opts ChannelSearchOpts) (ChannelListWithTeamData, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=%v&exclude_policy_constrained=%v",
+ page, perPage, opts.IncludeDeleted, opts.ExcludePolicyConstrained)
+ r, err := c.DoAPIGet(c.channelsRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelListWithTeamData
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("getAllChannels", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetAllChannelsWithCount get all the channels including the total count. Must be a system administrator.
+func (c *Client4) GetAllChannelsWithCount(page int, perPage int, etag string) (ChannelListWithTeamData, int64, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&include_total_count="+c.boolString(true), page, perPage)
+ r, err := c.DoAPIGet(c.channelsRoute()+query, etag)
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var cwc *ChannelsWithCount
+ err = json.NewDecoder(r.Body).Decode(&cwc)
+ if err != nil {
+ return nil, 0, BuildResponse(r), NewAppError("GetAllChannelsWithCount", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return cwc.Channels, cwc.TotalCount, BuildResponse(r), nil
+}
+
+// CreateChannel creates a channel based on the provided channel struct.
+func (c *Client4) CreateChannel(channel *Channel) (*Channel, *Response, error) {
+ channelJSON, jsonErr := json.Marshal(channel)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("CreateChannel", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.channelsRoute(), string(channelJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("CreateChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// UpdateChannel updates a channel based on the provided channel struct.
+func (c *Client4) UpdateChannel(channel *Channel) (*Channel, *Response, error) {
+ channelJSON, jsonErr := json.Marshal(channel)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("UpdateChannel", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPut(c.channelRoute(channel.Id), string(channelJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("UpdateChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// PatchChannel partially updates a channel. Any missing fields are not updated.
+func (c *Client4) PatchChannel(channelId string, patch *ChannelPatch) (*Channel, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.channelRoute(channelId)+"/patch", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("PatchChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// UpdateChannelPrivacy updates channel privacy
+func (c *Client4) UpdateChannelPrivacy(channelId string, privacy ChannelType) (*Channel, *Response, error) {
+ requestBody := map[string]string{"privacy": string(privacy)}
+ r, err := c.DoAPIPut(c.channelRoute(channelId)+"/privacy", MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("UpdateChannelPrivacy", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// RestoreChannel restores a previously deleted channel. Any missing fields are not updated.
+func (c *Client4) RestoreChannel(channelId string) (*Channel, *Response, error) {
+ r, err := c.DoAPIPost(c.channelRoute(channelId)+"/restore", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("RestoreChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// CreateDirectChannel creates a direct message channel based on the two user
+// ids provided.
+func (c *Client4) CreateDirectChannel(userId1, userId2 string) (*Channel, *Response, error) {
+ requestBody := []string{userId1, userId2}
+ r, err := c.DoAPIPost(c.channelsRoute()+"/direct", ArrayToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("CreateDirectChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// CreateGroupChannel creates a group message channel based on userIds provided.
+func (c *Client4) CreateGroupChannel(userIds []string) (*Channel, *Response, error) {
+ r, err := c.DoAPIPost(c.channelsRoute()+"/group", ArrayToJSON(userIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("CreateGroupChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannel returns a channel based on the provided channel id string.
+func (c *Client4) GetChannel(channelId, etag string) (*Channel, *Response, error) {
+ r, err := c.DoAPIGet(c.channelRoute(channelId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelStats returns statistics for a channel.
+func (c *Client4) GetChannelStats(channelId string, etag string) (*ChannelStats, *Response, error) {
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/stats", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var stats ChannelStats
+ if jsonErr := json.NewDecoder(r.Body).Decode(&stats); jsonErr != nil {
+ return nil, nil, NewAppError("GetChannelStats", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &stats, BuildResponse(r), nil
+}
+
+// GetChannelMembersTimezones gets a list of timezones for a channel.
+func (c *Client4) GetChannelMembersTimezones(channelId string) ([]string, *Response, error) {
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/timezones", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ArrayFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// GetPinnedPosts gets a list of pinned posts.
+func (c *Client4) GetPinnedPosts(channelId string, etag string) (*PostList, *Response, error) {
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/pinned", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPinnedPosts", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// GetPrivateChannelsForTeam returns a list of private channels based on the provided team id string.
+func (c *Client4) GetPrivateChannelsForTeam(teamId string, page int, perPage int, etag string) ([]*Channel, *Response, error) {
+ query := fmt.Sprintf("/private?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.channelsForTeamRoute(teamId)+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetPrivateChannelsForTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.channelsForTeamRoute(teamId)+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetPublicChannelsForTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("/deleted?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.channelsForTeamRoute(teamId)+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetDeletedChannelsForTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetPublicChannelsByIdsForTeam returns a list of public channels based on provided team id string.
+func (c *Client4) GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) ([]*Channel, *Response, error) {
+ r, err := c.DoAPIPost(c.channelsForTeamRoute(teamId)+"/ids", ArrayToJSON(channelIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetPublicChannelsByIdsForTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelsForTeamForUser returns a list channels of on a team for a user.
+func (c *Client4) GetChannelsForTeamForUser(teamId, userId string, includeDeleted bool, etag string) ([]*Channel, *Response, error) {
+ r, err := c.DoAPIGet(c.channelsForTeamForUserRoute(teamId, userId, includeDeleted), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelsForTeamForUser", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelsForTeamAndUserWithLastDeleteAt returns a list channels of a team for a user, additionally filtered with lastDeleteAt. This does not have any effect if includeDeleted is set to false.
+func (c *Client4) GetChannelsForTeamAndUserWithLastDeleteAt(teamId, userId string, includeDeleted bool, lastDeleteAt int, etag string) ([]*Channel, *Response, error) {
+ route := fmt.Sprintf(c.userRoute(userId) + c.teamRoute(teamId) + "/channels")
+ route += fmt.Sprintf("?include_deleted=%v&last_delete_at=%d", includeDeleted, lastDeleteAt)
+ r, err := c.DoAPIGet(route, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelsForTeamAndUserWithLastDeleteAt", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// SearchChannels returns the channels on a team matching the provided search term.
+func (c *Client4) SearchChannels(teamId string, search *ChannelSearch) ([]*Channel, *Response, error) {
+ searchJSON, jsonErr := json.Marshal(search)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchChannels", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.channelsForTeamRoute(teamId)+"/search", string(searchJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("SearchChannels", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// SearchArchivedChannels returns the archived channels on a team matching the provided search term.
+func (c *Client4) SearchArchivedChannels(teamId string, search *ChannelSearch) ([]*Channel, *Response, error) {
+ searchJSON, jsonErr := json.Marshal(search)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchArchivedChannels", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.channelsForTeamRoute(teamId)+"/search_archived", string(searchJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("SearchArchivedChannels", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// SearchAllChannels search in all the channels. Must be a system administrator.
+func (c *Client4) SearchAllChannels(search *ChannelSearch) (ChannelListWithTeamData, *Response, error) {
+ searchJSON, jsonErr := json.Marshal(search)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchAllChannels", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.channelsRoute()+"/search", string(searchJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelListWithTeamData
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("SearchAllChannels", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// SearchAllChannelsPaged searches all the channels and returns the results paged with the total count.
+func (c *Client4) SearchAllChannelsPaged(search *ChannelSearch) (*ChannelsWithCount, *Response, error) {
+ searchJSON, jsonErr := json.Marshal(search)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchAllChannelsPaged", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.channelsRoute()+"/search", string(searchJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var cwc *ChannelsWithCount
+ err = json.NewDecoder(r.Body).Decode(&cwc)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetAllChannelsWithCount", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return cwc, BuildResponse(r), nil
+}
+
+// SearchGroupChannels returns the group channels of the user whose members' usernames match the search term.
+func (c *Client4) SearchGroupChannels(search *ChannelSearch) ([]*Channel, *Response, error) {
+ searchJSON, jsonErr := json.Marshal(search)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchGroupChannels", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.channelsRoute()+"/group/search", string(searchJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("SearchGroupChannels", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// DeleteChannel deletes channel based on the provided channel id string.
+func (c *Client4) DeleteChannel(channelId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.channelRoute(channelId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PermanentDeleteChannel deletes a channel based on the provided channel id string.
+func (c *Client4) PermanentDeleteChannel(channelId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.channelRoute(channelId) + "?permanent=" + c.boolString(true))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// MoveChannel moves the channel to the destination team.
+func (c *Client4) MoveChannel(channelId, teamId string, force bool) (*Channel, *Response, error) {
+ requestBody := map[string]interface{}{
+ "team_id": teamId,
+ "force": force,
+ }
+ r, err := c.DoAPIPost(c.channelRoute(channelId)+"/move", StringInterfaceToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("MoveChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.channelByNameRoute(channelName, teamId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelByName", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelByNameIncludeDeleted returns a channel based on the provided channel name and team id strings. Other then GetChannelByName it will also return deleted channels.
+func (c *Client4) GetChannelByNameIncludeDeleted(channelName, teamId string, etag string) (*Channel, *Response, error) {
+ r, err := c.DoAPIGet(c.channelByNameRoute(channelName, teamId)+"?include_deleted="+c.boolString(true), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelByNameIncludeDeleted", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.channelByNameForTeamNameRoute(channelName, teamName), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelByNameForTeamName", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelByNameForTeamNameIncludeDeleted returns a channel based on the provided channel name and team name strings. Other then GetChannelByNameForTeamName it will also return deleted channels.
+func (c *Client4) GetChannelByNameForTeamNameIncludeDeleted(channelName, teamName string, etag string) (*Channel, *Response, error) {
+ r, err := c.DoAPIGet(c.channelByNameForTeamNameRoute(channelName, teamName)+"?include_deleted="+c.boolString(true), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *Channel
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelByNameForTeamNameIncludeDeleted", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelMembers gets a page of channel members.
+func (c *Client4) GetChannelMembers(channelId string, page, perPage int, etag string) (ChannelMembers, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.channelMembersRoute(channelId)+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelMembers
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelMembers", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelMembersByIds gets the channel members in a channel for a list of user ids.
+func (c *Client4) GetChannelMembersByIds(channelId string, userIds []string) (ChannelMembers, *Response, error) {
+ r, err := c.DoAPIPost(c.channelMembersRoute(channelId)+"/ids", ArrayToJSON(userIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelMembers
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelMembersByIds", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelMember gets a channel member.
+func (c *Client4) GetChannelMember(channelId, userId, etag string) (*ChannelMember, *Response, error) {
+ r, err := c.DoAPIGet(c.channelMemberRoute(channelId, userId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *ChannelMember
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelMember", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// GetChannelMembersForUser gets all the channel members for a user on a team.
+func (c *Client4) GetChannelMembersForUser(userId, teamId, etag string) (ChannelMembers, *Response, error) {
+ r, err := c.DoAPIGet(fmt.Sprintf(c.userRoute(userId)+"/teams/%v/channels/members", teamId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelMembers
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelMembersForUser", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ url := fmt.Sprintf(c.channelsRoute()+"/members/%v/view", userId)
+ buf, err := json.Marshal(view)
+ if err != nil {
+ return nil, nil, NewAppError("ViewChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(url, buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *ChannelViewResponse
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("ViewChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+c.channelRoute(channelId)+"/unread", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *ChannelUnread
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelUnread", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// UpdateChannelRoles will update the roles on a channel for a user.
+func (c *Client4) UpdateChannelRoles(channelId, userId, roles string) (*Response, error) {
+ requestBody := map[string]string{"roles": roles}
+ r, err := c.DoAPIPut(c.channelMemberRoute(channelId, userId)+"/roles", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateChannelMemberSchemeRoles will update the scheme-derived roles on a channel for a user.
+func (c *Client4) UpdateChannelMemberSchemeRoles(channelId string, userId string, schemeRoles *SchemeRoles) (*Response, error) {
+ buf, err := json.Marshal(schemeRoles)
+ if err != nil {
+ return nil, NewAppError("UpdateChannelMemberSchemeRoles", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.channelMemberRoute(channelId, userId)+"/schemeRoles", buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateChannelNotifyProps will update the notification properties on a channel for a user.
+func (c *Client4) UpdateChannelNotifyProps(channelId, userId string, props map[string]string) (*Response, error) {
+ r, err := c.DoAPIPut(c.channelMemberRoute(channelId, userId)+"/notify_props", MapToJSON(props))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// AddChannelMember adds user to channel and return a channel member.
+func (c *Client4) AddChannelMember(channelId, userId string) (*ChannelMember, *Response, error) {
+ requestBody := map[string]string{"user_id": userId}
+ r, err := c.DoAPIPost(c.channelMembersRoute(channelId)+"", MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *ChannelMember
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("AddChannelMember", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// 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, error) {
+ requestBody := map[string]string{"user_id": userId, "post_root_id": postRootId}
+ r, err := c.DoAPIPost(c.channelMembersRoute(channelId)+"", MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch *ChannelMember
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("AddChannelMemberWithRootId", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// RemoveUserFromChannel will delete the channel member object for a user, effectively removing the user from a channel.
+func (c *Client4) RemoveUserFromChannel(channelId, userId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.channelMemberRoute(channelId, userId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// AutocompleteChannelsForTeam will return an ordered list of channels autocomplete suggestions.
+func (c *Client4) AutocompleteChannelsForTeam(teamId, name string) (ChannelList, *Response, error) {
+ query := fmt.Sprintf("?name=%v", name)
+ r, err := c.DoAPIGet(c.channelsForTeamRoute(teamId)+"/autocomplete"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelList
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("AutocompleteChannelsForTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// AutocompleteChannelsForTeamForSearch will return an ordered list of your channels autocomplete suggestions.
+func (c *Client4) AutocompleteChannelsForTeamForSearch(teamId, name string) (ChannelList, *Response, error) {
+ query := fmt.Sprintf("?name=%v", name)
+ r, err := c.DoAPIGet(c.channelsForTeamRoute(teamId)+"/search_autocomplete"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelList
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("AutocompleteChannelsForTeamForSearch", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// Post Section
+
+// CreatePost creates a post based on the provided post struct.
+func (c *Client4) CreatePost(post *Post) (*Post, *Response, error) {
+ postJSON, jsonErr := json.Marshal(post)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("CreatePost", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.postsRoute(), string(postJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p Post
+ if r.StatusCode == http.StatusNotModified {
+ return &p, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("CreatePost", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// 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, error) {
+ postJSON, jsonErr := json.Marshal(post)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("CreatePostEphemeral", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.postsEphemeralRoute(), string(postJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p Post
+ if r.StatusCode == http.StatusNotModified {
+ return &p, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("CreatePostEphemeral", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// UpdatePost updates a post based on the provided post struct.
+func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response, error) {
+ postJSON, jsonErr := json.Marshal(post)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("UpdatePost", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPut(c.postRoute(postId), string(postJSON))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p Post
+ if r.StatusCode == http.StatusNotModified {
+ return &p, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("UpdatePost", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// PatchPost partially updates a post. Any missing fields are not updated.
+func (c *Client4) PatchPost(postId string, patch *PostPatch) (*Post, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchPost", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.postRoute(postId)+"/patch", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p Post
+ if r.StatusCode == http.StatusNotModified {
+ return &p, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("PatchPost", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// SetPostUnread marks channel where post belongs as unread on the time of the provided post.
+func (c *Client4) SetPostUnread(userId string, postId string, collapsedThreadsSupported bool) (*Response, error) {
+ b, err := json.Marshal(map[string]bool{"collapsed_threads_supported": collapsedThreadsSupported})
+ if err != nil {
+ return nil, NewAppError("SetPostUnread", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.userRoute(userId)+c.postRoute(postId)+"/set_unread", b)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PinPost pin a post based on provided post id string.
+func (c *Client4) PinPost(postId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.postRoute(postId)+"/pin", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UnpinPost unpin a post based on provided post id string.
+func (c *Client4) UnpinPost(postId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.postRoute(postId)+"/unpin", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetPost gets a single post.
+func (c *Client4) GetPost(postId string, etag string) (*Post, *Response, error) {
+ r, err := c.DoAPIGet(c.postRoute(postId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var post Post
+ if r.StatusCode == http.StatusNotModified {
+ return &post, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&post); jsonErr != nil {
+ return nil, nil, NewAppError("GetPost", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &post, BuildResponse(r), nil
+}
+
+// DeletePost deletes a post from the provided post id string.
+func (c *Client4) DeletePost(postId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.postRoute(postId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetPostThread gets a post with all the other posts in the same thread.
+func (c *Client4) GetPostThread(postId string, etag string, collapsedThreads bool) (*PostList, *Response, error) {
+ url := c.postRoute(postId) + "/thread"
+ if collapsedThreads {
+ url += "?collapsedThreads=true"
+ }
+ r, err := c.DoAPIGet(url, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPostThread", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// 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, collapsedThreads bool) (*PostList, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ if collapsedThreads {
+ query += "&collapsedThreads=true"
+ }
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/posts"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPostsForChannel", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// GetFlaggedPostsForUser returns flagged posts of a user based on user id string.
+func (c *Client4) GetFlaggedPostsForUser(userId string, page int, perPage int) (*PostList, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/posts/flagged"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetFlaggedPostsForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ if !IsValidId(teamId) {
+ return nil, nil, 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)
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/posts/flagged"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetFlaggedPostsForUserInTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ if !IsValidId(channelId) {
+ return nil, nil, 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)
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/posts/flagged"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetFlaggedPostsForUserInChannel", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// GetPostsSince gets posts created after a specified time as Unix time in milliseconds.
+func (c *Client4) GetPostsSince(channelId string, time int64, collapsedThreads bool) (*PostList, *Response, error) {
+ query := fmt.Sprintf("?since=%v", time)
+ if collapsedThreads {
+ query += "&collapsedThreads=true"
+ }
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/posts"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPostsSince", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// 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, collapsedThreads bool) (*PostList, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&after=%v", page, perPage, postId)
+ if collapsedThreads {
+ query += "&collapsedThreads=true"
+ }
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/posts"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPostsAfter", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// 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, collapsedThreads bool) (*PostList, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&before=%v", page, perPage, postId)
+ if collapsedThreads {
+ query += "&collapsedThreads=true"
+ }
+ r, err := c.DoAPIGet(c.channelRoute(channelId)+"/posts"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPostsBefore", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// GetPostsAroundLastUnread gets a list of posts around last unread post by a user in a channel.
+func (c *Client4) GetPostsAroundLastUnread(userId, channelId string, limitBefore, limitAfter int, collapsedThreads bool) (*PostList, *Response, error) {
+ query := fmt.Sprintf("?limit_before=%v&limit_after=%v", limitBefore, limitAfter)
+ if collapsedThreads {
+ query += "&collapsedThreads=true"
+ }
+ r, err := c.DoAPIGet(c.userRoute(userId)+c.channelRoute(channelId)+"/posts/unread"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPostsAroundLastUnread", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// SearchFiles returns any posts with matching terms string.
+func (c *Client4) SearchFiles(teamId string, terms string, isOrSearch bool) (*FileInfoList, *Response, error) {
+ params := SearchParameter{
+ Terms: &terms,
+ IsOrSearch: &isOrSearch,
+ }
+ return c.SearchFilesWithParams(teamId, &params)
+}
+
+// SearchFilesWithParams returns any posts with matching terms string.
+func (c *Client4) SearchFilesWithParams(teamId string, params *SearchParameter) (*FileInfoList, *Response, error) {
+ js, jsonErr := json.Marshal(params)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchFilesWithParams", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/files/search", string(js))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list FileInfoList
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("SearchFilesWithParams", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// SearchPosts returns any posts with matching terms string.
+func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*PostList, *Response, error) {
+ params := SearchParameter{
+ Terms: &terms,
+ IsOrSearch: &isOrSearch,
+ }
+ return c.SearchPostsWithParams(teamId, &params)
+}
+
+// SearchPostsWithParams returns any posts with matching terms string.
+func (c *Client4) SearchPostsWithParams(teamId string, params *SearchParameter) (*PostList, *Response, error) {
+ js, jsonErr := json.Marshal(params)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("SearchFilesWithParams", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/posts/search", string(js))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PostList
+ if r.StatusCode == http.StatusNotModified {
+ return &list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("SearchFilesWithParams", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &list, BuildResponse(r), nil
+}
+
+// SearchPostsWithMatches returns any posts with matching terms string, including.
+func (c *Client4) SearchPostsWithMatches(teamId string, terms string, isOrSearch bool) (*PostSearchResults, *Response, error) {
+ requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch}
+ r, err := c.DoAPIPost(c.teamRoute(teamId)+"/posts/search", StringInterfaceToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var psr PostSearchResults
+ if jsonErr := json.NewDecoder(r.Body).Decode(&psr); jsonErr != nil {
+ return nil, nil, NewAppError("SearchPostsWithMatches", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &psr, BuildResponse(r), nil
+}
+
+// DoPostAction performs a post action.
+func (c *Client4) DoPostAction(postId, actionId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.postRoute(postId)+"/actions/"+actionId, "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DoPostActionWithCookie performs a post action with extra arguments
+func (c *Client4) DoPostActionWithCookie(postId, actionId, selected, cookieStr string) (*Response, error) {
+ var body []byte
+ if selected != "" || cookieStr != "" {
+ body, _ = json.Marshal(DoPostActionRequest{
+ SelectedOption: selected,
+ Cookie: cookieStr,
+ })
+ }
+ r, err := c.DoAPIPost(c.postRoute(postId)+"/actions/"+actionId, string(body))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// OpenInteractiveDialog sends a WebSocket event to a user's clients to
+// open interactive dialogs, based on the provided trigger ID and other
+// provided data. Used with interactive message buttons, menus and
+// slash commands.
+func (c *Client4) OpenInteractiveDialog(request OpenDialogRequest) (*Response, error) {
+ b, _ := json.Marshal(request)
+ r, err := c.DoAPIPost("/actions/dialogs/open", string(b))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// SubmitInteractiveDialog will submit the provided dialog data to the integration
+// configured by the URL. Used with the interactive dialogs integration feature.
+func (c *Client4) SubmitInteractiveDialog(request SubmitDialogRequest) (*SubmitDialogResponse, *Response, error) {
+ b, _ := json.Marshal(request)
+ r, err := c.DoAPIPost("/actions/dialogs/submit", string(b))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var resp SubmitDialogResponse
+ json.NewDecoder(r.Body).Decode(&resp)
+ return &resp, BuildResponse(r), nil
+}
+
+// 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, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormField("channel_id")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ _, err = io.Copy(part, strings.NewReader(channelId))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ part, err = writer.CreateFormFile("files", filename)
+ if err != nil {
+ return nil, nil, err
+ }
+ _, err = io.Copy(part, bytes.NewBuffer(data))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ err = writer.Close()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return c.DoUploadFile(c.filesRoute(), 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, error) {
+ return c.DoUploadFile(c.filesRoute()+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, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+fmt.Sprintf("?download=%v", download), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("DownloadFile", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// GetFileThumbnail gets the bytes for a file by id.
+func (c *Client4) GetFileThumbnail(fileId string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+"/thumbnail", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+fmt.Sprintf("/thumbnail?download=%v", download), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("DownloadFileThumbnail", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// GetFileLink gets the public link of a file by id.
+func (c *Client4) GetFileLink(fileId string) (string, *Response, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+"/link", "")
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["link"], BuildResponse(r), nil
+}
+
+// GetFilePreview gets the bytes for a file by id.
+func (c *Client4) GetFilePreview(fileId string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+"/preview", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetFilePreview", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// DownloadFilePreview gets the bytes for a file by id.
+func (c *Client4) DownloadFilePreview(fileId string, download bool) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+fmt.Sprintf("/preview?download=%v", download), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("DownloadFilePreview", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// GetFileInfo gets all the file info objects.
+func (c *Client4) GetFileInfo(fileId string) (*FileInfo, *Response, error) {
+ r, err := c.DoAPIGet(c.fileRoute(fileId)+"/info", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var fi FileInfo
+ if jsonErr := json.NewDecoder(r.Body).Decode(&fi); jsonErr != nil {
+ return nil, nil, NewAppError("GetFileInfo", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &fi, BuildResponse(r), nil
+}
+
+// GetFileInfosForPost gets all the file info objects attached to a post.
+func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo, *Response, error) {
+ r, err := c.DoAPIGet(c.postRoute(postId)+"/files/info", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list []*FileInfo
+ if r.StatusCode == http.StatusNotModified {
+ return list, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetFileInfosForPost", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// General/System Section
+
+// GenerateSupportPacket downloads the generated support packet
+func (c *Client4) GenerateSupportPacket() ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.systemRoute()+"/support_packet", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetFile", "model.client.read_job_result_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above.
+func (c *Client4) GetPing() (string, *Response, error) {
+ r, err := c.DoAPIGet(c.systemRoute()+"/ping", "")
+ if r != nil && r.StatusCode == 500 {
+ defer r.Body.Close()
+ return StatusUnhealthy, BuildResponse(r), err
+ }
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["status"], BuildResponse(r), nil
+}
+
+// GetPingWithServerStatus will return ok if several basic server health checks
+// all pass successfully.
+func (c *Client4) GetPingWithServerStatus() (string, *Response, error) {
+ r, err := c.DoAPIGet(c.systemRoute()+"/ping?get_server_status="+c.boolString(true), "")
+ if r != nil && r.StatusCode == 500 {
+ defer r.Body.Close()
+ return StatusUnhealthy, BuildResponse(r), err
+ }
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["status"], BuildResponse(r), nil
+}
+
+// GetPingWithFullServerStatus will return the full status if several basic server
+// health checks all pass successfully.
+func (c *Client4) GetPingWithFullServerStatus() (map[string]string, *Response, error) {
+ r, err := c.DoAPIGet(c.systemRoute()+"/ping?get_server_status="+c.boolString(true), "")
+ if r != nil && r.StatusCode == 500 {
+ defer r.Body.Close()
+ return map[string]string{"status": StatusUnhealthy}, BuildResponse(r), err
+ }
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// TestEmail will attempt to connect to the configured SMTP server.
+func (c *Client4) TestEmail(config *Config) (*Response, error) {
+ buf, err := json.Marshal(config)
+ if err != nil {
+ return nil, NewAppError("TestEmail", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.testEmailRoute(), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// TestSiteURL will test the validity of a site URL.
+func (c *Client4) TestSiteURL(siteURL string) (*Response, error) {
+ requestBody := make(map[string]string)
+ requestBody["site_url"] = siteURL
+ r, err := c.DoAPIPost(c.testSiteURLRoute(), MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// TestS3Connection will attempt to connect to the AWS S3.
+func (c *Client4) TestS3Connection(config *Config) (*Response, error) {
+ buf, err := json.Marshal(config)
+ if err != nil {
+ return nil, NewAppError("TestS3Connection", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.testS3Route(), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetConfig will retrieve the server config with some sanitized items.
+func (c *Client4) GetConfig() (*Config, *Response, error) {
+ r, err := c.DoAPIGet(c.configRoute(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ConfigFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// ReloadConfig will reload the server configuration.
+func (c *Client4) ReloadConfig() (*Response, error) {
+ r, err := c.DoAPIPost(c.configRoute()+"/reload", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.configRoute()+"/client?format=old", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.configRoute()+"/environment", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return StringInterfaceFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.licenseRoute()+"/client?format=old", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// DatabaseRecycle will recycle the connections. Discard current connection and get new one.
+func (c *Client4) DatabaseRecycle() (*Response, error) {
+ r, err := c.DoAPIPost(c.databaseRoute()+"/recycle", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// InvalidateCaches will purge the cache and can affect the performance while is cleaning.
+func (c *Client4) InvalidateCaches() (*Response, error) {
+ r, err := c.DoAPIPost(c.cacheRoute()+"/invalidate", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateConfig will update the server configuration.
+func (c *Client4) UpdateConfig(config *Config) (*Config, *Response, error) {
+ buf, err := json.Marshal(config)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateConfig", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.configRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ConfigFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// MigrateConfig will migrate existing config to the new one.
+func (c *Client4) MigrateConfig(from, to string) (*Response, error) {
+ m := make(map[string]string, 2)
+ m["from"] = from
+ m["to"] = to
+ r, err := c.DoAPIPost(c.configRoute()+"/migrate", MapToJSON(m))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UploadLicenseFile will add a license file to the system.
+func (c *Client4) UploadLicenseFile(data []byte) (*Response, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("license", "test-license.mattermost-license")
+ if err != nil {
+ return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if err = writer.Close(); err != nil {
+ return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ rq, err := http.NewRequest("POST", c.APIURL+c.licenseRoute(), bytes.NewReader(body.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+ rq.Header.Set("Content-Type", writer.FormDataContentType())
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ return BuildResponse(rp), nil
+}
+
+// RemoveLicenseFile will remove the server license it exists. Note that this will
+// disable all enterprise features.
+func (c *Client4) RemoveLicenseFile() (*Response, error) {
+ r, err := c.DoAPIDelete(c.licenseRoute())
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?name=%v&team_id=%v", name, teamId)
+ r, err := c.DoAPIGet(c.analyticsRoute()+"/old"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var rows AnalyticsRows
+ err = json.NewDecoder(r.Body).Decode(&rows)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetAnalyticsOld", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return rows, BuildResponse(r), nil
+}
+
+// Webhooks Section
+
+// CreateIncomingWebhook creates an incoming webhook for a channel.
+func (c *Client4) CreateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response, error) {
+ buf, err := json.Marshal(hook)
+ if err != nil {
+ return nil, nil, NewAppError("CreateIncomingWebhook", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.incomingWebhooksRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var iw IncomingWebhook
+ if jsonErr := json.NewDecoder(r.Body).Decode(&iw); jsonErr != nil {
+ return nil, nil, NewAppError("CreateIncomingWebhook", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &iw, BuildResponse(r), nil
+}
+
+// UpdateIncomingWebhook updates an incoming webhook for a channel.
+func (c *Client4) UpdateIncomingWebhook(hook *IncomingWebhook) (*IncomingWebhook, *Response, error) {
+ buf, err := json.Marshal(hook)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateIncomingWebhook", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.incomingWebhookRoute(hook.Id), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var iw IncomingWebhook
+ if jsonErr := json.NewDecoder(r.Body).Decode(&iw); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateIncomingWebhook", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &iw, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.incomingWebhooksRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var iwl []*IncomingWebhook
+ if r.StatusCode == http.StatusNotModified {
+ return iwl, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&iwl); jsonErr != nil {
+ return nil, nil, NewAppError("GetIncomingWebhooks", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return iwl, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId)
+ r, err := c.DoAPIGet(c.incomingWebhooksRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var iwl []*IncomingWebhook
+ if r.StatusCode == http.StatusNotModified {
+ return iwl, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&iwl); jsonErr != nil {
+ return nil, nil, NewAppError("GetIncomingWebhooksForTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return iwl, BuildResponse(r), nil
+}
+
+// GetIncomingWebhook returns an Incoming webhook given the hook ID.
+func (c *Client4) GetIncomingWebhook(hookID string, etag string) (*IncomingWebhook, *Response, error) {
+ r, err := c.DoAPIGet(c.incomingWebhookRoute(hookID), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var iw IncomingWebhook
+ if r.StatusCode == http.StatusNotModified {
+ return &iw, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&iw); jsonErr != nil {
+ return nil, nil, NewAppError("GetIncomingWebhook", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &iw, BuildResponse(r), nil
+}
+
+// DeleteIncomingWebhook deletes and Incoming Webhook given the hook ID.
+func (c *Client4) DeleteIncomingWebhook(hookID string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.incomingWebhookRoute(hookID))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// CreateOutgoingWebhook creates an outgoing webhook for a team or channel.
+func (c *Client4) CreateOutgoingWebhook(hook *OutgoingWebhook) (*OutgoingWebhook, *Response, error) {
+ buf, err := json.Marshal(hook)
+ if err != nil {
+ return nil, nil, NewAppError("CreateOutgoingWebhook", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.outgoingWebhooksRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ow OutgoingWebhook
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ow); jsonErr != nil {
+ return nil, nil, NewAppError("CreateOutgoingWebhook", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &ow, BuildResponse(r), nil
+}
+
+// UpdateOutgoingWebhook creates an outgoing webhook for a team or channel.
+func (c *Client4) UpdateOutgoingWebhook(hook *OutgoingWebhook) (*OutgoingWebhook, *Response, error) {
+ buf, err := json.Marshal(hook)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateOutgoingWebhook", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.outgoingWebhookRoute(hook.Id), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ow OutgoingWebhook
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ow); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateOutgoingWebhook", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &ow, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.outgoingWebhooksRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var owl []*OutgoingWebhook
+ if r.StatusCode == http.StatusNotModified {
+ return owl, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&owl); jsonErr != nil {
+ return nil, nil, NewAppError("GetOutgoingWebhooks", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return owl, BuildResponse(r), nil
+}
+
+// GetOutgoingWebhook outgoing webhooks on the system requested by Hook Id.
+func (c *Client4) GetOutgoingWebhook(hookId string) (*OutgoingWebhook, *Response, error) {
+ r, err := c.DoAPIGet(c.outgoingWebhookRoute(hookId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ow OutgoingWebhook
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ow); jsonErr != nil {
+ return nil, nil, NewAppError("GetOutgoingWebhook", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &ow, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&channel_id=%v", page, perPage, channelId)
+ r, err := c.DoAPIGet(c.outgoingWebhooksRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var owl []*OutgoingWebhook
+ if r.StatusCode == http.StatusNotModified {
+ return owl, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&owl); jsonErr != nil {
+ return nil, nil, NewAppError("GetOutgoingWebhooksForChannel", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return owl, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId)
+ r, err := c.DoAPIGet(c.outgoingWebhooksRoute()+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var owl []*OutgoingWebhook
+ if r.StatusCode == http.StatusNotModified {
+ return owl, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&owl); jsonErr != nil {
+ return nil, nil, NewAppError("GetOutgoingWebhooksForTeam", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return owl, BuildResponse(r), nil
+}
+
+// RegenOutgoingHookToken regenerate the outgoing webhook token.
+func (c *Client4) RegenOutgoingHookToken(hookId string) (*OutgoingWebhook, *Response, error) {
+ r, err := c.DoAPIPost(c.outgoingWebhookRoute(hookId)+"/regen_token", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ow OutgoingWebhook
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ow); jsonErr != nil {
+ return nil, nil, NewAppError("RegenOutgoingHookToken", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &ow, BuildResponse(r), nil
+}
+
+// DeleteOutgoingWebhook delete the outgoing webhook on the system requested by Hook Id.
+func (c *Client4) DeleteOutgoingWebhook(hookId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.outgoingWebhookRoute(hookId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Preferences Section
+
+// GetPreferences returns the user's preferences.
+func (c *Client4) GetPreferences(userId string) (Preferences, *Response, error) {
+ r, err := c.DoAPIGet(c.preferencesRoute(userId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var prefs Preferences
+ if jsonErr := json.NewDecoder(r.Body).Decode(&prefs); jsonErr != nil {
+ return nil, nil, NewAppError("GetPreferences", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return prefs, BuildResponse(r), nil
+}
+
+// UpdatePreferences saves the user's preferences.
+func (c *Client4) UpdatePreferences(userId string, preferences Preferences) (*Response, error) {
+ buf, err := json.Marshal(preferences)
+ if err != nil {
+ return nil, NewAppError("UpdatePreferences", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.preferencesRoute(userId), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DeletePreferences deletes the user's preferences.
+func (c *Client4) DeletePreferences(userId string, preferences Preferences) (*Response, error) {
+ buf, err := json.Marshal(preferences)
+ if err != nil {
+ return nil, NewAppError("DeletePreferences", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.preferencesRoute(userId)+"/delete", buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetPreferencesByCategory returns the user's preferences from the provided category string.
+func (c *Client4) GetPreferencesByCategory(userId string, category string) (Preferences, *Response, error) {
+ url := fmt.Sprintf(c.preferencesRoute(userId)+"/%s", category)
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var prefs Preferences
+ if jsonErr := json.NewDecoder(r.Body).Decode(&prefs); jsonErr != nil {
+ return nil, nil, NewAppError("GetPreferencesByCategory", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return prefs, BuildResponse(r), nil
+}
+
+// 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, error) {
+ url := fmt.Sprintf(c.preferencesRoute(userId)+"/%s/name/%v", category, preferenceName)
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var pref Preference
+ if jsonErr := json.NewDecoder(r.Body).Decode(&pref); jsonErr != nil {
+ return nil, nil, NewAppError("GetPreferenceByCategoryAndName", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &pref, BuildResponse(r), nil
+}
+
+// SAML Section
+
+// GetSamlMetadata returns metadata for the SAML configuration.
+func (c *Client4) GetSamlMetadata() (string, *Response, error) {
+ r, err := c.DoAPIGet(c.samlRoute()+"/metadata", "")
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ buf := new(bytes.Buffer)
+ _, err = buf.ReadFrom(r.Body)
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+
+ return buf.String(), BuildResponse(r), nil
+}
+
+func fileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("certificate", filename)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ 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.
+// The filename parameter is deprecated and ignored: the server will pick a hard-coded filename when writing to disk.
+func (c *Client4) UploadSamlIdpCertificate(data []byte, filename string) (*Response, error) {
+ body, writer, err := fileToMultipart(data, filename)
+ if err != nil {
+ return nil, NewAppError("UploadSamlIdpCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ _, resp, err := c.DoUploadFile(c.samlRoute()+"/certificate/idp", body, writer.FormDataContentType())
+ return resp, err
+}
+
+// UploadSamlPublicCertificate will upload a public certificate for SAML and set the config to use it.
+// The filename parameter is deprecated and ignored: the server will pick a hard-coded filename when writing to disk.
+func (c *Client4) UploadSamlPublicCertificate(data []byte, filename string) (*Response, error) {
+ body, writer, err := fileToMultipart(data, filename)
+ if err != nil {
+ return nil, NewAppError("UploadSamlPublicCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ _, resp, err := c.DoUploadFile(c.samlRoute()+"/certificate/public", body, writer.FormDataContentType())
+ return resp, err
+}
+
+// UploadSamlPrivateCertificate will upload a private key for SAML and set the config to use it.
+// The filename parameter is deprecated and ignored: the server will pick a hard-coded filename when writing to disk.
+func (c *Client4) UploadSamlPrivateCertificate(data []byte, filename string) (*Response, error) {
+ body, writer, err := fileToMultipart(data, filename)
+ if err != nil {
+ return nil, NewAppError("UploadSamlPrivateCertificate", "model.client.upload_saml_cert.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ _, resp, err := c.DoUploadFile(c.samlRoute()+"/certificate/private", body, writer.FormDataContentType())
+ return resp, err
+}
+
+// DeleteSamlIdpCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML.
+func (c *Client4) DeleteSamlIdpCertificate() (*Response, error) {
+ r, err := c.DoAPIDelete(c.samlRoute() + "/certificate/idp")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DeleteSamlPublicCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML.
+func (c *Client4) DeleteSamlPublicCertificate() (*Response, error) {
+ r, err := c.DoAPIDelete(c.samlRoute() + "/certificate/public")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DeleteSamlPrivateCertificate deletes the SAML IDP certificate from the server and updates the config to not use it and disable SAML.
+func (c *Client4) DeleteSamlPrivateCertificate() (*Response, error) {
+ r, err := c.DoAPIDelete(c.samlRoute() + "/certificate/private")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetSamlCertificateStatus returns metadata for the SAML configuration.
+func (c *Client4) GetSamlCertificateStatus() (*SamlCertificateStatus, *Response, error) {
+ r, err := c.DoAPIGet(c.samlRoute()+"/certificate/status", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var status SamlCertificateStatus
+ if jsonErr := json.NewDecoder(r.Body).Decode(&status); jsonErr != nil {
+ return nil, nil, NewAppError("GetSamlCertificateStatus", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &status, BuildResponse(r), nil
+}
+
+func (c *Client4) GetSamlMetadataFromIdp(samlMetadataURL string) (*SamlMetadataResponse, *Response, error) {
+ requestBody := make(map[string]string)
+ requestBody["saml_metadata_url"] = samlMetadataURL
+ r, err := c.DoAPIPost(c.samlRoute()+"/metadatafromidp", MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+
+ defer closeBody(r)
+ var resp SamlMetadataResponse
+ if jsonErr := json.NewDecoder(r.Body).Decode(&resp); jsonErr != nil {
+ return nil, nil, NewAppError("GetSamlMetadataFromIdp", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &resp, BuildResponse(r), nil
+}
+
+// ResetSamlAuthDataToEmail resets the AuthData field of SAML users to their Email.
+func (c *Client4) ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (int64, *Response, error) {
+ params := map[string]interface{}{
+ "include_deleted": includeDeleted,
+ "dry_run": dryRun,
+ "user_ids": userIDs,
+ }
+ b, _ := json.Marshal(params)
+ r, err := c.DoAPIPostBytes(c.samlRoute()+"/reset_auth_data", b)
+ if err != nil {
+ return 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ respBody := map[string]int64{}
+ err = json.NewDecoder(r.Body).Decode(&respBody)
+ if err != nil {
+ return 0, BuildResponse(r), NewAppError("Api4.ResetSamlAuthDataToEmail", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return respBody["num_affected"], BuildResponse(r), nil
+}
+
+// Compliance Section
+
+// CreateComplianceReport creates an incoming webhook for a channel.
+func (c *Client4) CreateComplianceReport(report *Compliance) (*Compliance, *Response, error) {
+ buf, err := json.Marshal(report)
+ if err != nil {
+ return nil, nil, NewAppError("CreateComplianceReport", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.complianceReportsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var comp Compliance
+ if jsonErr := json.NewDecoder(r.Body).Decode(&comp); jsonErr != nil {
+ return nil, nil, NewAppError("CreateComplianceReport", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &comp, BuildResponse(r), nil
+}
+
+// GetComplianceReports returns list of compliance reports.
+func (c *Client4) GetComplianceReports(page, perPage int) (Compliances, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.complianceReportsRoute()+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var comp Compliances
+ if jsonErr := json.NewDecoder(r.Body).Decode(&comp); jsonErr != nil {
+ return nil, nil, NewAppError("GetComplianceReports", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return comp, BuildResponse(r), nil
+}
+
+// GetComplianceReport returns a compliance report.
+func (c *Client4) GetComplianceReport(reportId string) (*Compliance, *Response, error) {
+ r, err := c.DoAPIGet(c.complianceReportRoute(reportId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var comp Compliance
+ if jsonErr := json.NewDecoder(r.Body).Decode(&comp); jsonErr != nil {
+ return nil, nil, NewAppError("GetComplianceReport", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &comp, BuildResponse(r), nil
+}
+
+// DownloadComplianceReport returns a full compliance report as a file.
+func (c *Client4) DownloadComplianceReport(reportId string) ([]byte, *Response, error) {
+ rq, err := http.NewRequest("GET", c.APIURL+c.complianceReportDownloadRoute(reportId), nil)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, "BEARER "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return nil, BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ data, err := ioutil.ReadAll(rp.Body)
+ if err != nil {
+ return nil, BuildResponse(rp), NewAppError("DownloadComplianceReport", "model.client.read_file.app_error", nil, err.Error(), rp.StatusCode)
+ }
+
+ return data, BuildResponse(rp), nil
+}
+
+// Cluster Section
+
+// GetClusterStatus returns the status of all the configured cluster nodes.
+func (c *Client4) GetClusterStatus() ([]*ClusterInfo, *Response, error) {
+ r, err := c.DoAPIGet(c.clusterRoute()+"/status", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*ClusterInfo
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetClusterStatus", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// LDAP Section
+
+// SyncLdap will force a sync with the configured LDAP server.
+// If includeRemovedMembers is true, then group members who left or were removed from a
+// synced team/channel will be re-joined; otherwise, they will be excluded.
+func (c *Client4) SyncLdap(includeRemovedMembers bool) (*Response, error) {
+ reqBody, _ := json.Marshal(map[string]interface{}{
+ "include_removed_members": includeRemovedMembers,
+ })
+ r, err := c.DoAPIPostBytes(c.ldapRoute()+"/sync", reqBody)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// TestLdap will attempt to connect to the configured LDAP server and return OK if configured
+// correctly.
+func (c *Client4) TestLdap() (*Response, error) {
+ r, err := c.DoAPIPost(c.ldapRoute()+"/test", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetLdapGroups retrieves the immediate child groups of the given parent group.
+func (c *Client4) GetLdapGroups() ([]*Group, *Response, error) {
+ path := fmt.Sprintf("%s/groups", c.ldapRoute())
+
+ r, err := c.DoAPIGet(path, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ responseData := struct {
+ Count int `json:"count"`
+ Groups []*Group `json:"groups"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil {
+ return nil, BuildResponse(r), NewAppError("Api4.GetLdapGroups", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ for i := range responseData.Groups {
+ responseData.Groups[i].DisplayName = *responseData.Groups[i].Name
+ }
+
+ return responseData.Groups, BuildResponse(r), nil
+}
+
+// LinkLdapGroup creates or undeletes a Mattermost group and associates it to the given LDAP group DN.
+func (c *Client4) LinkLdapGroup(dn string) (*Group, *Response, error) {
+ path := fmt.Sprintf("%s/groups/%s/link", c.ldapRoute(), dn)
+
+ r, err := c.DoAPIPost(path, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var g Group
+ if jsonErr := json.NewDecoder(r.Body).Decode(&g); jsonErr != nil {
+ return nil, nil, NewAppError("LinkLdapGroup", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &g, BuildResponse(r), nil
+}
+
+// UnlinkLdapGroup deletes the Mattermost group associated with the given LDAP group DN.
+func (c *Client4) UnlinkLdapGroup(dn string) (*Group, *Response, error) {
+ path := fmt.Sprintf("%s/groups/%s/link", c.ldapRoute(), dn)
+
+ r, err := c.DoAPIDelete(path)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var g Group
+ if jsonErr := json.NewDecoder(r.Body).Decode(&g); jsonErr != nil {
+ return nil, nil, NewAppError("UnlinkLdapGroup", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &g, BuildResponse(r), nil
+}
+
+// MigrateIdLdap migrates the LDAP enabled users to given attribute
+func (c *Client4) MigrateIdLdap(toAttribute string) (*Response, error) {
+ r, err := c.DoAPIPost(c.ldapRoute()+"/migrateid", MapToJSON(map[string]string{
+ "toAttribute": toAttribute,
+ }))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetGroupsByChannel retrieves the Mattermost Groups associated with a given channel
+func (c *Client4) GetGroupsByChannel(channelId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response, error) {
+ path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v&filter_allow_reference=%v", c.channelRoute(channelId), opts.Q, opts.IncludeMemberCount, opts.FilterAllowReference)
+ if opts.PageOpts != nil {
+ path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage)
+ }
+ r, err := c.DoAPIGet(path, "")
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ responseData := struct {
+ Groups []*GroupWithSchemeAdmin `json:"groups"`
+ Count int `json:"total_group_count"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil {
+ return nil, 0, BuildResponse(r), NewAppError("Api4.GetGroupsByChannel", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return responseData.Groups, responseData.Count, BuildResponse(r), nil
+}
+
+// GetGroupsByTeam retrieves the Mattermost Groups associated with a given team
+func (c *Client4) GetGroupsByTeam(teamId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response, error) {
+ path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v&filter_allow_reference=%v", c.teamRoute(teamId), opts.Q, opts.IncludeMemberCount, opts.FilterAllowReference)
+ if opts.PageOpts != nil {
+ path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage)
+ }
+ r, err := c.DoAPIGet(path, "")
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ responseData := struct {
+ Groups []*GroupWithSchemeAdmin `json:"groups"`
+ Count int `json:"total_group_count"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil {
+ return nil, 0, BuildResponse(r), NewAppError("Api4.GetGroupsByTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return responseData.Groups, responseData.Count, BuildResponse(r), nil
+}
+
+// GetGroupsAssociatedToChannelsByTeam retrieves the Mattermost Groups associated with channels in a given team
+func (c *Client4) GetGroupsAssociatedToChannelsByTeam(teamId string, opts GroupSearchOpts) (map[string][]*GroupWithSchemeAdmin, *Response, error) {
+ path := fmt.Sprintf("%s/groups_by_channels?q=%v&filter_allow_reference=%v", c.teamRoute(teamId), opts.Q, opts.FilterAllowReference)
+ if opts.PageOpts != nil {
+ path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage)
+ }
+ r, err := c.DoAPIGet(path, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ responseData := struct {
+ GroupsAssociatedToChannels map[string][]*GroupWithSchemeAdmin `json:"groups"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil {
+ return nil, BuildResponse(r), NewAppError("Api4.GetGroupsAssociatedToChannelsByTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return responseData.GroupsAssociatedToChannels, BuildResponse(r), nil
+}
+
+// GetGroups retrieves Mattermost Groups
+func (c *Client4) GetGroups(opts GroupSearchOpts) ([]*Group, *Response, error) {
+ path := fmt.Sprintf(
+ "%s?include_member_count=%v&not_associated_to_team=%v&not_associated_to_channel=%v&filter_allow_reference=%v&q=%v&filter_parent_team_permitted=%v",
+ c.groupsRoute(),
+ opts.IncludeMemberCount,
+ opts.NotAssociatedToTeam,
+ opts.NotAssociatedToChannel,
+ opts.FilterAllowReference,
+ opts.Q,
+ opts.FilterParentTeamPermitted,
+ )
+ if opts.Since > 0 {
+ path = fmt.Sprintf("%s&since=%v", path, opts.Since)
+ }
+ if opts.PageOpts != nil {
+ path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage)
+ }
+ r, err := c.DoAPIGet(path, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list []*Group
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetGroups", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetGroupsByUserId retrieves Mattermost Groups for a user
+func (c *Client4) GetGroupsByUserId(userId string) ([]*Group, *Response, error) {
+ path := fmt.Sprintf(
+ "%s/%v/groups",
+ c.usersRoute(),
+ userId,
+ )
+
+ r, err := c.DoAPIGet(path, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Group
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetGroupsByUserId", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+func (c *Client4) MigrateAuthToLdap(fromAuthService string, matchField string, force bool) (*Response, error) {
+ r, err := c.DoAPIPost(c.usersRoute()+"/migrate_auth/ldap", StringInterfaceToJSON(map[string]interface{}{
+ "from": fromAuthService,
+ "force": force,
+ "match_field": matchField,
+ }))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) MigrateAuthToSaml(fromAuthService string, usersMap map[string]string, auto bool) (*Response, error) {
+ r, err := c.DoAPIPost(c.usersRoute()+"/migrate_auth/saml", StringInterfaceToJSON(map[string]interface{}{
+ "from": fromAuthService,
+ "auto": auto,
+ "matches": usersMap,
+ }))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UploadLdapPublicCertificate will upload a public certificate for LDAP and set the config to use it.
+func (c *Client4) UploadLdapPublicCertificate(data []byte) (*Response, error) {
+ body, writer, err := fileToMultipart(data, LdapPublicCertificateName)
+ if err != nil {
+ return nil, NewAppError("UploadLdapPublicCertificate", "model.client.upload_ldap_cert.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ _, resp, err := c.DoUploadFile(c.ldapRoute()+"/certificate/public", body, writer.FormDataContentType())
+ return resp, err
+}
+
+// UploadLdapPrivateCertificate will upload a private key for LDAP and set the config to use it.
+func (c *Client4) UploadLdapPrivateCertificate(data []byte) (*Response, error) {
+ body, writer, err := fileToMultipart(data, LdapPrivateKeyName)
+ if err != nil {
+ return nil, NewAppError("UploadLdapPrivateCertificate", "model.client.upload_Ldap_cert.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ _, resp, err := c.DoUploadFile(c.ldapRoute()+"/certificate/private", body, writer.FormDataContentType())
+ return resp, err
+}
+
+// DeleteLdapPublicCertificate deletes the LDAP IDP certificate from the server and updates the config to not use it and disable LDAP.
+func (c *Client4) DeleteLdapPublicCertificate() (*Response, error) {
+ r, err := c.DoAPIDelete(c.ldapRoute() + "/certificate/public")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DeleteLDAPPrivateCertificate deletes the LDAP IDP certificate from the server and updates the config to not use it and disable LDAP.
+func (c *Client4) DeleteLdapPrivateCertificate() (*Response, error) {
+ r, err := c.DoAPIDelete(c.ldapRoute() + "/certificate/private")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Audits Section
+
+// GetAudits returns a list of audits for the whole system.
+func (c *Client4) GetAudits(page int, perPage int, etag string) (Audits, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet("/audits"+query, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var audits Audits
+ err = json.NewDecoder(r.Body).Decode(&audits)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetAudits", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return audits, BuildResponse(r), nil
+}
+
+// Brand Section
+
+// GetBrandImage retrieves the previously uploaded brand image.
+func (c *Client4) GetBrandImage() ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.brandRoute()+"/image", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ if r.StatusCode >= 300 {
+ return nil, BuildResponse(r), AppErrorFromJSON(r.Body)
+ }
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetBrandImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+
+ return data, BuildResponse(r), nil
+}
+
+// DeleteBrandImage deletes the brand image for the system.
+func (c *Client4) DeleteBrandImage() (*Response, error) {
+ r, err := c.DoAPIDelete(c.brandRoute() + "/image")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ return BuildResponse(r), nil
+}
+
+// UploadBrandImage sets the brand image for the system.
+func (c *Client4) UploadBrandImage(data []byte) (*Response, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("image", "brand.png")
+ if err != nil {
+ return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
+ return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if err = writer.Close(); err != nil {
+ return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.writer.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ rq, err := http.NewRequest("POST", c.APIURL+c.brandRoute()+"/image", bytes.NewReader(body.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+ rq.Header.Set("Content-Type", writer.FormDataContentType())
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ return BuildResponse(rp), nil
+}
+
+// Logs Section
+
+// GetLogs page of logs as a string array.
+func (c *Client4) GetLogs(page, perPage int) ([]string, *Response, error) {
+ query := fmt.Sprintf("?page=%v&logs_per_page=%v", page, perPage)
+ r, err := c.DoAPIGet("/logs"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ArrayFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIPost("/logs", MapToJSON(message))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// 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, error) {
+ buf, err := json.Marshal(app)
+ if err != nil {
+ return nil, nil, NewAppError("CreateOAuthApp", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.oAuthAppsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var oapp OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&oapp); jsonErr != nil {
+ return nil, nil, NewAppError("CreateOAuthApp", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &oapp, BuildResponse(r), nil
+}
+
+// UpdateOAuthApp updates a page of registered OAuth 2.0 client applications with Mattermost acting as an OAuth 2.0 service provider.
+func (c *Client4) UpdateOAuthApp(app *OAuthApp) (*OAuthApp, *Response, error) {
+ buf, err := json.Marshal(app)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateOAuthApp", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.oAuthAppRoute(app.Id), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var oapp OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&oapp); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateOAuthApp", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &oapp, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.oAuthAppsRoute()+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetOAuthApps", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.oAuthAppRoute(appId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var oapp OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&oapp); jsonErr != nil {
+ return nil, nil, NewAppError("GetOAuthApp", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &oapp, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.oAuthAppRoute(appId)+"/info", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var oapp OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&oapp); jsonErr != nil {
+ return nil, nil, NewAppError("GetOAuthAppInfo", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &oapp, BuildResponse(r), nil
+}
+
+// DeleteOAuthApp deletes a registered OAuth 2.0 client application.
+func (c *Client4) DeleteOAuthApp(appId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.oAuthAppRoute(appId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// RegenerateOAuthAppSecret regenerates the client secret for a registered OAuth 2.0 client application.
+func (c *Client4) RegenerateOAuthAppSecret(appId string) (*OAuthApp, *Response, error) {
+ r, err := c.DoAPIPost(c.oAuthAppRoute(appId)+"/regen_secret", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var oapp OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&oapp); jsonErr != nil {
+ return nil, nil, NewAppError("RegenerateOAuthAppSecret", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &oapp, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/oauth/apps/authorized"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*OAuthApp
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetAuthorizedOAuthAppsForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ buf, err := json.Marshal(authRequest)
+ if err != nil {
+ return "", BuildResponse(nil), NewAppError("AuthorizeOAuthApp", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIRequestBytes(http.MethodPost, c.URL+"/oauth/authorize", buf, "")
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["redirect"], BuildResponse(r), nil
+}
+
+// DeauthorizeOAuthApp will deauthorize an OAuth 2.0 client application from accessing a user's account.
+func (c *Client4) DeauthorizeOAuthApp(appId string) (*Response, error) {
+ requestData := map[string]string{"client_id": appId}
+ r, err := c.DoAPIRequest(http.MethodPost, c.URL+"/oauth/deauthorize", MapToJSON(requestData), "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetOAuthAccessToken is a test helper function for the OAuth access token endpoint.
+func (c *Client4) GetOAuthAccessToken(data url.Values) (*AccessResponse, *Response, error) {
+ url := c.URL + "/oauth/access_token"
+ rq, err := http.NewRequest(http.MethodPost, url, strings.NewReader(data.Encode()))
+ if err != nil {
+ return nil, nil, err
+ }
+ rq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return nil, BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ var ar *AccessResponse
+ err = json.NewDecoder(rp.Body).Decode(&ar)
+ if err != nil {
+ return nil, BuildResponse(rp), NewAppError(url, "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return ar, BuildResponse(rp), nil
+}
+
+// Elasticsearch Section
+
+// TestElasticsearch will attempt to connect to the configured Elasticsearch server and return OK if configured.
+// correctly.
+func (c *Client4) TestElasticsearch() (*Response, error) {
+ r, err := c.DoAPIPost(c.elasticsearchRoute()+"/test", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PurgeElasticsearchIndexes immediately deletes all Elasticsearch indexes.
+func (c *Client4) PurgeElasticsearchIndexes() (*Response, error) {
+ r, err := c.DoAPIPost(c.elasticsearchRoute()+"/purge_indexes", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Bleve Section
+
+// PurgeBleveIndexes immediately deletes all Bleve indexes.
+func (c *Client4) PurgeBleveIndexes() (*Response, error) {
+ r, err := c.DoAPIPost(c.bleveRoute()+"/purge_indexes", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Data Retention Section
+
+// GetDataRetentionPolicy will get the current global data retention policy details.
+func (c *Client4) GetDataRetentionPolicy() (*GlobalRetentionPolicy, *Response, error) {
+ r, err := c.DoAPIGet(c.dataRetentionRoute()+"/policy", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p GlobalRetentionPolicy
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("GetDataRetentionPolicy", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// GetDataRetentionPolicyByID will get the details for the granular data retention policy with the specified ID.
+func (c *Client4) GetDataRetentionPolicyByID(policyID string) (*RetentionPolicyWithTeamAndChannelCounts, *Response, error) {
+ r, err := c.DoAPIGet(c.dataRetentionPolicyRoute(policyID), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var p RetentionPolicyWithTeamAndChannelCounts
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("GetDataRetentionPolicyByID", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// GetDataRetentionPoliciesCount will get the total number of granular data retention policies.
+func (c *Client4) GetDataRetentionPoliciesCount() (int64, *Response, error) {
+ type CountBody struct {
+ TotalCount int64 `json:"total_count"`
+ }
+ r, err := c.DoAPIGet(c.dataRetentionRoute()+"/policies_count", "")
+ if err != nil {
+ return 0, BuildResponse(r), err
+ }
+ var countObj CountBody
+ err = json.NewDecoder(r.Body).Decode(&countObj)
+ if err != nil {
+ return 0, nil, NewAppError("Client4.GetDataRetentionPoliciesCount", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return countObj.TotalCount, BuildResponse(r), nil
+}
+
+// GetDataRetentionPolicies will get the current granular data retention policies' details.
+func (c *Client4) GetDataRetentionPolicies(page, perPage int) (*RetentionPolicyWithTeamAndChannelCountsList, *Response, error) {
+ query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage)
+ r, err := c.DoAPIGet(c.dataRetentionRoute()+"/policies"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var p RetentionPolicyWithTeamAndChannelCountsList
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("GetDataRetentionPolicies", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// CreateDataRetentionPolicy will create a new granular data retention policy which will be applied to
+// the specified teams and channels. The Id field of `policy` must be empty.
+func (c *Client4) CreateDataRetentionPolicy(policy *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response, error) {
+ policyJSON, jsonErr := json.Marshal(policy)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("CreateDataRetentionPolicy", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.dataRetentionRoute()+"/policies", policyJSON)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p RetentionPolicyWithTeamAndChannelCounts
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("CreateDataRetentionPolicy", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// DeleteDataRetentionPolicy will delete the granular data retention policy with the specified ID.
+func (c *Client4) DeleteDataRetentionPolicy(policyID string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.dataRetentionPolicyRoute(policyID))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PatchDataRetentionPolicy will patch the granular data retention policy with the specified ID.
+// The Id field of `patch` must be non-empty.
+func (c *Client4) PatchDataRetentionPolicy(patch *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response, error) {
+ patchJSON, jsonErr := json.Marshal(patch)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("PatchDataRetentionPolicy", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPatchBytes(c.dataRetentionPolicyRoute(patch.ID), patchJSON)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var p RetentionPolicyWithTeamAndChannelCounts
+ if jsonErr := json.NewDecoder(r.Body).Decode(&p); jsonErr != nil {
+ return nil, nil, NewAppError("PatchDataRetentionPolicy", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &p, BuildResponse(r), nil
+}
+
+// GetTeamsForRetentionPolicy will get the teams to which the specified policy is currently applied.
+func (c *Client4) GetTeamsForRetentionPolicy(policyID string, page, perPage int) (*TeamsWithCount, *Response, error) {
+ query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage)
+ r, err := c.DoAPIGet(c.dataRetentionPolicyRoute(policyID)+"/teams"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ var teams *TeamsWithCount
+ err = json.NewDecoder(r.Body).Decode(&teams)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.GetTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return teams, BuildResponse(r), nil
+}
+
+// SearchTeamsForRetentionPolicy will search the teams to which the specified policy is currently applied.
+func (c *Client4) SearchTeamsForRetentionPolicy(policyID string, term string) ([]*Team, *Response, error) {
+ body, _ := json.Marshal(map[string]interface{}{"term": term})
+ r, err := c.DoAPIPostBytes(c.dataRetentionPolicyRoute(policyID)+"/teams/search", body)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ var teams []*Team
+ err = json.NewDecoder(r.Body).Decode(&teams)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.SearchTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return teams, BuildResponse(r), nil
+}
+
+// AddTeamsToRetentionPolicy will add the specified teams to the granular data retention policy
+// with the specified ID.
+func (c *Client4) AddTeamsToRetentionPolicy(policyID string, teamIDs []string) (*Response, error) {
+ body, _ := json.Marshal(teamIDs)
+ r, err := c.DoAPIPostBytes(c.dataRetentionPolicyRoute(policyID)+"/teams", body)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// RemoveTeamsFromRetentionPolicy will remove the specified teams from the granular data retention policy
+// with the specified ID.
+func (c *Client4) RemoveTeamsFromRetentionPolicy(policyID string, teamIDs []string) (*Response, error) {
+ body, _ := json.Marshal(teamIDs)
+ r, err := c.DoAPIDeleteBytes(c.dataRetentionPolicyRoute(policyID)+"/teams", body)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetChannelsForRetentionPolicy will get the channels to which the specified policy is currently applied.
+func (c *Client4) GetChannelsForRetentionPolicy(policyID string, page, perPage int) (*ChannelsWithCount, *Response, error) {
+ query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage)
+ r, err := c.DoAPIGet(c.dataRetentionPolicyRoute(policyID)+"/channels"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ var channels *ChannelsWithCount
+ err = json.NewDecoder(r.Body).Decode(&channels)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.GetChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return channels, BuildResponse(r), nil
+}
+
+// SearchChannelsForRetentionPolicy will search the channels to which the specified policy is currently applied.
+func (c *Client4) SearchChannelsForRetentionPolicy(policyID string, term string) (ChannelListWithTeamData, *Response, error) {
+ body, _ := json.Marshal(map[string]interface{}{"term": term})
+ r, err := c.DoAPIPostBytes(c.dataRetentionPolicyRoute(policyID)+"/channels/search", body)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ var channels ChannelListWithTeamData
+ err = json.NewDecoder(r.Body).Decode(&channels)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.SearchChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return channels, BuildResponse(r), nil
+}
+
+// AddChannelsToRetentionPolicy will add the specified channels to the granular data retention policy
+// with the specified ID.
+func (c *Client4) AddChannelsToRetentionPolicy(policyID string, channelIDs []string) (*Response, error) {
+ body, _ := json.Marshal(channelIDs)
+ r, err := c.DoAPIPostBytes(c.dataRetentionPolicyRoute(policyID)+"/channels", body)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// RemoveChannelsFromRetentionPolicy will remove the specified channels from the granular data retention policy
+// with the specified ID.
+func (c *Client4) RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) (*Response, error) {
+ body, _ := json.Marshal(channelIDs)
+ r, err := c.DoAPIDeleteBytes(c.dataRetentionPolicyRoute(policyID)+"/channels", body)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetTeamPoliciesForUser will get the data retention policies for the teams to which a user belongs.
+func (c *Client4) GetTeamPoliciesForUser(userID string, offset, limit int) (*RetentionPolicyForTeamList, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userID)+"/data_retention/team_policies", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ var teams RetentionPolicyForTeamList
+ err = json.NewDecoder(r.Body).Decode(&teams)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.GetTeamPoliciesForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return &teams, BuildResponse(r), nil
+}
+
+// GetChannelPoliciesForUser will get the data retention policies for the channels to which a user belongs.
+func (c *Client4) GetChannelPoliciesForUser(userID string, offset, limit int) (*RetentionPolicyForChannelList, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userID)+"/data_retention/channel_policies", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ var channels RetentionPolicyForChannelList
+ err = json.NewDecoder(r.Body).Decode(&channels)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.GetChannelPoliciesForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return &channels, BuildResponse(r), nil
+}
+
+// Commands Section
+
+// CreateCommand will create a new command if the user have the right permissions.
+func (c *Client4) CreateCommand(cmd *Command) (*Command, *Response, error) {
+ buf, err := json.Marshal(cmd)
+ if err != nil {
+ return nil, nil, NewAppError("CreateCommand", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.commandsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var command Command
+ if jsonErr := json.NewDecoder(r.Body).Decode(&command); jsonErr != nil {
+ return nil, nil, NewAppError("CreateCommand", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &command, BuildResponse(r), nil
+}
+
+// UpdateCommand updates a command based on the provided Command struct.
+func (c *Client4) UpdateCommand(cmd *Command) (*Command, *Response, error) {
+ buf, err := json.Marshal(cmd)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateCommand", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.commandRoute(cmd.Id), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var command Command
+ if jsonErr := json.NewDecoder(r.Body).Decode(&command); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateCommand", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &command, BuildResponse(r), nil
+}
+
+// MoveCommand moves a command to a different team.
+func (c *Client4) MoveCommand(teamId string, commandId string) (*Response, error) {
+ cmr := CommandMoveRequest{TeamId: teamId}
+ buf, err := json.Marshal(cmr)
+ if err != nil {
+ return nil, NewAppError("MoveCommand", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.commandMoveRoute(commandId), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DeleteCommand deletes a command based on the provided command id string.
+func (c *Client4) DeleteCommand(commandId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.commandRoute(commandId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// ListCommands will retrieve a list of commands available in the team.
+func (c *Client4) ListCommands(teamId string, customOnly bool) ([]*Command, *Response, error) {
+ query := fmt.Sprintf("?team_id=%v&custom_only=%v", teamId, customOnly)
+ r, err := c.DoAPIGet(c.commandsRoute()+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list []*Command
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("ListCommands", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// ListCommandAutocompleteSuggestions will retrieve a list of suggestions for a userInput.
+func (c *Client4) ListCommandAutocompleteSuggestions(userInput, teamId string) ([]AutocompleteSuggestion, *Response, error) {
+ query := fmt.Sprintf("/commands/autocomplete_suggestions?user_input=%v", userInput)
+ r, err := c.DoAPIGet(c.teamRoute(teamId)+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []AutocompleteSuggestion
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("ListCommandAutocompleteSuggestions", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetCommandById will retrieve a command by id.
+func (c *Client4) GetCommandById(cmdId string) (*Command, *Response, error) {
+ url := fmt.Sprintf("%s/%s", c.commandsRoute(), cmdId)
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var command Command
+ if jsonErr := json.NewDecoder(r.Body).Decode(&command); jsonErr != nil {
+ return nil, nil, NewAppError("GetCommandById", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &command, BuildResponse(r), nil
+}
+
+// ExecuteCommand executes a given slash command.
+func (c *Client4) ExecuteCommand(channelId, command string) (*CommandResponse, *Response, error) {
+ commandArgs := &CommandArgs{
+ ChannelId: channelId,
+ Command: command,
+ }
+ buf, err := json.Marshal(commandArgs)
+ if err != nil {
+ return nil, nil, NewAppError("ExecuteCommand", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.commandsRoute()+"/execute", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ response, err := CommandResponseFromJSON(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("ExecuteCommand", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return response, BuildResponse(r), nil
+}
+
+// ExecuteCommandWithTeam 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, error) {
+ commandArgs := &CommandArgs{
+ ChannelId: channelId,
+ TeamId: teamId,
+ Command: command,
+ }
+ buf, err := json.Marshal(commandArgs)
+ if err != nil {
+ return nil, nil, NewAppError("ExecuteCommandWithTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.commandsRoute()+"/execute", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ response, err := CommandResponseFromJSON(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("ExecuteCommandWithTeam", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return response, BuildResponse(r), nil
+}
+
+// ListAutocompleteCommands will retrieve a list of commands available in the team.
+func (c *Client4) ListAutocompleteCommands(teamId string) ([]*Command, *Response, error) {
+ r, err := c.DoAPIGet(c.teamAutoCompleteCommandsRoute(teamId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Command
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("ListAutocompleteCommands", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// RegenCommandToken will create a new token if the user have the right permissions.
+func (c *Client4) RegenCommandToken(commandId string) (string, *Response, error) {
+ r, err := c.DoAPIPut(c.commandRoute(commandId)+"/regen_token", "")
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["token"], BuildResponse(r), nil
+}
+
+// Status Section
+
+// GetUserStatus returns a user based on the provided user id string.
+func (c *Client4) GetUserStatus(userId, etag string) (*Status, *Response, error) {
+ r, err := c.DoAPIGet(c.userStatusRoute(userId), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var s Status
+ if r.StatusCode == http.StatusNotModified {
+ return &s, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserStatus", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// GetUsersStatusesByIds returns a list of users status based on the provided user ids.
+func (c *Client4) GetUsersStatusesByIds(userIds []string) ([]*Status, *Response, error) {
+ r, err := c.DoAPIPost(c.userStatusesRoute()+"/ids", ArrayToJSON(userIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Status
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUsersStatusesByIds", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// UpdateUserStatus sets a user's status based on the provided user id string.
+func (c *Client4) UpdateUserStatus(userId string, userStatus *Status) (*Status, *Response, error) {
+ buf, err := json.Marshal(userStatus)
+ if err != nil {
+ return nil, nil, NewAppError("UpdateUserStatus", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.userStatusRoute(userId), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var s Status
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("UpdateUserStatus", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// 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, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ part, err := writer.CreateFormFile("image", filename)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if _, err := io.Copy(part, bytes.NewBuffer(image)); err != nil {
+ return nil, nil, err
+ }
+
+ emojiJSON, jsonErr := json.Marshal(emoji)
+ if jsonErr != nil {
+ return nil, nil, NewAppError("CreateEmoji", "api.marshal_error", nil, jsonErr.Error(), 0)
+ }
+
+ if err := writer.WriteField("emoji", string(emojiJSON)); err != nil {
+ return nil, nil, err
+ }
+
+ if err := writer.Close(); err != nil {
+ return nil, nil, err
+ }
+
+ return c.DoEmojiUploadFile(c.emojisRoute(), body.Bytes(), writer.FormDataContentType())
+}
+
+// GetEmojiList returns a page of custom emoji on the system.
+func (c *Client4) GetEmojiList(page, perPage int) ([]*Emoji, *Response, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage)
+ r, err := c.DoAPIGet(c.emojisRoute()+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list []*Emoji
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetEmojiList", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ query := fmt.Sprintf("?page=%v&per_page=%v&sort=%v", page, perPage, sort)
+ r, err := c.DoAPIGet(c.emojisRoute()+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Emoji
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetSortedEmojiList", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// DeleteEmoji delete an custom emoji on the provided emoji id string.
+func (c *Client4) DeleteEmoji(emojiId string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.emojiRoute(emojiId))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetEmoji returns a custom emoji based on the emojiId string.
+func (c *Client4) GetEmoji(emojiId string) (*Emoji, *Response, error) {
+ r, err := c.DoAPIGet(c.emojiRoute(emojiId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var e Emoji
+ if jsonErr := json.NewDecoder(r.Body).Decode(&e); jsonErr != nil {
+ return nil, nil, NewAppError("GetEmoji", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &e, BuildResponse(r), nil
+}
+
+// GetEmojiByName returns a custom emoji based on the name string.
+func (c *Client4) GetEmojiByName(name string) (*Emoji, *Response, error) {
+ r, err := c.DoAPIGet(c.emojiByNameRoute(name), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var e Emoji
+ if jsonErr := json.NewDecoder(r.Body).Decode(&e); jsonErr != nil {
+ return nil, nil, NewAppError("GetEmojiByName", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &e, BuildResponse(r), nil
+}
+
+// GetEmojiImage returns the emoji image.
+func (c *Client4) GetEmojiImage(emojiId string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.emojiRoute(emojiId)+"/image", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetEmojiImage", "model.client.read_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+
+ return data, BuildResponse(r), nil
+}
+
+// SearchEmoji returns a list of emoji matching some search criteria.
+func (c *Client4) SearchEmoji(search *EmojiSearch) ([]*Emoji, *Response, error) {
+ buf, err := json.Marshal(search)
+ if err != nil {
+ return nil, nil, NewAppError("SearchEmoji", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.emojisRoute()+"/search", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Emoji
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("SearchEmoji", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// AutocompleteEmoji returns a list of emoji starting with or matching name.
+func (c *Client4) AutocompleteEmoji(name string, etag string) ([]*Emoji, *Response, error) {
+ query := fmt.Sprintf("?name=%v", name)
+ r, err := c.DoAPIGet(c.emojisRoute()+"/autocomplete"+query, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Emoji
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("AutocompleteEmoji", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ buf, err := json.Marshal(reaction)
+ if err != nil {
+ return nil, nil, NewAppError("SaveReaction", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.reactionsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var re Reaction
+ if jsonErr := json.NewDecoder(r.Body).Decode(&re); jsonErr != nil {
+ return nil, nil, NewAppError("SaveReaction", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &re, BuildResponse(r), nil
+}
+
+// GetReactions returns a list of reactions to a post.
+func (c *Client4) GetReactions(postId string) ([]*Reaction, *Response, error) {
+ r, err := c.DoAPIGet(c.postRoute(postId)+"/reactions", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Reaction
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetReactions", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// DeleteReaction deletes reaction of a user in a post.
+func (c *Client4) DeleteReaction(reaction *Reaction) (*Response, error) {
+ r, err := c.DoAPIDelete(c.userRoute(reaction.UserId) + c.postRoute(reaction.PostId) + fmt.Sprintf("/reactions/%v", reaction.EmojiName))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// FetchBulkReactions returns a map of postIds and corresponding reactions
+func (c *Client4) GetBulkReactions(postIds []string) (map[string][]*Reaction, *Response, error) {
+ r, err := c.DoAPIPost(c.postsRoute()+"/ids/reactions", ArrayToJSON(postIds))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ reactions := map[string][]*Reaction{}
+ if jsonErr := json.NewDecoder(r.Body).Decode(&reactions); jsonErr != nil {
+ return nil, nil, NewAppError("GetBulkReactions", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return reactions, BuildResponse(r), nil
+}
+
+// Timezone Section
+
+// GetSupportedTimezone returns a page of supported timezones on the system.
+func (c *Client4) GetSupportedTimezone() ([]string, *Response, error) {
+ r, err := c.DoAPIGet(c.timezonesRoute(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var timezones []string
+ json.NewDecoder(r.Body).Decode(&timezones)
+ return timezones, BuildResponse(r), nil
+}
+
+// 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, error) {
+ requestBody := make(map[string]string)
+ requestBody["url"] = url
+
+ r, err := c.DoAPIPost(c.openGraphRoute(), MapToJSON(requestBody))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body), BuildResponse(r), nil
+}
+
+// Jobs Section
+
+// GetJob gets a single job.
+func (c *Client4) GetJob(id string) (*Job, *Response, error) {
+ r, err := c.DoAPIGet(c.jobsRoute()+fmt.Sprintf("/%v", id), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var j Job
+ if jsonErr := json.NewDecoder(r.Body).Decode(&j); jsonErr != nil {
+ return nil, nil, NewAppError("GetJob", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &j, BuildResponse(r), nil
+}
+
+// GetJobs gets all jobs, sorted with the job that was created most recently first.
+func (c *Client4) GetJobs(page int, perPage int) ([]*Job, *Response, error) {
+ r, err := c.DoAPIGet(c.jobsRoute()+fmt.Sprintf("?page=%v&per_page=%v", page, perPage), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Job
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetJobs", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// 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, error) {
+ r, err := c.DoAPIGet(c.jobsRoute()+fmt.Sprintf("/type/%v?page=%v&per_page=%v", jobType, page, perPage), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Job
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetJobsByType", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// CreateJob creates a job based on the provided job struct.
+func (c *Client4) CreateJob(job *Job) (*Job, *Response, error) {
+ buf, err := json.Marshal(job)
+ if err != nil {
+ return nil, nil, NewAppError("CreateJob", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.jobsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var j Job
+ if jsonErr := json.NewDecoder(r.Body).Decode(&j); jsonErr != nil {
+ return nil, nil, NewAppError("CreateJob", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &j, BuildResponse(r), nil
+}
+
+// CancelJob requests the cancellation of the job with the provided Id.
+func (c *Client4) CancelJob(jobId string) (*Response, error) {
+ r, err := c.DoAPIPost(c.jobsRoute()+fmt.Sprintf("/%v/cancel", jobId), "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DownloadJob downloads the results of the job
+func (c *Client4) DownloadJob(jobId string) ([]byte, *Response, error) {
+ r, err := c.DoAPIGet(c.jobsRoute()+fmt.Sprintf("/%v/download", jobId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ data, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetFile", "model.client.read_job_result_file.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return data, BuildResponse(r), nil
+}
+
+// Roles Section
+
+// GetRole gets a single role by ID.
+func (c *Client4) GetRole(id string) (*Role, *Response, error) {
+ r, err := c.DoAPIGet(c.rolesRoute()+fmt.Sprintf("/%v", id), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var role Role
+ if jsonErr := json.NewDecoder(r.Body).Decode(&role); jsonErr != nil {
+ return nil, nil, NewAppError("GetRole", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &role, BuildResponse(r), nil
+}
+
+// GetRoleByName gets a single role by Name.
+func (c *Client4) GetRoleByName(name string) (*Role, *Response, error) {
+ r, err := c.DoAPIGet(c.rolesRoute()+fmt.Sprintf("/name/%v", name), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var role Role
+ if jsonErr := json.NewDecoder(r.Body).Decode(&role); jsonErr != nil {
+ return nil, nil, NewAppError("GetRoleByName", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &role, BuildResponse(r), nil
+}
+
+// GetRolesByNames returns a list of roles based on the provided role names.
+func (c *Client4) GetRolesByNames(roleNames []string) ([]*Role, *Response, error) {
+ r, err := c.DoAPIPost(c.rolesRoute()+"/names", ArrayToJSON(roleNames))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Role
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetRolesByNames", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// PatchRole partially updates a role in the system. Any missing fields are not updated.
+func (c *Client4) PatchRole(roleId string, patch *RolePatch) (*Role, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchRole", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.rolesRoute()+fmt.Sprintf("/%v/patch", roleId), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var role Role
+ if jsonErr := json.NewDecoder(r.Body).Decode(&role); jsonErr != nil {
+ return nil, nil, NewAppError("PatchRole", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &role, BuildResponse(r), nil
+}
+
+// Schemes Section
+
+// CreateScheme creates a new Scheme.
+func (c *Client4) CreateScheme(scheme *Scheme) (*Scheme, *Response, error) {
+ buf, err := json.Marshal(scheme)
+ if err != nil {
+ return nil, nil, NewAppError("CreateScheme", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.schemesRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var s Scheme
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("CreateScheme", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// GetScheme gets a single scheme by ID.
+func (c *Client4) GetScheme(id string) (*Scheme, *Response, error) {
+ r, err := c.DoAPIGet(c.schemeRoute(id), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var s Scheme
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("GetScheme", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// GetSchemes ets 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, error) {
+ r, err := c.DoAPIGet(c.schemesRoute()+fmt.Sprintf("?scope=%v&page=%v&per_page=%v", scope, page, perPage), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Scheme
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetSchemes", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// DeleteScheme deletes a single scheme by ID.
+func (c *Client4) DeleteScheme(id string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.schemeRoute(id))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// PatchScheme partially updates a scheme in the system. Any missing fields are not updated.
+func (c *Client4) PatchScheme(id string, patch *SchemePatch) (*Scheme, *Response, error) {
+ buf, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchScheme", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.schemeRoute(id)+"/patch", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var s Scheme
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("PatchScheme", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// GetTeamsForScheme gets the teams using this scheme, sorted alphabetically by display name.
+func (c *Client4) GetTeamsForScheme(schemeId string, page int, perPage int) ([]*Team, *Response, error) {
+ r, err := c.DoAPIGet(c.schemeRoute(schemeId)+fmt.Sprintf("/teams?page=%v&per_page=%v", page, perPage), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*Team
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetTeamsForScheme", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// GetChannelsForScheme gets the channels using this scheme, sorted alphabetically by display name.
+func (c *Client4) GetChannelsForScheme(schemeId string, page int, perPage int) (ChannelList, *Response, error) {
+ r, err := c.DoAPIGet(c.schemeRoute(schemeId)+fmt.Sprintf("/channels?page=%v&per_page=%v", page, perPage), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch ChannelList
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelsForScheme", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// Plugin Section
+
+// UploadPlugin takes an io.Reader stream pointing to the contents of a .tar.gz plugin.
+func (c *Client4) UploadPlugin(file io.Reader) (*Manifest, *Response, error) {
+ return c.uploadPlugin(file, false)
+}
+
+func (c *Client4) UploadPluginForced(file io.Reader) (*Manifest, *Response, error) {
+ return c.uploadPlugin(file, true)
+}
+
+func (c *Client4) uploadPlugin(file io.Reader, force bool) (*Manifest, *Response, error) {
+ body := new(bytes.Buffer)
+ writer := multipart.NewWriter(body)
+
+ if force {
+ err := writer.WriteField("force", c.boolString(true))
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ part, err := writer.CreateFormFile("plugin", "plugin.tar.gz")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if _, err = io.Copy(part, file); err != nil {
+ return nil, nil, err
+ }
+
+ if err = writer.Close(); err != nil {
+ return nil, nil, err
+ }
+
+ rq, err := http.NewRequest("POST", c.APIURL+c.pluginsRoute(), body)
+ if err != nil {
+ return nil, nil, err
+ }
+ rq.Header.Set("Content-Type", writer.FormDataContentType())
+
+ if c.AuthToken != "" {
+ rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken)
+ }
+
+ rp, err := c.HTTPClient.Do(rq)
+ if err != nil {
+ return nil, BuildResponse(rp), err
+ }
+ defer closeBody(rp)
+
+ if rp.StatusCode >= 300 {
+ return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body)
+ }
+
+ var m Manifest
+ if jsonErr := json.NewDecoder(rp.Body).Decode(&m); jsonErr != nil {
+ return nil, nil, NewAppError("uploadPlugin", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &m, BuildResponse(rp), nil
+}
+
+func (c *Client4) InstallPluginFromURL(downloadURL string, force bool) (*Manifest, *Response, error) {
+ forceStr := c.boolString(force)
+
+ url := fmt.Sprintf("%s?plugin_download_url=%s&force=%s", c.pluginsRoute()+"/install_from_url", url.QueryEscape(downloadURL), forceStr)
+ r, err := c.DoAPIPost(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var m Manifest
+ if jsonErr := json.NewDecoder(r.Body).Decode(&m); jsonErr != nil {
+ return nil, nil, NewAppError("InstallPluginFromUrl", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &m, BuildResponse(r), nil
+}
+
+// InstallMarketplacePlugin will install marketplace plugin.
+func (c *Client4) InstallMarketplacePlugin(request *InstallMarketplacePluginRequest) (*Manifest, *Response, error) {
+ buf, err := json.Marshal(request)
+ if err != nil {
+ return nil, nil, NewAppError("InstallMarketplacePlugin", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPost(c.pluginsRoute()+"/marketplace", string(buf))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var m Manifest
+ if jsonErr := json.NewDecoder(r.Body).Decode(&m); jsonErr != nil {
+ return nil, nil, NewAppError("InstallMarketplacePlugin", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &m, BuildResponse(r), nil
+}
+
+// GetPlugins will return a list of plugin manifests for currently active plugins.
+func (c *Client4) GetPlugins() (*PluginsResponse, *Response, error) {
+ r, err := c.DoAPIGet(c.pluginsRoute(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var resp PluginsResponse
+ if jsonErr := json.NewDecoder(r.Body).Decode(&resp); jsonErr != nil {
+ return nil, nil, NewAppError("GetPlugins", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &resp, BuildResponse(r), nil
+}
+
+// GetPluginStatuses will return the plugins installed on any server in the cluster, for reporting
+// to the administrator via the system console.
+func (c *Client4) GetPluginStatuses() (PluginStatuses, *Response, error) {
+ r, err := c.DoAPIGet(c.pluginsRoute()+"/statuses", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list PluginStatuses
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetPluginStatuses", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// RemovePlugin will disable and delete a plugin.
+func (c *Client4) RemovePlugin(id string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.pluginRoute(id))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetWebappPlugins will return a list of plugins that the webapp should download.
+func (c *Client4) GetWebappPlugins() ([]*Manifest, *Response, error) {
+ r, err := c.DoAPIGet(c.pluginsRoute()+"/webapp", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var list []*Manifest
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetWebappPlugins", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// EnablePlugin will enable an plugin installed.
+func (c *Client4) EnablePlugin(id string) (*Response, error) {
+ r, err := c.DoAPIPost(c.pluginRoute(id)+"/enable", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// DisablePlugin will disable an enabled plugin.
+func (c *Client4) DisablePlugin(id string) (*Response, error) {
+ r, err := c.DoAPIPost(c.pluginRoute(id)+"/disable", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetMarketplacePlugins will return a list of plugins that an admin can install.
+func (c *Client4) GetMarketplacePlugins(filter *MarketplacePluginFilter) ([]*MarketplacePlugin, *Response, error) {
+ route := c.pluginsRoute() + "/marketplace"
+ u, err := url.Parse(route)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ filter.ApplyToURL(u)
+
+ r, err := c.DoAPIGet(u.String(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ plugins, err := MarketplacePluginsFromReader(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError(route, "model.client.parse_plugins.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ return plugins, BuildResponse(r), nil
+}
+
+// UpdateChannelScheme will update a channel's scheme.
+func (c *Client4) UpdateChannelScheme(channelId, schemeId string) (*Response, error) {
+ sip := &SchemeIDPatch{SchemeID: &schemeId}
+ buf, err := json.Marshal(sip)
+ if err != nil {
+ return nil, NewAppError("UpdateChannelScheme", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.channelSchemeRoute(channelId), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// UpdateTeamScheme will update a team's scheme.
+func (c *Client4) UpdateTeamScheme(teamId, schemeId string) (*Response, error) {
+ sip := &SchemeIDPatch{SchemeID: &schemeId}
+ buf, err := json.Marshal(sip)
+ if err != nil {
+ return nil, NewAppError("UpdateTeamScheme", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.teamSchemeRoute(teamId), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// 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, error) {
+ url := fmt.Sprintf("%s?url=%s", c.redirectLocationRoute(), url.QueryEscape(urlParam))
+ r, err := c.DoAPIGet(url, etag)
+ if err != nil {
+ return "", BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return MapFromJSON(r.Body)["location"], BuildResponse(r), nil
+}
+
+// SetServerBusy will mark the server as busy, which disables non-critical services for `secs` seconds.
+func (c *Client4) SetServerBusy(secs int) (*Response, error) {
+ url := fmt.Sprintf("%s?seconds=%d", c.serverBusyRoute(), secs)
+ r, err := c.DoAPIPost(url, "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// ClearServerBusy will mark the server as not busy.
+func (c *Client4) ClearServerBusy() (*Response, error) {
+ r, err := c.DoAPIDelete(c.serverBusyRoute())
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetServerBusy returns the current ServerBusyState including the time when a server marked busy
+// will automatically have the flag cleared.
+func (c *Client4) GetServerBusy() (*ServerBusyState, *Response, error) {
+ r, err := c.DoAPIGet(c.serverBusyRoute(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var sbs ServerBusyState
+ if jsonErr := json.NewDecoder(r.Body).Decode(&sbs); jsonErr != nil {
+ return nil, nil, NewAppError("GetServerBusy", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &sbs, BuildResponse(r), nil
+}
+
+// RegisterTermsOfServiceAction saves action performed by a user against a specific terms of service.
+func (c *Client4) RegisterTermsOfServiceAction(userId, termsOfServiceId string, accepted bool) (*Response, error) {
+ url := c.userTermsOfServiceRoute(userId)
+ data := map[string]interface{}{"termsOfServiceId": termsOfServiceId, "accepted": accepted}
+ r, err := c.DoAPIPost(url, StringInterfaceToJSON(data))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetTermsOfService fetches the latest terms of service
+func (c *Client4) GetTermsOfService(etag string) (*TermsOfService, *Response, error) {
+ url := c.termsOfServiceRoute()
+ r, err := c.DoAPIGet(url, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tos TermsOfService
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tos); jsonErr != nil {
+ return nil, nil, NewAppError("GetTermsOfService", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &tos, BuildResponse(r), nil
+}
+
+// GetUserTermsOfService fetches user's latest terms of service action if the latest action was for acceptance.
+func (c *Client4) GetUserTermsOfService(userId, etag string) (*UserTermsOfService, *Response, error) {
+ url := c.userTermsOfServiceRoute(userId)
+ r, err := c.DoAPIGet(url, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var u UserTermsOfService
+ if jsonErr := json.NewDecoder(r.Body).Decode(&u); jsonErr != nil {
+ return nil, nil, NewAppError("GetUserTermsOfService", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &u, BuildResponse(r), nil
+}
+
+// CreateTermsOfService creates new terms of service.
+func (c *Client4) CreateTermsOfService(text, userId string) (*TermsOfService, *Response, error) {
+ url := c.termsOfServiceRoute()
+ data := map[string]interface{}{"text": text}
+ r, err := c.DoAPIPost(url, StringInterfaceToJSON(data))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var tos TermsOfService
+ if jsonErr := json.NewDecoder(r.Body).Decode(&tos); jsonErr != nil {
+ return nil, nil, NewAppError("CreateTermsOfService", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &tos, BuildResponse(r), nil
+}
+
+func (c *Client4) GetGroup(groupID, etag string) (*Group, *Response, error) {
+ r, err := c.DoAPIGet(c.groupRoute(groupID), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var g Group
+ if jsonErr := json.NewDecoder(r.Body).Decode(&g); jsonErr != nil {
+ return nil, nil, NewAppError("GetGroup", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &g, BuildResponse(r), nil
+}
+
+func (c *Client4) PatchGroup(groupID string, patch *GroupPatch) (*Group, *Response, error) {
+ payload, _ := json.Marshal(patch)
+ r, err := c.DoAPIPut(c.groupRoute(groupID)+"/patch", string(payload))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var g Group
+ if jsonErr := json.NewDecoder(r.Body).Decode(&g); jsonErr != nil {
+ return nil, nil, NewAppError("PatchGroup", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &g, BuildResponse(r), nil
+}
+
+func (c *Client4) LinkGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType, patch *GroupSyncablePatch) (*GroupSyncable, *Response, error) {
+ payload, _ := json.Marshal(patch)
+ url := fmt.Sprintf("%s/link", c.groupSyncableRoute(groupID, syncableID, syncableType))
+ r, err := c.DoAPIPost(url, string(payload))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var gs GroupSyncable
+ if jsonErr := json.NewDecoder(r.Body).Decode(&gs); jsonErr != nil {
+ return nil, nil, NewAppError("LinkGroupSyncable", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &gs, BuildResponse(r), nil
+}
+
+func (c *Client4) UnlinkGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType) (*Response, error) {
+ url := fmt.Sprintf("%s/link", c.groupSyncableRoute(groupID, syncableID, syncableType))
+ r, err := c.DoAPIDelete(url)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) GetGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType, etag string) (*GroupSyncable, *Response, error) {
+ r, err := c.DoAPIGet(c.groupSyncableRoute(groupID, syncableID, syncableType), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var gs GroupSyncable
+ if jsonErr := json.NewDecoder(r.Body).Decode(&gs); jsonErr != nil {
+ return nil, nil, NewAppError("GetGroupSyncable", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &gs, BuildResponse(r), nil
+}
+
+func (c *Client4) GetGroupSyncables(groupID string, syncableType GroupSyncableType, etag string) ([]*GroupSyncable, *Response, error) {
+ r, err := c.DoAPIGet(c.groupSyncablesRoute(groupID, syncableType), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*GroupSyncable
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetGroupSyncables", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+func (c *Client4) PatchGroupSyncable(groupID, syncableID string, syncableType GroupSyncableType, patch *GroupSyncablePatch) (*GroupSyncable, *Response, error) {
+ payload, _ := json.Marshal(patch)
+ r, err := c.DoAPIPut(c.groupSyncableRoute(groupID, syncableID, syncableType)+"/patch", string(payload))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var gs GroupSyncable
+ if jsonErr := json.NewDecoder(r.Body).Decode(&gs); jsonErr != nil {
+ return nil, nil, NewAppError("PatchGroupSyncable", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &gs, BuildResponse(r), nil
+}
+
+func (c *Client4) TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page, perPage int, etag string) ([]*UserWithGroups, int64, *Response, error) {
+ groupIDStr := strings.Join(groupIDs, ",")
+ query := fmt.Sprintf("?group_ids=%s&page=%d&per_page=%d", groupIDStr, page, perPage)
+ r, err := c.DoAPIGet(c.teamRoute(teamID)+"/members_minus_group_members"+query, etag)
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ugc UsersWithGroupsAndCount
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ugc); jsonErr != nil {
+ return nil, 0, nil, NewAppError("TeamMembersMinusGroupMembers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return ugc.Users, ugc.Count, BuildResponse(r), nil
+}
+
+func (c *Client4) ChannelMembersMinusGroupMembers(channelID string, groupIDs []string, page, perPage int, etag string) ([]*UserWithGroups, int64, *Response, error) {
+ groupIDStr := strings.Join(groupIDs, ",")
+ query := fmt.Sprintf("?group_ids=%s&page=%d&per_page=%d", groupIDStr, page, perPage)
+ r, err := c.DoAPIGet(c.channelRoute(channelID)+"/members_minus_group_members"+query, etag)
+ if err != nil {
+ return nil, 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var ugc UsersWithGroupsAndCount
+ if jsonErr := json.NewDecoder(r.Body).Decode(&ugc); jsonErr != nil {
+ return nil, 0, nil, NewAppError("ChannelMembersMinusGroupMembers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return ugc.Users, ugc.Count, BuildResponse(r), nil
+}
+
+func (c *Client4) PatchConfig(config *Config) (*Config, *Response, error) {
+ buf, err := json.Marshal(config)
+ if err != nil {
+ return nil, nil, NewAppError("PatchConfig", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPutBytes(c.configRoute()+"/patch", buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ConfigFromJSON(r.Body), BuildResponse(r), nil
+}
+
+func (c *Client4) GetChannelModerations(channelID string, etag string) ([]*ChannelModeration, *Response, error) {
+ r, err := c.DoAPIGet(c.channelRoute(channelID)+"/moderations", etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*ChannelModeration
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelModerations", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+func (c *Client4) PatchChannelModerations(channelID string, patch []*ChannelModerationPatch) ([]*ChannelModeration, *Response, error) {
+ payload, err := json.Marshal(patch)
+ if err != nil {
+ return nil, nil, NewAppError("PatchChannelModerations", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ r, err := c.DoAPIPut(c.channelRoute(channelID)+"/moderations/patch", string(payload))
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*ChannelModeration
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("PatchChannelModerations", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+func (c *Client4) GetKnownUsers() ([]string, *Response, error) {
+ r, err := c.DoAPIGet(c.usersRoute()+"/known", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var userIds []string
+ json.NewDecoder(r.Body).Decode(&userIds)
+ return userIds, BuildResponse(r), nil
+}
+
+// PublishUserTyping publishes a user is typing websocket event based on the provided TypingRequest.
+func (c *Client4) PublishUserTyping(userID string, typingRequest TypingRequest) (*Response, error) {
+ buf, err := json.Marshal(typingRequest)
+ if err != nil {
+ return nil, NewAppError("PublishUserTyping", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.publishUserTypingRoute(userID), buf)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezones bool, etag string) ([]*ChannelMemberCountByGroup, *Response, error) {
+ r, err := c.DoAPIGet(c.channelRoute(channelID)+"/member_counts_by_group?include_timezones="+strconv.FormatBool(includeTimezones), etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var ch []*ChannelMemberCountByGroup
+ err = json.NewDecoder(r.Body).Decode(&ch)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("GetChannelMemberCountsByGroup", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return ch, BuildResponse(r), nil
+}
+
+// RequestTrialLicense will request a trial license and install it in the server
+func (c *Client4) RequestTrialLicense(users int) (*Response, error) {
+ b, _ := json.Marshal(map[string]interface{}{"users": users, "terms_accepted": true})
+ r, err := c.DoAPIPost("/trial-license", string(b))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// GetGroupStats retrieves stats for a Mattermost Group
+func (c *Client4) GetGroupStats(groupID string) (*GroupStats, *Response, error) {
+ r, err := c.DoAPIGet(c.groupRoute(groupID)+"/stats", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var gs GroupStats
+ if jsonErr := json.NewDecoder(r.Body).Decode(&gs); jsonErr != nil {
+ return nil, nil, NewAppError("GetGroupStats", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &gs, BuildResponse(r), nil
+}
+
+func (c *Client4) GetSidebarCategoriesForTeamForUser(userID, teamID, etag string) (*OrderedSidebarCategories, *Response, error) {
+ route := c.userCategoryRoute(userID, teamID)
+ r, err := c.DoAPIGet(route, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+
+ var cat *OrderedSidebarCategories
+ err = json.NewDecoder(r.Body).Decode(&cat)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.GetSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return cat, BuildResponse(r), nil
+}
+
+func (c *Client4) CreateSidebarCategoryForTeamForUser(userID, teamID string, category *SidebarCategoryWithChannels) (*SidebarCategoryWithChannels, *Response, error) {
+ payload, _ := json.Marshal(category)
+ route := c.userCategoryRoute(userID, teamID)
+ r, err := c.DoAPIPostBytes(route, payload)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var cat *SidebarCategoryWithChannels
+ err = json.NewDecoder(r.Body).Decode(&cat)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.CreateSidebarCategoryForTeamForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return cat, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateSidebarCategoriesForTeamForUser(userID, teamID string, categories []*SidebarCategoryWithChannels) ([]*SidebarCategoryWithChannels, *Response, error) {
+ payload, _ := json.Marshal(categories)
+ route := c.userCategoryRoute(userID, teamID)
+
+ r, err := c.DoAPIPutBytes(route, payload)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var cat []*SidebarCategoryWithChannels
+ err = json.NewDecoder(r.Body).Decode(&cat)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.UpdateSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+
+ return cat, BuildResponse(r), nil
+}
+
+func (c *Client4) GetSidebarCategoryOrderForTeamForUser(userID, teamID, etag string) ([]string, *Response, error) {
+ route := c.userCategoryRoute(userID, teamID) + "/order"
+ r, err := c.DoAPIGet(route, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ArrayFromJSON(r.Body), BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateSidebarCategoryOrderForTeamForUser(userID, teamID string, order []string) ([]string, *Response, error) {
+ payload, _ := json.Marshal(order)
+ route := c.userCategoryRoute(userID, teamID) + "/order"
+ r, err := c.DoAPIPutBytes(route, payload)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ArrayFromJSON(r.Body), BuildResponse(r), nil
+}
+
+func (c *Client4) GetSidebarCategoryForTeamForUser(userID, teamID, categoryID, etag string) (*SidebarCategoryWithChannels, *Response, error) {
+ route := c.userCategoryRoute(userID, teamID) + "/" + categoryID
+ r, err := c.DoAPIGet(route, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var cat *SidebarCategoryWithChannels
+ err = json.NewDecoder(r.Body).Decode(&cat)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.UpdateSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+
+ return cat, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateSidebarCategoryForTeamForUser(userID, teamID, categoryID string, category *SidebarCategoryWithChannels) (*SidebarCategoryWithChannels, *Response, error) {
+ payload, _ := json.Marshal(category)
+ route := c.userCategoryRoute(userID, teamID) + "/" + categoryID
+ r, err := c.DoAPIPutBytes(route, payload)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var cat *SidebarCategoryWithChannels
+ err = json.NewDecoder(r.Body).Decode(&cat)
+ if err != nil {
+ return nil, BuildResponse(r), NewAppError("Client4.UpdateSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, err.Error(), r.StatusCode)
+ }
+
+ return cat, BuildResponse(r), nil
+}
+
+// CheckIntegrity performs a database integrity check.
+func (c *Client4) CheckIntegrity() ([]IntegrityCheckResult, *Response, error) {
+ r, err := c.DoAPIPost("/integrity", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var results []IntegrityCheckResult
+ if err := json.NewDecoder(r.Body).Decode(&results); err != nil {
+ return nil, BuildResponse(r), NewAppError("Api4.CheckIntegrity", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ return results, BuildResponse(r), nil
+}
+
+func (c *Client4) GetNotices(lastViewed int64, teamId string, client NoticeClientType, clientVersion, locale, etag string) (NoticeMessages, *Response, error) {
+ url := fmt.Sprintf("/system/notices/%s?lastViewed=%d&client=%s&clientVersion=%s&locale=%s", teamId, lastViewed, client, clientVersion, locale)
+ r, err := c.DoAPIGet(url, etag)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ notices, err := UnmarshalProductNoticeMessages(r.Body)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ return notices, BuildResponse(r), nil
+}
+
+func (c *Client4) MarkNoticesViewed(ids []string) (*Response, error) {
+ r, err := c.DoAPIPut("/system/notices/view", ArrayToJSON(ids))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// CreateUpload creates a new upload session.
+func (c *Client4) CreateUpload(us *UploadSession) (*UploadSession, *Response, error) {
+ buf, err := json.Marshal(us)
+ if err != nil {
+ return nil, nil, NewAppError("CreateUpload", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ r, err := c.DoAPIPostBytes(c.uploadsRoute(), buf)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var s UploadSession
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("CreateUpload", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// GetUpload returns the upload session for the specified uploadId.
+func (c *Client4) GetUpload(uploadId string) (*UploadSession, *Response, error) {
+ r, err := c.DoAPIGet(c.uploadRoute(uploadId), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var s UploadSession
+ if jsonErr := json.NewDecoder(r.Body).Decode(&s); jsonErr != nil {
+ return nil, nil, NewAppError("GetUpload", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &s, BuildResponse(r), nil
+}
+
+// GetUploadsForUser returns the upload sessions created by the specified
+// userId.
+func (c *Client4) GetUploadsForUser(userId string) ([]*UploadSession, *Response, error) {
+ r, err := c.DoAPIGet(c.userRoute(userId)+"/uploads", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var list []*UploadSession
+ if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
+ return nil, nil, NewAppError("GetUploadsForUser", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return list, BuildResponse(r), nil
+}
+
+// UploadData performs an upload. On success it returns
+// a FileInfo object.
+func (c *Client4) UploadData(uploadId string, data io.Reader) (*FileInfo, *Response, error) {
+ url := c.uploadRoute(uploadId)
+ r, err := c.DoAPIRequestReader("POST", c.APIURL+url, data, nil)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var fi FileInfo
+ if r.StatusCode == http.StatusNoContent {
+ return nil, BuildResponse(r), nil
+ }
+ if jsonErr := json.NewDecoder(r.Body).Decode(&fi); jsonErr != nil {
+ return nil, nil, NewAppError("UploadData", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
+ }
+ return &fi, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdatePassword(userId, currentPassword, newPassword string) (*Response, error) {
+ requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword}
+ r, err := c.DoAPIPut(c.userRoute(userId)+"/password", MapToJSON(requestBody))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+// Cloud Section
+
+func (c *Client4) GetCloudProducts() ([]*Product, *Response, error) {
+ r, err := c.DoAPIGet(c.cloudRoute()+"/products", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var cloudProducts []*Product
+ json.NewDecoder(r.Body).Decode(&cloudProducts)
+
+ return cloudProducts, BuildResponse(r), nil
+}
+
+func (c *Client4) CreateCustomerPayment() (*StripeSetupIntent, *Response, error) {
+ r, err := c.DoAPIPost(c.cloudRoute()+"/payment", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var setupIntent *StripeSetupIntent
+ json.NewDecoder(r.Body).Decode(&setupIntent)
+
+ return setupIntent, BuildResponse(r), nil
+}
+
+func (c *Client4) ConfirmCustomerPayment(confirmRequest *ConfirmPaymentMethodRequest) (*Response, error) {
+ json, _ := json.Marshal(confirmRequest)
+
+ r, err := c.DoAPIPostBytes(c.cloudRoute()+"/payment/confirm", json)
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) GetCloudCustomer() (*CloudCustomer, *Response, error) {
+ r, err := c.DoAPIGet(c.cloudRoute()+"/customer", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var cloudCustomer *CloudCustomer
+ json.NewDecoder(r.Body).Decode(&cloudCustomer)
+
+ return cloudCustomer, BuildResponse(r), nil
+}
+
+func (c *Client4) GetSubscription() (*Subscription, *Response, error) {
+ r, err := c.DoAPIGet(c.cloudRoute()+"/subscription", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var subscription *Subscription
+ json.NewDecoder(r.Body).Decode(&subscription)
+
+ return subscription, BuildResponse(r), nil
+}
+
+func (c *Client4) GetSubscriptionStats() (*SubscriptionStats, *Response, error) {
+ r, err := c.DoAPIGet(c.cloudRoute()+"/subscription/stats", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var stats *SubscriptionStats
+ json.NewDecoder(r.Body).Decode(&stats)
+ return stats, BuildResponse(r), nil
+}
+
+func (c *Client4) GetInvoicesForSubscription() ([]*Invoice, *Response, error) {
+ r, err := c.DoAPIGet(c.cloudRoute()+"/subscription/invoices", "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var invoices []*Invoice
+ json.NewDecoder(r.Body).Decode(&invoices)
+
+ return invoices, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateCloudCustomer(customerInfo *CloudCustomerInfo) (*CloudCustomer, *Response, error) {
+ customerBytes, _ := json.Marshal(customerInfo)
+
+ r, err := c.DoAPIPutBytes(c.cloudRoute()+"/customer", customerBytes)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var customer *CloudCustomer
+ json.NewDecoder(r.Body).Decode(&customer)
+
+ return customer, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateCloudCustomerAddress(address *Address) (*CloudCustomer, *Response, error) {
+ addressBytes, _ := json.Marshal(address)
+
+ r, err := c.DoAPIPutBytes(c.cloudRoute()+"/customer/address", addressBytes)
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var customer *CloudCustomer
+ json.NewDecoder(r.Body).Decode(&customer)
+
+ return customer, BuildResponse(r), nil
+}
+
+func (c *Client4) ListImports() ([]string, *Response, error) {
+ r, err := c.DoAPIGet(c.importsRoute(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ArrayFromJSON(r.Body), BuildResponse(r), nil
+}
+
+func (c *Client4) ListExports() ([]string, *Response, error) {
+ r, err := c.DoAPIGet(c.exportsRoute(), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return ArrayFromJSON(r.Body), BuildResponse(r), nil
+}
+
+func (c *Client4) DeleteExport(name string) (*Response, error) {
+ r, err := c.DoAPIDelete(c.exportRoute(name))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) DownloadExport(name string, wr io.Writer, offset int64) (int64, *Response, error) {
+ var headers map[string]string
+ if offset > 0 {
+ headers = map[string]string{
+ HeaderRange: fmt.Sprintf("bytes=%d-", offset),
+ }
+ }
+ r, err := c.DoAPIRequestWithHeaders(http.MethodGet, c.APIURL+c.exportRoute(name), "", headers)
+ if err != nil {
+ return 0, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ n, err := io.Copy(wr, r.Body)
+ if err != nil {
+ return n, BuildResponse(r), NewAppError("DownloadExport", "model.client.copy.app_error", nil, err.Error(), r.StatusCode)
+ }
+ return n, BuildResponse(r), nil
+}
+
+func (c *Client4) GetUserThreads(userId, teamId string, options GetUserThreadsOpts) (*Threads, *Response, error) {
+ v := url.Values{}
+ if options.Since != 0 {
+ v.Set("since", fmt.Sprintf("%d", options.Since))
+ }
+ if options.Before != "" {
+ v.Set("before", options.Before)
+ }
+ if options.After != "" {
+ v.Set("after", options.After)
+ }
+ if options.PageSize != 0 {
+ v.Set("per_page", fmt.Sprintf("%d", options.PageSize))
+ }
+ if options.Extended {
+ v.Set("extended", "true")
+ }
+ if options.Deleted {
+ v.Set("deleted", "true")
+ }
+ if options.Unread {
+ v.Set("unread", "true")
+ }
+ url := c.userThreadsRoute(userId, teamId)
+ if len(v) > 0 {
+ url += "?" + v.Encode()
+ }
+
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var threads Threads
+ json.NewDecoder(r.Body).Decode(&threads)
+
+ return &threads, BuildResponse(r), nil
+}
+
+func (c *Client4) GetUserThread(userId, teamId, threadId string, extended bool) (*ThreadResponse, *Response, error) {
+ url := c.userThreadRoute(userId, teamId, threadId)
+ if extended {
+ url += "?extended=true"
+ }
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var thread ThreadResponse
+ json.NewDecoder(r.Body).Decode(&thread)
+
+ return &thread, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateThreadsReadForUser(userId, teamId string) (*Response, error) {
+ r, err := c.DoAPIPut(fmt.Sprintf("%s/read", c.userThreadsRoute(userId, teamId)), "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateThreadReadForUser(userId, teamId, threadId string, timestamp int64) (*ThreadResponse, *Response, error) {
+ r, err := c.DoAPIPut(fmt.Sprintf("%s/read/%d", c.userThreadRoute(userId, teamId, threadId), timestamp), "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+ var thread ThreadResponse
+ json.NewDecoder(r.Body).Decode(&thread)
+
+ return &thread, BuildResponse(r), nil
+}
+
+func (c *Client4) UpdateThreadFollowForUser(userId, teamId, threadId string, state bool) (*Response, error) {
+ var err error
+ var r *http.Response
+ if state {
+ r, err = c.DoAPIPut(c.userThreadRoute(userId, teamId, threadId)+"/following", "")
+ } else {
+ r, err = c.DoAPIDelete(c.userThreadRoute(userId, teamId, threadId) + "/following")
+ }
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) SendAdminUpgradeRequestEmail() (*Response, error) {
+ r, err := c.DoAPIPost(c.cloudRoute()+"/subscription/limitreached/invite", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) SendAdminUpgradeRequestEmailOnJoin() (*Response, error) {
+ r, err := c.DoAPIPost(c.cloudRoute()+"/subscription/limitreached/join", "")
+ if err != nil {
+ return BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ return BuildResponse(r), nil
+}
+
+func (c *Client4) GetAllSharedChannels(teamID string, page, perPage int) ([]*SharedChannel, *Response, error) {
+ url := fmt.Sprintf("%s/%s?page=%d&per_page=%d", c.sharedChannelsRoute(), teamID, page, perPage)
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return nil, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var channels []*SharedChannel
+ json.NewDecoder(r.Body).Decode(&channels)
+
+ return channels, BuildResponse(r), nil
+}
+
+func (c *Client4) GetRemoteClusterInfo(remoteID string) (RemoteClusterInfo, *Response, error) {
+ url := fmt.Sprintf("%s/remote_info/%s", c.sharedChannelsRoute(), remoteID)
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return RemoteClusterInfo{}, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ var rci RemoteClusterInfo
+ json.NewDecoder(r.Body).Decode(&rci)
+
+ return rci, BuildResponse(r), nil
+}
+
+func (c *Client4) GetAncillaryPermissions(subsectionPermissions []string) ([]string, *Response, error) {
+ var returnedPermissions []string
+ url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.permissionsRoute(), strings.Join(subsectionPermissions, ","))
+ r, err := c.DoAPIGet(url, "")
+ if err != nil {
+ return returnedPermissions, BuildResponse(r), err
+ }
+ defer closeBody(r)
+
+ json.NewDecoder(r.Body).Decode(&returnedPermissions)
+ return returnedPermissions, BuildResponse(r), nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/cloud.go b/vendor/github.com/mattermost/mattermost-server/v6/model/cloud.go
new file mode 100644
index 00000000..ffd85a2a
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/cloud.go
@@ -0,0 +1,188 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import "strings"
+
+const (
+ EventTypeFailedPayment = "failed-payment"
+ EventTypeFailedPaymentNoCard = "failed-payment-no-card"
+ EventTypeSendAdminWelcomeEmail = "send-admin-welcome-email"
+ EventTypeTrialWillEnd = "trial-will-end"
+ EventTypeTrialEnded = "trial-ended"
+ JoinLimitation = "join"
+ InviteLimitation = "invite"
+)
+
+var MockCWS string
+
+type BillingScheme string
+
+const (
+ BillingSchemePerSeat = BillingScheme("per_seat")
+ BillingSchemeFlatFee = BillingScheme("flat_fee")
+)
+
+type RecurringInterval string
+
+const (
+ RecurringIntervalYearly = RecurringInterval("year")
+ RecurringIntervalMonthly = RecurringInterval("month")
+)
+
+type SubscriptionFamily string
+
+const (
+ SubscriptionFamilyCloud = SubscriptionFamily("cloud")
+ SubscriptionFamilyOnPrem = SubscriptionFamily("on-prem")
+)
+
+// Product model represents a product on the cloud system.
+type Product struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ PricePerSeat float64 `json:"price_per_seat"`
+ AddOns []*AddOn `json:"add_ons"`
+ SKU string `json:"sku"`
+ PriceID string `json:"price_id"`
+ Family SubscriptionFamily `json:"product_family"`
+ RecurringInterval RecurringInterval `json:"recurring_interval"`
+ BillingScheme BillingScheme `json:"billing_scheme"`
+}
+
+// AddOn represents an addon to a product.
+type AddOn struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ DisplayName string `json:"display_name"`
+ PricePerSeat float64 `json:"price_per_seat"`
+}
+
+// StripeSetupIntent represents the SetupIntent model from Stripe for updating payment methods.
+type StripeSetupIntent struct {
+ ID string `json:"id"`
+ ClientSecret string `json:"client_secret"`
+}
+
+// ConfirmPaymentMethodRequest contains the fields for the customer payment update API.
+type ConfirmPaymentMethodRequest struct {
+ StripeSetupIntentID string `json:"stripe_setup_intent_id"`
+}
+
+// Customer model represents a customer on the system.
+type CloudCustomer struct {
+ CloudCustomerInfo
+ ID string `json:"id"`
+ CreatorID string `json:"creator_id"`
+ CreateAt int64 `json:"create_at"`
+ BillingAddress *Address `json:"billing_address"`
+ CompanyAddress *Address `json:"company_address"`
+ PaymentMethod *PaymentMethod `json:"payment_method"`
+}
+
+// CloudCustomerInfo represents editable info of a customer.
+type CloudCustomerInfo struct {
+ Name string `json:"name"`
+ Email string `json:"email,omitempty"`
+ ContactFirstName string `json:"contact_first_name,omitempty"`
+ ContactLastName string `json:"contact_last_name,omitempty"`
+ NumEmployees int `json:"num_employees"`
+}
+
+// Address model represents a customer's address.
+type Address struct {
+ City string `json:"city"`
+ Country string `json:"country"`
+ Line1 string `json:"line1"`
+ Line2 string `json:"line2"`
+ PostalCode string `json:"postal_code"`
+ State string `json:"state"`
+}
+
+// PaymentMethod represents methods of payment for a customer.
+type PaymentMethod struct {
+ Type string `json:"type"`
+ LastFour int `json:"last_four"`
+ ExpMonth int `json:"exp_month"`
+ ExpYear int `json:"exp_year"`
+ CardBrand string `json:"card_brand"`
+ Name string `json:"name"`
+}
+
+// Subscription model represents a subscription on the system.
+type Subscription struct {
+ ID string `json:"id"`
+ CustomerID string `json:"customer_id"`
+ ProductID string `json:"product_id"`
+ AddOns []string `json:"add_ons"`
+ StartAt int64 `json:"start_at"`
+ EndAt int64 `json:"end_at"`
+ CreateAt int64 `json:"create_at"`
+ Seats int `json:"seats"`
+ Status string `json:"status"`
+ DNS string `json:"dns"`
+ IsPaidTier string `json:"is_paid_tier"`
+ LastInvoice *Invoice `json:"last_invoice"`
+ IsFreeTrial string `json:"is_free_trial"`
+ TrialEndAt int64 `json:"trial_end_at"`
+}
+
+// GetWorkSpaceNameFromDNS returns the work space name. For example from test.mattermost.cloud.com, it returns test
+func (s *Subscription) GetWorkSpaceNameFromDNS() string {
+ return strings.Split(s.DNS, ".")[0]
+}
+
+// Invoice model represents a cloud invoice
+type Invoice struct {
+ ID string `json:"id"`
+ Number string `json:"number"`
+ CreateAt int64 `json:"create_at"`
+ Total int64 `json:"total"`
+ Tax int64 `json:"tax"`
+ Status string `json:"status"`
+ Description string `json:"description"`
+ PeriodStart int64 `json:"period_start"`
+ PeriodEnd int64 `json:"period_end"`
+ SubscriptionID string `json:"subscription_id"`
+ Items []*InvoiceLineItem `json:"line_items"`
+}
+
+// InvoiceLineItem model represents a cloud invoice lineitem tied to an invoice.
+type InvoiceLineItem struct {
+ PriceID string `json:"price_id"`
+ Total int64 `json:"total"`
+ Quantity int64 `json:"quantity"`
+ PricePerUnit int64 `json:"price_per_unit"`
+ Description string `json:"description"`
+ Type string `json:"type"`
+ Metadata map[string]interface{} `json:"metadata"`
+}
+
+type CWSWebhookPayload struct {
+ Event string `json:"event"`
+ FailedPayment *FailedPayment `json:"failed_payment"`
+ CloudWorkspaceOwner *CloudWorkspaceOwner `json:"cloud_workspace_owner"`
+ SubscriptionTrialEndUnixTimeStamp int64 `json:"trial_end_time_stamp"`
+}
+
+type FailedPayment struct {
+ CardBrand string `json:"card_brand"`
+ LastFour int `json:"last_four"`
+ FailureMessage string `json:"failure_message"`
+}
+
+// CloudWorkspaceOwner is part of the CWS Webhook payload that contains information about the user that created the workspace from the CWS
+type CloudWorkspaceOwner struct {
+ UserName string `json:"username"`
+}
+type SubscriptionStats struct {
+ RemainingSeats int `json:"remaining_seats"`
+ IsPaidTier string `json:"is_paid_tier"`
+ IsFreeTrial string `json:"is_free_trial"`
+}
+
+type SubscriptionChange struct {
+ ProductID string `json:"product_id"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_discovery.go b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_discovery.go
new file mode 100644
index 00000000..160a5917
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_discovery.go
@@ -0,0 +1,115 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "os"
+)
+
+const (
+ CDSOfflineAfterMillis = 1000 * 60 * 30 // 30 minutes
+ CDSTypeApp = "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 o.Hostname == "" {
+ if hn, err := os.Hostname(); err == nil {
+ o.Hostname = hn
+ }
+ }
+}
+
+func (o *ClusterDiscovery) AutoFillIPAddress(iface string, ipAddress string) {
+ // attempt to set the hostname to the first non-local IP address
+ if o.Hostname == "" {
+ if ipAddress != "" {
+ o.Hostname = ipAddress
+ } else {
+ o.Hostname = GetServerIPAddress(iface)
+ }
+ }
+}
+
+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 !IsValidId(o.Id) {
+ return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.ClusterName == "" {
+ return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.name.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.Type == "" {
+ return NewAppError("ClusterDiscovery.IsValid", "model.cluster.is_valid.type.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.Hostname == "" {
+ 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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_info.go b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_info.go
new file mode 100644
index 00000000..48d11d2f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_info.go
@@ -0,0 +1,12 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type ClusterInfo struct {
+ Id string `json:"id"`
+ Version string `json:"version"`
+ ConfigHash string `json:"config_hash"`
+ IPAddress string `json:"ipaddress"`
+ Hostname string `json:"hostname"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_message.go b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_message.go
new file mode 100644
index 00000000..9db02ffc
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_message.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type ClusterEvent string
+
+const (
+ ClusterEventPublish ClusterEvent = "publish"
+ ClusterEventUpdateStatus ClusterEvent = "update_status"
+ ClusterEventInvalidateAllCaches ClusterEvent = "inv_all_caches"
+ ClusterEventInvalidateCacheForReactions ClusterEvent = "inv_reactions"
+ ClusterEventInvalidateCacheForChannelMembersNotifyProps ClusterEvent = "inv_channel_members_notify_props"
+ ClusterEventInvalidateCacheForChannelByName ClusterEvent = "inv_channel_name"
+ ClusterEventInvalidateCacheForChannel ClusterEvent = "inv_channel"
+ ClusterEventInvalidateCacheForChannelGuestCount ClusterEvent = "inv_channel_guest_count"
+ ClusterEventInvalidateCacheForUser ClusterEvent = "inv_user"
+ ClusterEventInvalidateCacheForUserTeams ClusterEvent = "inv_user_teams"
+ ClusterEventClearSessionCacheForUser ClusterEvent = "clear_session_user"
+ ClusterEventInvalidateCacheForRoles ClusterEvent = "inv_roles"
+ ClusterEventInvalidateCacheForRolePermissions ClusterEvent = "inv_role_permissions"
+ ClusterEventInvalidateCacheForProfileByIds ClusterEvent = "inv_profile_ids"
+ ClusterEventInvalidateCacheForProfileInChannel ClusterEvent = "inv_profile_in_channel"
+ ClusterEventInvalidateCacheForSchemes ClusterEvent = "inv_schemes"
+ ClusterEventInvalidateCacheForFileInfos ClusterEvent = "inv_file_infos"
+ ClusterEventInvalidateCacheForWebhooks ClusterEvent = "inv_webhooks"
+ ClusterEventInvalidateCacheForEmojisById ClusterEvent = "inv_emojis_by_id"
+ ClusterEventInvalidateCacheForEmojisIdByName ClusterEvent = "inv_emojis_id_by_name"
+ ClusterEventInvalidateCacheForChannelPinnedpostsCounts ClusterEvent = "inv_channel_pinnedposts_counts"
+ ClusterEventInvalidateCacheForChannelMemberCounts ClusterEvent = "inv_channel_member_counts"
+ ClusterEventInvalidateCacheForLastPosts ClusterEvent = "inv_last_posts"
+ ClusterEventInvalidateCacheForLastPostTime ClusterEvent = "inv_last_post_time"
+ ClusterEventInvalidateCacheForTeams ClusterEvent = "inv_teams"
+ ClusterEventClearSessionCacheForAllUsers ClusterEvent = "inv_all_user_sessions"
+ ClusterEventInstallPlugin ClusterEvent = "install_plugin"
+ ClusterEventRemovePlugin ClusterEvent = "remove_plugin"
+ ClusterEventPluginEvent ClusterEvent = "plugin_event"
+ ClusterEventInvalidateCacheForTermsOfService ClusterEvent = "inv_terms_of_service"
+ ClusterEventBusyStateChanged ClusterEvent = "busy_state_change"
+
+ // Gossip communication
+ ClusterGossipEventRequestGetLogs = "gossip_request_get_logs"
+ ClusterGossipEventResponseGetLogs = "gossip_response_get_logs"
+ ClusterGossipEventRequestGetClusterStats = "gossip_request_cluster_stats"
+ ClusterGossipEventResponseGetClusterStats = "gossip_response_cluster_stats"
+ ClusterGossipEventRequestGetPluginStatuses = "gossip_request_plugin_statuses"
+ ClusterGossipEventResponseGetPluginStatuses = "gossip_response_plugin_statuses"
+ ClusterGossipEventRequestSaveConfig = "gossip_request_save_config"
+ ClusterGossipEventResponseSaveConfig = "gossip_response_save_config"
+
+ // SendTypes for ClusterMessage.
+ ClusterSendBestEffort = "best_effort"
+ ClusterSendReliable = "reliable"
+)
+
+type ClusterMessage struct {
+ Event ClusterEvent `json:"event"`
+ SendType string `json:"-"`
+ WaitForAllToSend bool `json:"-"`
+ Data []byte `json:"data,omitempty"`
+ Props map[string]string `json:"props,omitempty"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_stats.go b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_stats.go
new file mode 100644
index 00000000..3b41cb6e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/cluster_stats.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/command.go b/vendor/github.com/mattermost/mattermost-server/v6/model/command.go
new file mode 100644
index 00000000..4bb95298
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/command.go
@@ -0,0 +1,136 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "strings"
+)
+
+const (
+ CommandMethodPost = "P"
+ CommandMethodGet = "G"
+ MinTriggerLength = 1
+ MaxTriggerLength = 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"`
+ // PluginId records the id of the plugin that created this Command. If it is blank, the Command
+ // was not created by a plugin.
+ PluginId string `json:"plugin_id"`
+ AutocompleteData *AutocompleteData `db:"-" json:"autocomplete_data,omitempty"`
+ // AutocompleteIconData is a base64 encoded svg
+ AutocompleteIconData string `db:"-" json:"autocomplete_icon_data,omitempty"`
+}
+
+func (o *Command) IsValid() *AppError {
+ if !IsValidId(o.Id) {
+ 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 the CreatorId is blank, this should be a command created by a plugin.
+ if o.CreatorId == "" && !IsValidPluginId(o.PluginId) {
+ return NewAppError("Command.IsValid", "model.command.is_valid.plugin_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ // If the PluginId is blank, this should be a command associated with a userId.
+ if o.PluginId == "" && !IsValidId(o.CreatorId) {
+ return NewAppError("Command.IsValid", "model.command.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.CreatorId != "" && o.PluginId != "" {
+ return NewAppError("Command.IsValid", "model.command.is_valid.plugin_id.app_error", nil, "command cannot have both a CreatorId and a PluginId", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.TeamId) {
+ return NewAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(o.Trigger) < MinTriggerLength || len(o.Trigger) > MaxTriggerLength || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") {
+ return NewAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.URL == "" || 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 == CommandMethodGet || o.Method == CommandMethodPost) {
+ 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)
+ }
+
+ if o.AutocompleteData != nil {
+ if err := o.AutocompleteData.IsValid(); err != nil {
+ return NewAppError("Command.IsValid", "model.command.is_valid.autocomplete_data.app_error", nil, err.Error(), 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/v6/model/command_args.go b/vendor/github.com/mattermost/mattermost-server/v6/model/command_args.go
new file mode 100644
index 00000000..c8333acb
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/command_args.go
@@ -0,0 +1,45 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "github.com/mattermost/mattermost-server/v6/shared/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"`
+ TriggerId string `json:"trigger_id,omitempty"`
+ Command string `json:"command"`
+ SiteURL string `json:"-"`
+ T i18n.TranslateFunc `json:"-"`
+ UserMentions UserMentionMap `json:"-"`
+ ChannelMentions ChannelMentionMap `json:"-"`
+
+ // DO NOT USE Session field is deprecated. MM-26398
+ Session Session `json:"-"`
+}
+
+// AddUserMention adds or overrides an entry in UserMentions with name username
+// and identifier userId
+func (o *CommandArgs) AddUserMention(username, userId string) {
+ if o.UserMentions == nil {
+ o.UserMentions = make(UserMentionMap)
+ }
+
+ o.UserMentions[username] = userId
+}
+
+// AddChannelMention adds or overrides an entry in ChannelMentions with name
+// channelName and identifier channelId
+func (o *CommandArgs) AddChannelMention(channelName, channelId string) {
+ if o.ChannelMentions == nil {
+ o.ChannelMentions = make(ChannelMentionMap)
+ }
+
+ o.ChannelMentions[channelName] = channelId
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/command_autocomplete.go b/vendor/github.com/mattermost/mattermost-server/v6/model/command_autocomplete.go
new file mode 100644
index 00000000..a71a08c6
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/command_autocomplete.go
@@ -0,0 +1,410 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "net/url"
+ "path"
+ "reflect"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+// AutocompleteArgType describes autocomplete argument type
+type AutocompleteArgType string
+
+// Argument types
+const (
+ AutocompleteArgTypeText AutocompleteArgType = "TextInput"
+ AutocompleteArgTypeStaticList AutocompleteArgType = "StaticList"
+ AutocompleteArgTypeDynamicList AutocompleteArgType = "DynamicList"
+)
+
+// AutocompleteData describes slash command autocomplete information.
+type AutocompleteData struct {
+ // Trigger of the command
+ Trigger string
+ // Hint of a command
+ Hint string
+ // Text displayed to the user to help with the autocomplete description
+ HelpText string
+ // Role of the user who should be able to see the autocomplete info of this command
+ RoleID string
+ // Arguments of the command. Arguments can be named or positional.
+ // If they are positional order in the list matters, if they are named order does not matter.
+ // All arguments should be either named or positional, no mixing allowed.
+ Arguments []*AutocompleteArg
+ // Subcommands of the command
+ SubCommands []*AutocompleteData
+}
+
+// AutocompleteArg describes an argument of the command. Arguments can be named or positional.
+// If Name is empty string Argument is positional otherwise it is named argument.
+// Named arguments are passed as --Name Argument_Value.
+type AutocompleteArg struct {
+ // Name of the argument
+ Name string
+ // Text displayed to the user to help with the autocomplete
+ HelpText string
+ // Type of the argument
+ Type AutocompleteArgType
+ // Required determines if argument is optional or not.
+ Required bool
+ // Actual data of the argument (depends on the Type)
+ Data interface{}
+}
+
+// AutocompleteTextArg describes text user can input as an argument.
+type AutocompleteTextArg struct {
+ // Hint of the input text
+ Hint string
+ // Regex pattern to match
+ Pattern string
+}
+
+// AutocompleteListItem describes an item in the AutocompleteStaticListArg.
+type AutocompleteListItem struct {
+ Item string
+ Hint string
+ HelpText string
+}
+
+// AutocompleteStaticListArg is used to input one of the arguments from the list,
+// for example [yes, no], [on, off], and so on.
+type AutocompleteStaticListArg struct {
+ PossibleArguments []AutocompleteListItem
+}
+
+// AutocompleteDynamicListArg is used when user wants to download possible argument list from the URL.
+type AutocompleteDynamicListArg struct {
+ FetchURL string
+}
+
+// AutocompleteSuggestion describes a single suggestion item sent to the front-end
+// Example: for user input `/jira cre` -
+// Complete might be `/jira create`
+// Suggestion might be `create`,
+// Hint might be `[issue text]`,
+// Description might be `Create a new Issue`
+type AutocompleteSuggestion struct {
+ // Complete describes completed suggestion
+ Complete string
+ // Suggestion describes what user might want to input next
+ Suggestion string
+ // Hint describes a hint about the suggested input
+ Hint string
+ // Description of the command or a suggestion
+ Description string
+ // IconData is base64 encoded svg image
+ IconData string
+}
+
+// NewAutocompleteData returns new Autocomplete data.
+func NewAutocompleteData(trigger, hint, helpText string) *AutocompleteData {
+ return &AutocompleteData{
+ Trigger: trigger,
+ Hint: hint,
+ HelpText: helpText,
+ RoleID: SystemUserRoleId,
+ Arguments: []*AutocompleteArg{},
+ SubCommands: []*AutocompleteData{},
+ }
+}
+
+// AddCommand adds a subcommand to the autocomplete data.
+func (ad *AutocompleteData) AddCommand(command *AutocompleteData) {
+ ad.SubCommands = append(ad.SubCommands, command)
+}
+
+// AddTextArgument adds positional AutocompleteArgTypeText argument to the command.
+func (ad *AutocompleteData) AddTextArgument(helpText, hint, pattern string) {
+ ad.AddNamedTextArgument("", helpText, hint, pattern, true)
+}
+
+// AddNamedTextArgument adds named AutocompleteArgTypeText argument to the command.
+func (ad *AutocompleteData) AddNamedTextArgument(name, helpText, hint, pattern string, required bool) {
+ argument := AutocompleteArg{
+ Name: name,
+ HelpText: helpText,
+ Type: AutocompleteArgTypeText,
+ Required: required,
+ Data: &AutocompleteTextArg{Hint: hint, Pattern: pattern},
+ }
+ ad.Arguments = append(ad.Arguments, &argument)
+}
+
+// AddStaticListArgument adds positional AutocompleteArgTypeStaticList argument to the command.
+func (ad *AutocompleteData) AddStaticListArgument(helpText string, required bool, items []AutocompleteListItem) {
+ ad.AddNamedStaticListArgument("", helpText, required, items)
+}
+
+// AddNamedStaticListArgument adds named AutocompleteArgTypeStaticList argument to the command.
+func (ad *AutocompleteData) AddNamedStaticListArgument(name, helpText string, required bool, items []AutocompleteListItem) {
+ argument := AutocompleteArg{
+ Name: name,
+ HelpText: helpText,
+ Type: AutocompleteArgTypeStaticList,
+ Required: required,
+ Data: &AutocompleteStaticListArg{PossibleArguments: items},
+ }
+ ad.Arguments = append(ad.Arguments, &argument)
+}
+
+// AddDynamicListArgument adds positional AutocompleteArgTypeDynamicList argument to the command.
+func (ad *AutocompleteData) AddDynamicListArgument(helpText, url string, required bool) {
+ ad.AddNamedDynamicListArgument("", helpText, url, required)
+}
+
+// AddNamedDynamicListArgument adds named AutocompleteArgTypeDynamicList argument to the command.
+func (ad *AutocompleteData) AddNamedDynamicListArgument(name, helpText, url string, required bool) {
+ argument := AutocompleteArg{
+ Name: name,
+ HelpText: helpText,
+ Type: AutocompleteArgTypeDynamicList,
+ Required: required,
+ Data: &AutocompleteDynamicListArg{FetchURL: url},
+ }
+ ad.Arguments = append(ad.Arguments, &argument)
+}
+
+// Equals method checks if command is the same.
+func (ad *AutocompleteData) Equals(command *AutocompleteData) bool {
+ if !(ad.Trigger == command.Trigger && ad.HelpText == command.HelpText && ad.RoleID == command.RoleID && ad.Hint == command.Hint) {
+ return false
+ }
+ if len(ad.Arguments) != len(command.Arguments) || len(ad.SubCommands) != len(command.SubCommands) {
+ return false
+ }
+ for i := range ad.Arguments {
+ if !ad.Arguments[i].Equals(command.Arguments[i]) {
+ return false
+ }
+ }
+ for i := range ad.SubCommands {
+ if !ad.SubCommands[i].Equals(command.SubCommands[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// UpdateRelativeURLsForPluginCommands method updates relative urls for plugin commands
+func (ad *AutocompleteData) UpdateRelativeURLsForPluginCommands(baseURL *url.URL) error {
+ for _, arg := range ad.Arguments {
+ if arg.Type != AutocompleteArgTypeDynamicList {
+ continue
+ }
+ dynamicList, ok := arg.Data.(*AutocompleteDynamicListArg)
+ if !ok {
+ return errors.New("Not a proper DynamicList type argument")
+ }
+ dynamicListURL, err := url.Parse(dynamicList.FetchURL)
+ if err != nil {
+ return errors.Wrapf(err, "FetchURL is not a proper url")
+ }
+ if !dynamicListURL.IsAbs() {
+ absURL := &url.URL{}
+ *absURL = *baseURL
+ absURL.Path = path.Join(absURL.Path, dynamicList.FetchURL)
+ dynamicList.FetchURL = absURL.String()
+ }
+
+ }
+ for _, command := range ad.SubCommands {
+ err := command.UpdateRelativeURLsForPluginCommands(baseURL)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// IsValid method checks if autocomplete data is valid.
+func (ad *AutocompleteData) IsValid() error {
+ if ad == nil {
+ return errors.New("No nil commands are allowed in AutocompleteData")
+ }
+ if ad.Trigger == "" {
+ return errors.New("An empty command name in the autocomplete data")
+ }
+ if strings.ToLower(ad.Trigger) != ad.Trigger {
+ return errors.New("Command should be lowercase")
+ }
+ roles := []string{SystemAdminRoleId, SystemUserRoleId, ""}
+ if stringNotInSlice(ad.RoleID, roles) {
+ return errors.New("Wrong role in the autocomplete data")
+ }
+ if len(ad.Arguments) > 0 && len(ad.SubCommands) > 0 {
+ return errors.New("Command can't have arguments and subcommands")
+ }
+ if len(ad.Arguments) > 0 {
+ namedArgumentIndex := -1
+ for i, arg := range ad.Arguments {
+ if arg.Name != "" { // it's a named argument
+ if namedArgumentIndex == -1 { // first named argument
+ namedArgumentIndex = i
+ }
+ } else { // it's a positional argument
+ if namedArgumentIndex != -1 {
+ return errors.New("Named argument should not be before positional argument")
+ }
+ }
+ if arg.Type == AutocompleteArgTypeDynamicList {
+ dynamicList, ok := arg.Data.(*AutocompleteDynamicListArg)
+ if !ok {
+ return errors.New("Not a proper DynamicList type argument")
+ }
+ _, err := url.Parse(dynamicList.FetchURL)
+ if err != nil {
+ return errors.Wrapf(err, "FetchURL is not a proper url")
+ }
+ } else if arg.Type == AutocompleteArgTypeStaticList {
+ staticList, ok := arg.Data.(*AutocompleteStaticListArg)
+ if !ok {
+ return errors.New("Not a proper StaticList type argument")
+ }
+ for _, arg := range staticList.PossibleArguments {
+ if arg.Item == "" {
+ return errors.New("Possible argument name not set in StaticList argument")
+ }
+ }
+ } else if arg.Type == AutocompleteArgTypeText {
+ if _, ok := arg.Data.(*AutocompleteTextArg); !ok {
+ return errors.New("Not a proper TextInput type argument")
+ }
+ if arg.Name == "" && !arg.Required {
+ return errors.New("Positional argument can not be optional")
+ }
+ }
+ }
+ }
+ for _, command := range ad.SubCommands {
+ err := command.IsValid()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Equals method checks if argument is the same.
+func (a *AutocompleteArg) Equals(arg *AutocompleteArg) bool {
+ if a.Name != arg.Name ||
+ a.HelpText != arg.HelpText ||
+ a.Type != arg.Type ||
+ a.Required != arg.Required ||
+ !reflect.DeepEqual(a.Data, arg.Data) {
+ return false
+ }
+ return true
+}
+
+// UnmarshalJSON will unmarshal argument
+func (a *AutocompleteArg) UnmarshalJSON(b []byte) error {
+ var arg map[string]interface{}
+ if err := json.Unmarshal(b, &arg); err != nil {
+ return errors.Wrapf(err, "Can't unmarshal argument %s", string(b))
+ }
+ var ok bool
+ a.Name, ok = arg["Name"].(string)
+ if !ok {
+ return errors.Errorf("No field Name in the argument %s", string(b))
+ }
+
+ a.HelpText, ok = arg["HelpText"].(string)
+ if !ok {
+ return errors.Errorf("No field HelpText in the argument %s", string(b))
+ }
+
+ t, ok := arg["Type"].(string)
+ if !ok {
+ return errors.Errorf("No field Type in the argument %s", string(b))
+ }
+ a.Type = AutocompleteArgType(t)
+
+ a.Required, ok = arg["Required"].(bool)
+ if !ok {
+ return errors.Errorf("No field Required in the argument %s", string(b))
+ }
+
+ data, ok := arg["Data"]
+ if !ok {
+ return errors.Errorf("No field Data in the argument %s", string(b))
+ }
+
+ if a.Type == AutocompleteArgTypeText {
+ m, ok := data.(map[string]interface{})
+ if !ok {
+ return errors.Errorf("Wrong Data type in the TextInput argument %s", string(b))
+ }
+ pattern, ok := m["Pattern"].(string)
+ if !ok {
+ return errors.Errorf("No field Pattern in the TextInput argument %s", string(b))
+ }
+ hint, ok := m["Hint"].(string)
+ if !ok {
+ return errors.Errorf("No field Hint in the TextInput argument %s", string(b))
+ }
+ a.Data = &AutocompleteTextArg{Hint: hint, Pattern: pattern}
+ } else if a.Type == AutocompleteArgTypeStaticList {
+ m, ok := data.(map[string]interface{})
+ if !ok {
+ return errors.Errorf("Wrong Data type in the StaticList argument %s", string(b))
+ }
+ list, ok := m["PossibleArguments"].([]interface{})
+ if !ok {
+ return errors.Errorf("No field PossibleArguments in the StaticList argument %s", string(b))
+ }
+
+ possibleArguments := []AutocompleteListItem{}
+ for i := range list {
+ args, ok := list[i].(map[string]interface{})
+ if !ok {
+ return errors.Errorf("Wrong AutocompleteStaticListItem type in the StaticList argument %s", string(b))
+ }
+ item, ok := args["Item"].(string)
+ if !ok {
+ return errors.Errorf("No field Item in the StaticList's possible arguments %s", string(b))
+ }
+
+ hint, ok := args["Hint"].(string)
+ if !ok {
+ return errors.Errorf("No field Hint in the StaticList's possible arguments %s", string(b))
+ }
+ helpText, ok := args["HelpText"].(string)
+ if !ok {
+ return errors.Errorf("No field Hint in the StaticList's possible arguments %s", string(b))
+ }
+
+ possibleArguments = append(possibleArguments, AutocompleteListItem{
+ Item: item,
+ Hint: hint,
+ HelpText: helpText,
+ })
+ }
+ a.Data = &AutocompleteStaticListArg{PossibleArguments: possibleArguments}
+ } else if a.Type == AutocompleteArgTypeDynamicList {
+ m, ok := data.(map[string]interface{})
+ if !ok {
+ return errors.Errorf("Wrong type in the DynamicList argument %s", string(b))
+ }
+ url, ok := m["FetchURL"].(string)
+ if !ok {
+ return errors.Errorf("No field FetchURL in the DynamicList's argument %s", string(b))
+ }
+ a.Data = &AutocompleteDynamicListArg{FetchURL: url}
+ }
+ return nil
+}
+
+func stringNotInSlice(a string, slice []string) bool {
+ for _, b := range slice {
+ if b == a {
+ return false
+ }
+ }
+ return true
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/command_request.go b/vendor/github.com/mattermost/mattermost-server/v6/model/command_request.go
new file mode 100644
index 00000000..331394cb
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/command_request.go
@@ -0,0 +1,8 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type CommandMoveRequest struct {
+ TeamId string `json:"team_id"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/command_response.go b/vendor/github.com/mattermost/mattermost-server/v6/model/command_response.go
new file mode 100644
index 00000000..b2521f8e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/command_response.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2015-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/v6/utils/jsonutils"
+)
+
+const (
+ CommandResponseTypeInChannel = "in_channel"
+ CommandResponseTypeEphemeral = "ephemeral"
+)
+
+type CommandResponse struct {
+ ResponseType string `json:"response_type"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ ChannelId string `json:"channel_id"`
+ IconURL string `json:"icon_url"`
+ Type string `json:"type"`
+ Props StringInterface `json:"props"`
+ GotoLocation string `json:"goto_location"`
+ TriggerId string `json:"trigger_id"`
+ SkipSlackParsing bool `json:"skip_slack_parsing"` // Set to `true` to skip the Slack-compatibility handling of Text.
+ Attachments []*SlackAttachment `json:"attachments"`
+ ExtraResponses []*CommandResponse `json:"extra_responses"`
+}
+
+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)
+
+ if o.ExtraResponses != nil {
+ for _, resp := range o.ExtraResponses {
+ resp.Attachments = StringifySlackFieldValue(resp.Attachments)
+ }
+ }
+
+ return &o, nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/command_webhook.go b/vendor/github.com/mattermost/mattermost-server/v6/model/command_webhook.go
new file mode 100644
index 00000000..8093c1b7
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/command_webhook.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2015-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
+ UseCount int
+}
+
+const (
+ CommandWebhookLifetime = 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 !IsValidId(o.Id) {
+ 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 !IsValidId(o.CommandId) {
+ return NewAppError("CommandWebhook.IsValid", "model.command_hook.command_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.UserId) {
+ return NewAppError("CommandWebhook.IsValid", "model.command_hook.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.ChannelId) {
+ return NewAppError("CommandWebhook.IsValid", "model.command_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.RootId != "" && !IsValidId(o.RootId) {
+ return NewAppError("CommandWebhook.IsValid", "model.command_hook.root_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/compliance.go b/vendor/github.com/mattermost/mattermost-server/v6/model/compliance.go
new file mode 100644
index 00000000..b46a0d10
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/compliance.go
@@ -0,0 +1,109 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "strings"
+)
+
+const (
+ ComplianceStatusCreated = "created"
+ ComplianceStatusRunning = "running"
+ ComplianceStatusFinished = "finished"
+ ComplianceStatusFailed = "failed"
+ ComplianceStatusRemoved = "removed"
+
+ ComplianceTypeDaily = "daily"
+ ComplianceTypeAdhoc = "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
+
+// ComplianceExportCursor is used for paginated iteration of posts
+// for compliance export.
+// We need to keep track of the last post ID in addition to the last post
+// CreateAt to break ties when two posts have the same CreateAt.
+type ComplianceExportCursor struct {
+ LastChannelsQueryPostCreateAt int64
+ LastChannelsQueryPostID string
+ ChannelsQueryCompleted bool
+ LastDirectMessagesQueryPostCreateAt int64
+ LastDirectMessagesQueryPostID string
+ DirectMessagesQueryCompleted bool
+}
+
+func (c *Compliance) PreSave() {
+ if c.Id == "" {
+ c.Id = NewId()
+ }
+
+ if c.Status == "" {
+ c.Status = ComplianceStatusCreated
+ }
+
+ c.Count = 0
+ c.Emails = NormalizeEmail(c.Emails)
+ c.Keywords = strings.ToLower(c.Keywords)
+
+ c.CreateAt = GetMillis()
+}
+
+func (c *Compliance) DeepCopy() *Compliance {
+ copy := *c
+ return &copy
+}
+
+func (c *Compliance) JobName() string {
+ jobName := c.Type
+ if c.Type == ComplianceTypeDaily {
+ jobName += "-" + c.Desc
+ }
+
+ jobName += "-" + c.Id
+
+ return jobName
+}
+
+func (c *Compliance) IsValid() *AppError {
+ if !IsValidId(c.Id) {
+ return NewAppError("Compliance.IsValid", "model.compliance.is_valid.id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if c.CreateAt == 0 {
+ return NewAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(c.Desc) > 512 || c.Desc == "" {
+ return NewAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if c.StartAt == 0 {
+ return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_at.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if c.EndAt == 0 {
+ return NewAppError("Compliance.IsValid", "model.compliance.is_valid.end_at.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if c.EndAt <= c.StartAt {
+ return NewAppError("Compliance.IsValid", "model.compliance.is_valid.start_end_at.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/compliance_post.go b/vendor/github.com/mattermost/mattermost-server/v6/model/compliance_post.go
new file mode 100644
index 00000000..5c859ffe
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/compliance_post.go
@@ -0,0 +1,121 @@
+// Copyright (c) 2015-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
+ PostOriginalId string
+ PostMessage string
+ PostType string
+ PostProps string
+ PostHashtags string
+ PostFileIds string
+
+ IsBot bool
+}
+
+func CompliancePostHeader() []string {
+ return []string{
+ "TeamName",
+ "TeamDisplayName",
+
+ "ChannelName",
+ "ChannelDisplayName",
+ "ChannelType",
+
+ "UserUsername",
+ "UserEmail",
+ "UserNickname",
+ "UserType",
+
+ "PostId",
+ "PostCreateAt",
+ "PostUpdateAt",
+ "PostDeleteAt",
+ "PostRootId",
+ "PostOriginalId",
+ "PostMessage",
+ "PostType",
+ "PostProps",
+ "PostHashtags",
+ "PostFileIds",
+ }
+}
+
+func cleanComplianceStrings(in string) string {
+ if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched {
+ return "'" + in
+ }
+ return in
+}
+
+func (cp *CompliancePost) Row() []string {
+
+ postDeleteAt := ""
+ if cp.PostDeleteAt > 0 {
+ postDeleteAt = time.Unix(0, cp.PostDeleteAt*int64(1000*1000)).Format(time.RFC3339)
+ }
+
+ postUpdateAt := ""
+ if cp.PostUpdateAt != cp.PostCreateAt {
+ postUpdateAt = time.Unix(0, cp.PostUpdateAt*int64(1000*1000)).Format(time.RFC3339)
+ }
+
+ userType := "user"
+ if cp.IsBot {
+ userType = "bot"
+ }
+
+ return []string{
+ cleanComplianceStrings(cp.TeamName),
+ cleanComplianceStrings(cp.TeamDisplayName),
+
+ cleanComplianceStrings(cp.ChannelName),
+ cleanComplianceStrings(cp.ChannelDisplayName),
+ cleanComplianceStrings(cp.ChannelType),
+
+ cleanComplianceStrings(cp.UserUsername),
+ cleanComplianceStrings(cp.UserEmail),
+ cleanComplianceStrings(cp.UserNickname),
+ userType,
+
+ cp.PostId,
+ time.Unix(0, cp.PostCreateAt*int64(1000*1000)).Format(time.RFC3339),
+ postUpdateAt,
+ postDeleteAt,
+
+ cp.PostRootId,
+ cp.PostOriginalId,
+ cleanComplianceStrings(cp.PostMessage),
+ cp.PostType,
+ cp.PostProps,
+ cp.PostHashtags,
+ cp.PostFileIds,
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/config.go b/vendor/github.com/mattermost/mattermost-server/v6/model/config.go
new file mode 100644
index 00000000..39cdb893
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/config.go
@@ -0,0 +1,3915 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "io"
+ "math"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/mattermost/ldap"
+
+ "github.com/mattermost/mattermost-server/v6/shared/filestore"
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+const (
+ ConnSecurityNone = ""
+ ConnSecurityPlain = "PLAIN"
+ ConnSecurityTLS = "TLS"
+ ConnSecurityStarttls = "STARTTLS"
+
+ ImageDriverLocal = "local"
+ ImageDriverS3 = "amazons3"
+
+ DatabaseDriverMysql = "mysql"
+ DatabaseDriverPostgres = "postgres"
+
+ SearchengineElasticsearch = "elasticsearch"
+
+ MinioAccessKey = "minioaccesskey"
+ MinioSecretKey = "miniosecretkey"
+ MinioBucket = "mattermost-test"
+
+ PasswordMaximumLength = 64
+ PasswordMinimumLength = 5
+
+ ServiceGitlab = "gitlab"
+ ServiceGoogle = "google"
+ ServiceOffice365 = "office365"
+ ServiceOpenid = "openid"
+
+ GenericNoChannelNotification = "generic_no_channel"
+ GenericNotification = "generic"
+ GenericNotificationServer = "https://push-test.mattermost.com"
+ MmSupportAdvisorAddress = "support-advisor@mattermost.com"
+ FullNotification = "full"
+ IdLoadedNotification = "id_loaded"
+
+ DirectMessageAny = "any"
+ DirectMessageTeam = "team"
+
+ ShowUsername = "username"
+ ShowNicknameFullName = "nickname_full_name"
+ ShowFullName = "full_name"
+
+ PermissionsAll = "all"
+ PermissionsChannelAdmin = "channel_admin"
+ PermissionsTeamAdmin = "team_admin"
+ PermissionsSystemAdmin = "system_admin"
+
+ FakeSetting = "********************************"
+
+ RestrictEmojiCreationAll = "all"
+ RestrictEmojiCreationAdmin = "admin"
+ RestrictEmojiCreationSystemAdmin = "system_admin"
+
+ PermissionsDeletePostAll = "all"
+ PermissionsDeletePostTeamAdmin = "team_admin"
+ PermissionsDeletePostSystemAdmin = "system_admin"
+
+ GroupUnreadChannelsDisabled = "disabled"
+ GroupUnreadChannelsDefaultOn = "default_on"
+ GroupUnreadChannelsDefaultOff = "default_off"
+
+ CollapsedThreadsDisabled = "disabled"
+ CollapsedThreadsDefaultOn = "default_on"
+ CollapsedThreadsDefaultOff = "default_off"
+
+ EmailBatchingBufferSize = 256
+ EmailBatchingInterval = 30
+
+ EmailNotificationContentsFull = "full"
+ EmailNotificationContentsGeneric = "generic"
+
+ SitenameMaxLength = 30
+
+ ServiceSettingsDefaultSiteURL = "http://localhost:8065"
+ ServiceSettingsDefaultTLSCertFile = ""
+ ServiceSettingsDefaultTLSKeyFile = ""
+ ServiceSettingsDefaultReadTimeout = 300
+ ServiceSettingsDefaultWriteTimeout = 300
+ ServiceSettingsDefaultIdleTimeout = 60
+ ServiceSettingsDefaultMaxLoginAttempts = 10
+ ServiceSettingsDefaultAllowCorsFrom = ""
+ ServiceSettingsDefaultListenAndAddress = ":8065"
+ ServiceSettingsDefaultGfycatAPIKey = "2_KtH_W5"
+ ServiceSettingsDefaultGfycatAPISecret = "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof"
+
+ TeamSettingsDefaultSiteName = "Mattermost"
+ TeamSettingsDefaultMaxUsersPerTeam = 50
+ TeamSettingsDefaultCustomBrandText = ""
+ TeamSettingsDefaultCustomDescriptionText = ""
+ TeamSettingsDefaultUserStatusAwayTimeout = 300
+
+ SqlSettingsDefaultDataSource = "postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable&connect_timeout=10"
+
+ FileSettingsDefaultDirectory = "./data/"
+
+ ImportSettingsDefaultDirectory = "./import"
+ ImportSettingsDefaultRetentionDays = 30
+
+ ExportSettingsDefaultDirectory = "./export"
+ ExportSettingsDefaultRetentionDays = 30
+
+ EmailSettingsDefaultFeedbackOrganization = ""
+
+ SupportSettingsDefaultTermsOfServiceLink = "https://mattermost.com/terms-of-service/"
+ SupportSettingsDefaultPrivacyPolicyLink = "https://mattermost.com/privacy-policy/"
+ SupportSettingsDefaultAboutLink = "https://about.mattermost.com/default-about/"
+ SupportSettingsDefaultHelpLink = "https://about.mattermost.com/default-help/"
+ SupportSettingsDefaultReportAProblemLink = "https://about.mattermost.com/default-report-a-problem/"
+ SupportSettingsDefaultSupportEmail = ""
+ SupportSettingsDefaultReAcceptancePeriod = 365
+
+ LdapSettingsDefaultFirstNameAttribute = ""
+ LdapSettingsDefaultLastNameAttribute = ""
+ LdapSettingsDefaultEmailAttribute = ""
+ LdapSettingsDefaultUsernameAttribute = ""
+ LdapSettingsDefaultNicknameAttribute = ""
+ LdapSettingsDefaultIdAttribute = ""
+ LdapSettingsDefaultPositionAttribute = ""
+ LdapSettingsDefaultLoginFieldName = ""
+ LdapSettingsDefaultGroupDisplayNameAttribute = ""
+ LdapSettingsDefaultGroupIdAttribute = ""
+ LdapSettingsDefaultPictureAttribute = ""
+
+ SamlSettingsDefaultIdAttribute = ""
+ SamlSettingsDefaultGuestAttribute = ""
+ SamlSettingsDefaultAdminAttribute = ""
+ SamlSettingsDefaultFirstNameAttribute = ""
+ SamlSettingsDefaultLastNameAttribute = ""
+ SamlSettingsDefaultEmailAttribute = ""
+ SamlSettingsDefaultUsernameAttribute = ""
+ SamlSettingsDefaultNicknameAttribute = ""
+ SamlSettingsDefaultLocaleAttribute = ""
+ SamlSettingsDefaultPositionAttribute = ""
+
+ SamlSettingsSignatureAlgorithmSha1 = "RSAwithSHA1"
+ SamlSettingsSignatureAlgorithmSha256 = "RSAwithSHA256"
+ SamlSettingsSignatureAlgorithmSha512 = "RSAwithSHA512"
+ SamlSettingsDefaultSignatureAlgorithm = SamlSettingsSignatureAlgorithmSha1
+
+ SamlSettingsCanonicalAlgorithmC14n = "Canonical1.0"
+ SamlSettingsCanonicalAlgorithmC14n11 = "Canonical1.1"
+ SamlSettingsDefaultCanonicalAlgorithm = SamlSettingsCanonicalAlgorithmC14n
+
+ NativeappSettingsDefaultAppDownloadLink = "https://mattermost.com/download/#mattermostApps"
+ NativeappSettingsDefaultAndroidAppDownloadLink = "https://about.mattermost.com/mattermost-android-app/"
+ NativeappSettingsDefaultIosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/"
+
+ ExperimentalSettingsDefaultLinkMetadataTimeoutMilliseconds = 5000
+
+ AnalyticsSettingsDefaultMaxUsersForStatistics = 2500
+
+ AnnouncementSettingsDefaultBannerColor = "#f2a93b"
+ AnnouncementSettingsDefaultBannerTextColor = "#333333"
+ AnnouncementSettingsDefaultNoticesJsonURL = "https://notices.mattermost.com/"
+ AnnouncementSettingsDefaultNoticesFetchFrequencySeconds = 3600
+
+ TeamSettingsDefaultTeamText = "default"
+
+ ElasticsearchSettingsDefaultConnectionURL = "http://localhost:9200"
+ ElasticsearchSettingsDefaultUsername = "elastic"
+ ElasticsearchSettingsDefaultPassword = "changeme"
+ ElasticsearchSettingsDefaultPostIndexReplicas = 1
+ ElasticsearchSettingsDefaultPostIndexShards = 1
+ ElasticsearchSettingsDefaultChannelIndexReplicas = 1
+ ElasticsearchSettingsDefaultChannelIndexShards = 1
+ ElasticsearchSettingsDefaultUserIndexReplicas = 1
+ ElasticsearchSettingsDefaultUserIndexShards = 1
+ ElasticsearchSettingsDefaultAggregatePostsAfterDays = 365
+ ElasticsearchSettingsDefaultPostsAggregatorJobStartTime = "03:00"
+ ElasticsearchSettingsDefaultIndexPrefix = ""
+ ElasticsearchSettingsDefaultLiveIndexingBatchSize = 1
+ ElasticsearchSettingsDefaultBulkIndexingTimeWindowSeconds = 3600
+ ElasticsearchSettingsDefaultRequestTimeoutSeconds = 30
+
+ BleveSettingsDefaultIndexDir = ""
+ BleveSettingsDefaultBulkIndexingTimeWindowSeconds = 3600
+
+ DataRetentionSettingsDefaultMessageRetentionDays = 365
+ DataRetentionSettingsDefaultFileRetentionDays = 365
+ DataRetentionSettingsDefaultDeletionJobStartTime = "02:00"
+ DataRetentionSettingsDefaultBatchSize = 3000
+
+ PluginSettingsDefaultDirectory = "./plugins"
+ PluginSettingsDefaultClientDirectory = "./client/plugins"
+ PluginSettingsDefaultEnableMarketplace = true
+ PluginSettingsDefaultMarketplaceURL = "https://api.integrations.mattermost.com"
+ PluginSettingsOldMarketplaceURL = "https://marketplace.integrations.mattermost.com"
+
+ ComplianceExportTypeCsv = "csv"
+ ComplianceExportTypeActiance = "actiance"
+ ComplianceExportTypeGlobalrelay = "globalrelay"
+ ComplianceExportTypeGlobalrelayZip = "globalrelay-zip"
+ GlobalrelayCustomerTypeA9 = "A9"
+ GlobalrelayCustomerTypeA10 = "A10"
+
+ ClientSideCertCheckPrimaryAuth = "primary"
+ ClientSideCertCheckSecondaryAuth = "secondary"
+
+ ImageProxyTypeLocal = "local"
+ ImageProxyTypeAtmosCamo = "atmos/camo"
+
+ GoogleSettingsDefaultScope = "profile email"
+ GoogleSettingsDefaultAuthEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"
+ GoogleSettingsDefaultTokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"
+ GoogleSettingsDefaultUserAPIEndpoint = "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses,nicknames,metadata"
+
+ Office365SettingsDefaultScope = "User.Read"
+ Office365SettingsDefaultAuthEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
+ Office365SettingsDefaultTokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
+ Office365SettingsDefaultUserAPIEndpoint = "https://graph.microsoft.com/v1.0/me"
+
+ CloudSettingsDefaultCwsURL = "https://customers.mattermost.com"
+ CloudSettingsDefaultCwsAPIURL = "https://portal.internal.prod.cloud.mattermost.com"
+ OpenidSettingsDefaultScope = "profile openid email"
+
+ LocalModeSocketPath = "/var/tmp/mattermost_local.socket"
+)
+
+func GetDefaultAppCustomURLSchemes() []string {
+ return []string{"mmauth://", "mmauthbeta://"}
+}
+
+var ServerTLSSupportedCiphers = map[string]uint16{
+ "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
+ "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ "TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+ "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ "TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
+ "TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
+ "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+ "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+}
+
+type ServiceSettings struct {
+ SiteURL *string `access:"environment_web_server,authentication_saml,write_restrictable"`
+ WebsocketURL *string `access:"write_restrictable,cloud_restrictable"`
+ LicenseFileLocation *string `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ ListenAddress *string `access:"environment_web_server,write_restrictable,cloud_restrictable"` // telemetry: none
+ ConnectionSecurity *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ TLSCertFile *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ TLSKeyFile *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ TLSMinVer *string `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ TLSStrictTransport *bool `access:"write_restrictable,cloud_restrictable"`
+ TLSStrictTransportMaxAge *int64 `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ TLSOverwriteCiphers []string `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ UseLetsEncrypt *bool `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ LetsEncryptCertificateCacheFile *string `access:"environment_web_server,write_restrictable,cloud_restrictable"` // telemetry: none
+ Forward80To443 *bool `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ TrustedProxyIPHeader []string `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ ReadTimeout *int `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ WriteTimeout *int `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ IdleTimeout *int `access:"write_restrictable,cloud_restrictable"`
+ MaximumLoginAttempts *int `access:"authentication_password,write_restrictable,cloud_restrictable"`
+ GoroutineHealthThreshold *int `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ EnableOAuthServiceProvider *bool `access:"integrations_integration_management"`
+ EnableIncomingWebhooks *bool `access:"integrations_integration_management"`
+ EnableOutgoingWebhooks *bool `access:"integrations_integration_management"`
+ EnableCommands *bool `access:"integrations_integration_management"`
+ EnablePostUsernameOverride *bool `access:"integrations_integration_management"`
+ EnablePostIconOverride *bool `access:"integrations_integration_management"`
+ GoogleDeveloperKey *string `access:"site_posts,write_restrictable,cloud_restrictable"`
+ EnableLinkPreviews *bool `access:"site_posts"`
+ EnablePermalinkPreviews *bool `access:"site_posts"`
+ RestrictLinkPreviews *string `access:"site_posts"`
+ EnableTesting *bool `access:"environment_developer,write_restrictable,cloud_restrictable"`
+ EnableDeveloper *bool `access:"environment_developer,write_restrictable,cloud_restrictable"`
+ EnableOpenTracing *bool `access:"write_restrictable,cloud_restrictable"`
+ EnableSecurityFixAlert *bool `access:"environment_smtp,write_restrictable,cloud_restrictable"`
+ EnableInsecureOutgoingConnections *bool `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ AllowedUntrustedInternalConnections *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ EnableMultifactorAuthentication *bool `access:"authentication_mfa"`
+ EnforceMultifactorAuthentication *bool `access:"authentication_mfa"`
+ EnableUserAccessTokens *bool `access:"integrations_integration_management"`
+ AllowCorsFrom *string `access:"integrations_cors,write_restrictable,cloud_restrictable"`
+ CorsExposedHeaders *string `access:"integrations_cors,write_restrictable,cloud_restrictable"`
+ CorsAllowCredentials *bool `access:"integrations_cors,write_restrictable,cloud_restrictable"`
+ CorsDebug *bool `access:"integrations_cors,write_restrictable,cloud_restrictable"`
+ AllowCookiesForSubdomains *bool `access:"write_restrictable,cloud_restrictable"`
+ ExtendSessionLengthWithActivity *bool `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
+ SessionLengthWebInDays *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
+ SessionLengthMobileInDays *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
+ SessionLengthSSOInDays *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
+ SessionCacheInMinutes *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
+ SessionIdleTimeoutInMinutes *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
+ WebsocketSecurePort *int `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ WebsocketPort *int `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ WebserverMode *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ EnableGifPicker *bool `access:"integrations_gif"`
+ GfycatAPIKey *string `access:"integrations_gif"`
+ GfycatAPISecret *string `access:"integrations_gif"`
+ EnableCustomEmoji *bool `access:"site_emoji"`
+ EnableEmojiPicker *bool `access:"site_emoji"`
+ PostEditTimeLimit *int `access:"user_management_permissions"`
+ TimeBetweenUserTypingUpdatesMilliseconds *int64 `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ EnablePostSearch *bool `access:"write_restrictable,cloud_restrictable"`
+ EnableFileSearch *bool `access:"write_restrictable"`
+ MinimumHashtagLength *int `access:"environment_database,write_restrictable,cloud_restrictable"`
+ EnableUserTypingMessages *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ EnableChannelViewedMessages *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ EnableUserStatuses *bool `access:"write_restrictable,cloud_restrictable"`
+ ExperimentalEnableAuthenticationTransfer *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ ClusterLogTimeoutMilliseconds *int `access:"write_restrictable,cloud_restrictable"`
+ EnablePreviewFeatures *bool `access:"experimental_features"`
+ EnableTutorial *bool `access:"experimental_features"`
+ EnableOnboardingFlow *bool `access:"experimental_features"`
+ ExperimentalEnableDefaultChannelLeaveJoinMessages *bool `access:"experimental_features"`
+ ExperimentalGroupUnreadChannels *string `access:"experimental_features"`
+ EnableAPITeamDeletion *bool
+ EnableAPIUserDeletion *bool
+ ExperimentalEnableHardenedMode *bool `access:"experimental_features"`
+ ExperimentalStrictCSRFEnforcement *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ EnableEmailInvitations *bool `access:"authentication_signup"`
+ DisableBotsWhenOwnerIsDeactivated *bool `access:"integrations_bot_accounts,write_restrictable,cloud_restrictable"`
+ EnableBotAccountCreation *bool `access:"integrations_bot_accounts"`
+ EnableSVGs *bool `access:"site_posts"`
+ EnableLatex *bool `access:"site_posts"`
+ EnableAPIChannelDeletion *bool
+ EnableLocalMode *bool
+ LocalModeSocketLocation *string // telemetry: none
+ EnableAWSMetering *bool // telemetry: none
+ SplitKey *string `access:"experimental_feature_flags,write_restrictable"` // telemetry: none
+ FeatureFlagSyncIntervalSeconds *int `access:"experimental_feature_flags,write_restrictable"` // telemetry: none
+ DebugSplit *bool `access:"experimental_feature_flags,write_restrictable"` // telemetry: none
+ ThreadAutoFollow *bool `access:"experimental_features"`
+ CollapsedThreads *string `access:"experimental_features"`
+ ManagedResourcePaths *string `access:"environment_web_server,write_restrictable,cloud_restrictable"`
+ EnableReliableWebSockets *bool `access:"experimental_features"` // telemetry: none
+}
+
+func (s *ServiceSettings) SetDefaults(isUpdate bool) {
+ 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 {
+ if s.EnableDeveloper != nil && *s.EnableDeveloper {
+ s.SiteURL = NewString(ServiceSettingsDefaultSiteURL)
+ } else {
+ s.SiteURL = NewString("")
+ }
+ }
+
+ if s.WebsocketURL == nil {
+ s.WebsocketURL = NewString("")
+ }
+
+ if s.LicenseFileLocation == nil {
+ s.LicenseFileLocation = NewString("")
+ }
+
+ if s.ListenAddress == nil {
+ s.ListenAddress = NewString(ServiceSettingsDefaultListenAndAddress)
+ }
+
+ if s.EnableLinkPreviews == nil {
+ s.EnableLinkPreviews = NewBool(true)
+ }
+
+ if s.EnablePermalinkPreviews == nil {
+ s.EnablePermalinkPreviews = NewBool(true)
+ }
+
+ if s.RestrictLinkPreviews == nil {
+ s.RestrictLinkPreviews = NewString("")
+ }
+
+ if s.EnableTesting == nil {
+ s.EnableTesting = NewBool(false)
+ }
+
+ if s.EnableDeveloper == nil {
+ s.EnableDeveloper = NewBool(false)
+ }
+
+ if s.EnableOpenTracing == nil {
+ s.EnableOpenTracing = 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.GoogleDeveloperKey == nil {
+ s.GoogleDeveloperKey = NewString("")
+ }
+
+ if s.EnableOAuthServiceProvider == nil {
+ s.EnableOAuthServiceProvider = NewBool(false)
+ }
+
+ if s.EnableIncomingWebhooks == nil {
+ s.EnableIncomingWebhooks = NewBool(true)
+ }
+
+ if s.EnableOutgoingWebhooks == nil {
+ s.EnableOutgoingWebhooks = NewBool(true)
+ }
+
+ if s.ConnectionSecurity == nil {
+ s.ConnectionSecurity = NewString("")
+ }
+
+ if s.TLSKeyFile == nil {
+ s.TLSKeyFile = NewString(ServiceSettingsDefaultTLSKeyFile)
+ }
+
+ if s.TLSCertFile == nil {
+ s.TLSCertFile = NewString(ServiceSettingsDefaultTLSCertFile)
+ }
+
+ if s.TLSMinVer == nil {
+ s.TLSMinVer = NewString("1.2")
+ }
+
+ if s.TLSStrictTransport == nil {
+ s.TLSStrictTransport = NewBool(false)
+ }
+
+ if s.TLSStrictTransportMaxAge == nil {
+ s.TLSStrictTransportMaxAge = NewInt64(63072000)
+ }
+
+ if s.TLSOverwriteCiphers == nil {
+ s.TLSOverwriteCiphers = []string{}
+ }
+
+ 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(ServiceSettingsDefaultReadTimeout)
+ }
+
+ if s.WriteTimeout == nil {
+ s.WriteTimeout = NewInt(ServiceSettingsDefaultWriteTimeout)
+ }
+
+ if s.IdleTimeout == nil {
+ s.IdleTimeout = NewInt(ServiceSettingsDefaultIdleTimeout)
+ }
+
+ if s.MaximumLoginAttempts == nil {
+ s.MaximumLoginAttempts = NewInt(ServiceSettingsDefaultMaxLoginAttempts)
+ }
+
+ if s.Forward80To443 == nil {
+ s.Forward80To443 = NewBool(false)
+ }
+
+ if isUpdate {
+ // When updating an existing configuration, ensure that defaults are set.
+ if s.TrustedProxyIPHeader == nil {
+ s.TrustedProxyIPHeader = []string{HeaderForwarded, HeaderRealIP}
+ }
+ } else {
+ // When generating a blank configuration, leave the list empty.
+ s.TrustedProxyIPHeader = []string{}
+ }
+
+ if s.TimeBetweenUserTypingUpdatesMilliseconds == nil {
+ s.TimeBetweenUserTypingUpdatesMilliseconds = NewInt64(5000)
+ }
+
+ if s.EnablePostSearch == nil {
+ s.EnablePostSearch = NewBool(true)
+ }
+
+ if s.EnableFileSearch == nil {
+ s.EnableFileSearch = NewBool(true)
+ }
+
+ if s.MinimumHashtagLength == nil {
+ s.MinimumHashtagLength = NewInt(3)
+ }
+
+ 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.EnableTutorial == nil {
+ s.EnableTutorial = NewBool(true)
+ }
+
+ if s.EnableOnboardingFlow == nil {
+ s.EnableOnboardingFlow = NewBool(true)
+ }
+
+ // Must be manually enabled for existing installations.
+ if s.ExtendSessionLengthWithActivity == nil {
+ s.ExtendSessionLengthWithActivity = NewBool(!isUpdate)
+ }
+
+ if s.SessionLengthWebInDays == nil {
+ if isUpdate {
+ s.SessionLengthWebInDays = NewInt(180)
+ } else {
+ s.SessionLengthWebInDays = NewInt(30)
+ }
+ }
+
+ if s.SessionLengthMobileInDays == nil {
+ if isUpdate {
+ s.SessionLengthMobileInDays = NewInt(180)
+ } else {
+ 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(43200)
+ }
+
+ if s.EnableCommands == nil {
+ s.EnableCommands = NewBool(true)
+ }
+
+ if s.EnablePostUsernameOverride == nil {
+ s.EnablePostUsernameOverride = NewBool(false)
+ }
+
+ if s.EnablePostIconOverride == nil {
+ s.EnablePostIconOverride = NewBool(false)
+ }
+
+ if s.WebsocketPort == nil {
+ s.WebsocketPort = NewInt(80)
+ }
+
+ if s.WebsocketSecurePort == nil {
+ s.WebsocketSecurePort = NewInt(443)
+ }
+
+ if s.AllowCorsFrom == nil {
+ s.AllowCorsFrom = NewString(ServiceSettingsDefaultAllowCorsFrom)
+ }
+
+ 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(true)
+ }
+
+ if s.EnableEmojiPicker == nil {
+ s.EnableEmojiPicker = NewBool(true)
+ }
+
+ if s.EnableGifPicker == nil {
+ s.EnableGifPicker = NewBool(true)
+ }
+
+ if s.GfycatAPIKey == nil || *s.GfycatAPIKey == "" {
+ s.GfycatAPIKey = NewString(ServiceSettingsDefaultGfycatAPIKey)
+ }
+
+ if s.GfycatAPISecret == nil || *s.GfycatAPISecret == "" {
+ s.GfycatAPISecret = NewString(ServiceSettingsDefaultGfycatAPISecret)
+ }
+
+ 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(GroupUnreadChannelsDisabled)
+ } else if *s.ExperimentalGroupUnreadChannels == "0" {
+ s.ExperimentalGroupUnreadChannels = NewString(GroupUnreadChannelsDisabled)
+ } else if *s.ExperimentalGroupUnreadChannels == "1" {
+ s.ExperimentalGroupUnreadChannels = NewString(GroupUnreadChannelsDefaultOn)
+ }
+
+ if s.EnableAPITeamDeletion == nil {
+ s.EnableAPITeamDeletion = NewBool(false)
+ }
+
+ if s.EnableAPIUserDeletion == nil {
+ s.EnableAPIUserDeletion = NewBool(false)
+ }
+
+ if s.EnableAPIChannelDeletion == nil {
+ s.EnableAPIChannelDeletion = NewBool(false)
+ }
+
+ if s.ExperimentalEnableHardenedMode == nil {
+ s.ExperimentalEnableHardenedMode = NewBool(false)
+ }
+
+ if s.ExperimentalStrictCSRFEnforcement == nil {
+ s.ExperimentalStrictCSRFEnforcement = NewBool(false)
+ }
+
+ if s.DisableBotsWhenOwnerIsDeactivated == nil {
+ s.DisableBotsWhenOwnerIsDeactivated = NewBool(true)
+ }
+
+ if s.EnableBotAccountCreation == nil {
+ s.EnableBotAccountCreation = NewBool(false)
+ }
+
+ if s.EnableSVGs == nil {
+ if isUpdate {
+ s.EnableSVGs = NewBool(true)
+ } else {
+ s.EnableSVGs = NewBool(false)
+ }
+ }
+
+ if s.EnableLatex == nil {
+ if isUpdate {
+ s.EnableLatex = NewBool(true)
+ } else {
+ s.EnableLatex = NewBool(false)
+ }
+ }
+
+ if s.EnableLocalMode == nil {
+ s.EnableLocalMode = NewBool(false)
+ }
+
+ if s.LocalModeSocketLocation == nil {
+ s.LocalModeSocketLocation = NewString(LocalModeSocketPath)
+ }
+
+ if s.EnableAWSMetering == nil {
+ s.EnableAWSMetering = NewBool(false)
+ }
+
+ if s.SplitKey == nil {
+ s.SplitKey = NewString("")
+ }
+
+ if s.FeatureFlagSyncIntervalSeconds == nil {
+ s.FeatureFlagSyncIntervalSeconds = NewInt(30)
+ }
+
+ if s.DebugSplit == nil {
+ s.DebugSplit = NewBool(false)
+ }
+
+ if s.ThreadAutoFollow == nil {
+ s.ThreadAutoFollow = NewBool(true)
+ }
+
+ if s.CollapsedThreads == nil {
+ s.CollapsedThreads = NewString(CollapsedThreadsDisabled)
+ }
+
+ if s.ManagedResourcePaths == nil {
+ s.ManagedResourcePaths = NewString("")
+ }
+
+ if s.EnableReliableWebSockets == nil {
+ s.EnableReliableWebSockets = NewBool(true)
+ }
+}
+
+type ClusterSettings struct {
+ Enable *bool `access:"environment_high_availability,write_restrictable"`
+ ClusterName *string `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+ OverrideHostname *string `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+ NetworkInterface *string `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ BindAddress *string `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ AdvertiseAddress *string `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ UseIPAddress *bool `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ EnableGossipCompression *bool `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ EnableExperimentalGossipEncryption *bool `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ ReadOnlyConfig *bool `access:"environment_high_availability,write_restrictable,cloud_restrictable"`
+ GossipPort *int `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+ StreamingPort *int `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+ MaxIdleConns *int `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+ MaxIdleConnsPerHost *int `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+ IdleConnTimeoutMilliseconds *int `access:"environment_high_availability,write_restrictable,cloud_restrictable"` // telemetry: none
+}
+
+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.NetworkInterface == nil {
+ s.NetworkInterface = NewString("")
+ }
+
+ if s.BindAddress == nil {
+ s.BindAddress = NewString("")
+ }
+
+ if s.AdvertiseAddress == nil {
+ s.AdvertiseAddress = NewString("")
+ }
+
+ if s.UseIPAddress == nil {
+ s.UseIPAddress = NewBool(true)
+ }
+
+ if s.EnableExperimentalGossipEncryption == nil {
+ s.EnableExperimentalGossipEncryption = NewBool(false)
+ }
+
+ if s.EnableGossipCompression == nil {
+ s.EnableGossipCompression = NewBool(true)
+ }
+
+ 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 `access:"environment_performance_monitoring,write_restrictable,cloud_restrictable"`
+ BlockProfileRate *int `access:"environment_performance_monitoring,write_restrictable,cloud_restrictable"`
+ ListenAddress *string `access:"environment_performance_monitoring,write_restrictable,cloud_restrictable"` // telemetry: none
+}
+
+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 `access:"experimental_features,cloud_restrictable"`
+ ClientSideCertCheck *string `access:"experimental_features,cloud_restrictable"`
+ EnableClickToReply *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ LinkMetadataTimeoutMilliseconds *int64 `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ RestrictSystemAdmin *bool `access:"experimental_features,write_restrictable"`
+ UseNewSAMLLibrary *bool `access:"experimental_features,cloud_restrictable"`
+ CloudUserLimit *int64 `access:"experimental_features,write_restrictable"`
+ CloudBilling *bool `access:"experimental_features,write_restrictable"`
+ EnableSharedChannels *bool `access:"experimental_features"`
+ EnableRemoteClusterService *bool `access:"experimental_features"`
+}
+
+func (s *ExperimentalSettings) SetDefaults() {
+ if s.ClientSideCertEnable == nil {
+ s.ClientSideCertEnable = NewBool(false)
+ }
+
+ if s.ClientSideCertCheck == nil {
+ s.ClientSideCertCheck = NewString(ClientSideCertCheckSecondaryAuth)
+ }
+
+ if s.EnableClickToReply == nil {
+ s.EnableClickToReply = NewBool(false)
+ }
+
+ if s.LinkMetadataTimeoutMilliseconds == nil {
+ s.LinkMetadataTimeoutMilliseconds = NewInt64(ExperimentalSettingsDefaultLinkMetadataTimeoutMilliseconds)
+ }
+
+ if s.RestrictSystemAdmin == nil {
+ s.RestrictSystemAdmin = NewBool(false)
+ }
+
+ if s.CloudUserLimit == nil {
+ // User limit 0 is treated as no limit
+ s.CloudUserLimit = NewInt64(0)
+ }
+
+ if s.CloudBilling == nil {
+ s.CloudBilling = NewBool(false)
+ }
+
+ if s.UseNewSAMLLibrary == nil {
+ s.UseNewSAMLLibrary = NewBool(false)
+ }
+
+ if s.EnableSharedChannels == nil {
+ s.EnableSharedChannels = NewBool(false)
+ }
+
+ if s.EnableRemoteClusterService == nil {
+ s.EnableRemoteClusterService = NewBool(false)
+ }
+}
+
+type AnalyticsSettings struct {
+ MaxUsersForStatistics *int `access:"write_restrictable,cloud_restrictable"`
+}
+
+func (s *AnalyticsSettings) SetDefaults() {
+ if s.MaxUsersForStatistics == nil {
+ s.MaxUsersForStatistics = NewInt(AnalyticsSettingsDefaultMaxUsersForStatistics)
+ }
+}
+
+type SSOSettings struct {
+ Enable *bool `access:"authentication_openid"`
+ Secret *string `access:"authentication_openid"` // telemetry: none
+ Id *string `access:"authentication_openid"` // telemetry: none
+ Scope *string `access:"authentication_openid"` // telemetry: none
+ AuthEndpoint *string `access:"authentication_openid"` // telemetry: none
+ TokenEndpoint *string `access:"authentication_openid"` // telemetry: none
+ UserAPIEndpoint *string `access:"authentication_openid"` // telemetry: none
+ DiscoveryEndpoint *string `access:"authentication_openid"` // telemetry: none
+ ButtonText *string `access:"authentication_openid"` // telemetry: none
+ ButtonColor *string `access:"authentication_openid"` // telemetry: none
+}
+
+func (s *SSOSettings) setDefaults(scope, authEndpoint, tokenEndpoint, userAPIEndpoint, buttonColor string) {
+ if s.Enable == nil {
+ s.Enable = NewBool(false)
+ }
+
+ if s.Secret == nil {
+ s.Secret = NewString("")
+ }
+
+ if s.Id == nil {
+ s.Id = NewString("")
+ }
+
+ if s.Scope == nil {
+ s.Scope = NewString(scope)
+ }
+
+ if s.DiscoveryEndpoint == nil {
+ s.DiscoveryEndpoint = NewString("")
+ }
+
+ if s.AuthEndpoint == nil {
+ s.AuthEndpoint = NewString(authEndpoint)
+ }
+
+ if s.TokenEndpoint == nil {
+ s.TokenEndpoint = NewString(tokenEndpoint)
+ }
+
+ if s.UserAPIEndpoint == nil {
+ s.UserAPIEndpoint = NewString(userAPIEndpoint)
+ }
+
+ if s.ButtonText == nil {
+ s.ButtonText = NewString("")
+ }
+
+ if s.ButtonColor == nil {
+ s.ButtonColor = NewString(buttonColor)
+ }
+}
+
+type Office365Settings struct {
+ Enable *bool `access:"authentication_openid"`
+ Secret *string `access:"authentication_openid"` // telemetry: none
+ Id *string `access:"authentication_openid"` // telemetry: none
+ Scope *string `access:"authentication_openid"`
+ AuthEndpoint *string `access:"authentication_openid"` // telemetry: none
+ TokenEndpoint *string `access:"authentication_openid"` // telemetry: none
+ UserAPIEndpoint *string `access:"authentication_openid"` // telemetry: none
+ DiscoveryEndpoint *string `access:"authentication_openid"` // telemetry: none
+ DirectoryId *string `access:"authentication_openid"` // telemetry: none
+}
+
+func (s *Office365Settings) setDefaults() {
+ if s.Enable == nil {
+ s.Enable = NewBool(false)
+ }
+
+ if s.Id == nil {
+ s.Id = NewString("")
+ }
+
+ if s.Secret == nil {
+ s.Secret = NewString("")
+ }
+
+ if s.Scope == nil {
+ s.Scope = NewString(Office365SettingsDefaultScope)
+ }
+
+ if s.DiscoveryEndpoint == nil {
+ s.DiscoveryEndpoint = NewString("")
+ }
+
+ if s.AuthEndpoint == nil {
+ s.AuthEndpoint = NewString(Office365SettingsDefaultAuthEndpoint)
+ }
+
+ if s.TokenEndpoint == nil {
+ s.TokenEndpoint = NewString(Office365SettingsDefaultTokenEndpoint)
+ }
+
+ if s.UserAPIEndpoint == nil {
+ s.UserAPIEndpoint = NewString(Office365SettingsDefaultUserAPIEndpoint)
+ }
+
+ if s.DirectoryId == nil {
+ s.DirectoryId = NewString("")
+ }
+}
+
+func (s *Office365Settings) SSOSettings() *SSOSettings {
+ ssoSettings := SSOSettings{}
+ ssoSettings.Enable = s.Enable
+ ssoSettings.Secret = s.Secret
+ ssoSettings.Id = s.Id
+ ssoSettings.Scope = s.Scope
+ ssoSettings.DiscoveryEndpoint = s.DiscoveryEndpoint
+ ssoSettings.AuthEndpoint = s.AuthEndpoint
+ ssoSettings.TokenEndpoint = s.TokenEndpoint
+ ssoSettings.UserAPIEndpoint = s.UserAPIEndpoint
+ return &ssoSettings
+}
+
+type ReplicaLagSettings struct {
+ DataSource *string `access:"environment,write_restrictable,cloud_restrictable"` // telemetry: none
+ QueryAbsoluteLag *string `access:"environment,write_restrictable,cloud_restrictable"` // telemetry: none
+ QueryTimeLag *string `access:"environment,write_restrictable,cloud_restrictable"` // telemetry: none
+}
+
+type SqlSettings struct {
+ DriverName *string `access:"environment_database,write_restrictable,cloud_restrictable"`
+ DataSource *string `access:"environment_database,write_restrictable,cloud_restrictable"` // telemetry: none
+ DataSourceReplicas []string `access:"environment_database,write_restrictable,cloud_restrictable"`
+ DataSourceSearchReplicas []string `access:"environment_database,write_restrictable,cloud_restrictable"`
+ MaxIdleConns *int `access:"environment_database,write_restrictable,cloud_restrictable"`
+ ConnMaxLifetimeMilliseconds *int `access:"environment_database,write_restrictable,cloud_restrictable"`
+ ConnMaxIdleTimeMilliseconds *int `access:"environment_database,write_restrictable,cloud_restrictable"`
+ MaxOpenConns *int `access:"environment_database,write_restrictable,cloud_restrictable"`
+ Trace *bool `access:"environment_database,write_restrictable,cloud_restrictable"`
+ AtRestEncryptKey *string `access:"environment_database,write_restrictable,cloud_restrictable"` // telemetry: none
+ QueryTimeout *int `access:"environment_database,write_restrictable,cloud_restrictable"`
+ DisableDatabaseSearch *bool `access:"environment_database,write_restrictable,cloud_restrictable"`
+ ReplicaLagSettings []*ReplicaLagSettings `access:"environment_database,write_restrictable,cloud_restrictable"` // telemetry: none
+}
+
+func (s *SqlSettings) SetDefaults(isUpdate bool) {
+ if s.DriverName == nil {
+ s.DriverName = NewString(DatabaseDriverPostgres)
+ }
+
+ if s.DataSource == nil {
+ s.DataSource = NewString(SqlSettingsDefaultDataSource)
+ }
+
+ if s.DataSourceReplicas == nil {
+ s.DataSourceReplicas = []string{}
+ }
+
+ if s.DataSourceSearchReplicas == nil {
+ s.DataSourceSearchReplicas = []string{}
+ }
+
+ if isUpdate {
+ // When updating an existing configuration, ensure an encryption key has been specified.
+ if s.AtRestEncryptKey == nil || *s.AtRestEncryptKey == "" {
+ s.AtRestEncryptKey = NewString(NewRandomString(32))
+ }
+ } else {
+ // When generating a blank configuration, leave this key empty to be generated on server start.
+ s.AtRestEncryptKey = NewString("")
+ }
+
+ 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.ConnMaxIdleTimeMilliseconds == nil {
+ s.ConnMaxIdleTimeMilliseconds = NewInt(300000)
+ }
+
+ if s.Trace == nil {
+ s.Trace = NewBool(false)
+ }
+
+ if s.QueryTimeout == nil {
+ s.QueryTimeout = NewInt(30)
+ }
+
+ if s.DisableDatabaseSearch == nil {
+ s.DisableDatabaseSearch = NewBool(false)
+ }
+
+ if s.ReplicaLagSettings == nil {
+ s.ReplicaLagSettings = []*ReplicaLagSettings{}
+ }
+}
+
+type LogSettings struct {
+ EnableConsole *bool `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ ConsoleLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ ConsoleJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ EnableColor *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none
+ EnableFile *bool `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ FileLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ FileJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ FileLocation *string `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ EnableWebhookDebugging *bool `access:"environment_logging,write_restrictable,cloud_restrictable"`
+ EnableDiagnostics *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none
+ EnableSentry *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none
+ AdvancedLoggingConfig *string `access:"environment_logging,write_restrictable,cloud_restrictable"`
+}
+
+func NewLogSettings() *LogSettings {
+ settings := &LogSettings{}
+ settings.SetDefaults()
+ return settings
+}
+
+func (s *LogSettings) SetDefaults() {
+ if s.EnableConsole == nil {
+ s.EnableConsole = NewBool(true)
+ }
+
+ if s.ConsoleLevel == nil {
+ s.ConsoleLevel = NewString("DEBUG")
+ }
+
+ if s.EnableColor == nil {
+ s.EnableColor = NewBool(false)
+ }
+
+ if s.EnableFile == nil {
+ s.EnableFile = NewBool(true)
+ }
+
+ if s.FileLevel == nil {
+ s.FileLevel = NewString("INFO")
+ }
+
+ if s.FileLocation == nil {
+ s.FileLocation = NewString("")
+ }
+
+ if s.EnableWebhookDebugging == nil {
+ s.EnableWebhookDebugging = NewBool(true)
+ }
+
+ if s.EnableDiagnostics == nil {
+ s.EnableDiagnostics = NewBool(true)
+ }
+
+ if s.EnableSentry == nil {
+ s.EnableSentry = NewBool(*s.EnableDiagnostics)
+ }
+
+ if s.ConsoleJson == nil {
+ s.ConsoleJson = NewBool(true)
+ }
+
+ if s.FileJson == nil {
+ s.FileJson = NewBool(true)
+ }
+
+ if s.AdvancedLoggingConfig == nil {
+ s.AdvancedLoggingConfig = NewString("")
+ }
+}
+
+type ExperimentalAuditSettings struct {
+ FileEnabled *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ FileName *string `access:"experimental_features,write_restrictable,cloud_restrictable"` // telemetry: none
+ FileMaxSizeMB *int `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ FileMaxAgeDays *int `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ FileMaxBackups *int `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ FileCompress *bool `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ FileMaxQueueSize *int `access:"experimental_features,write_restrictable,cloud_restrictable"`
+ AdvancedLoggingConfig *string `access:"experimental_features,write_restrictable,cloud_restrictable"`
+}
+
+func (s *ExperimentalAuditSettings) SetDefaults() {
+ if s.FileEnabled == nil {
+ s.FileEnabled = NewBool(false)
+ }
+
+ if s.FileName == nil {
+ s.FileName = NewString("")
+ }
+
+ if s.FileMaxSizeMB == nil {
+ s.FileMaxSizeMB = NewInt(100)
+ }
+
+ if s.FileMaxAgeDays == nil {
+ s.FileMaxAgeDays = NewInt(0) // no limit on age
+ }
+
+ if s.FileMaxBackups == nil { // no limit on number of backups
+ s.FileMaxBackups = NewInt(0)
+ }
+
+ if s.FileCompress == nil {
+ s.FileCompress = NewBool(false)
+ }
+
+ if s.FileMaxQueueSize == nil {
+ s.FileMaxQueueSize = NewInt(1000)
+ }
+
+ if s.AdvancedLoggingConfig == nil {
+ s.AdvancedLoggingConfig = NewString("")
+ }
+}
+
+type NotificationLogSettings struct {
+ EnableConsole *bool `access:"write_restrictable,cloud_restrictable"`
+ ConsoleLevel *string `access:"write_restrictable,cloud_restrictable"`
+ ConsoleJson *bool `access:"write_restrictable,cloud_restrictable"`
+ EnableColor *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none
+ EnableFile *bool `access:"write_restrictable,cloud_restrictable"`
+ FileLevel *string `access:"write_restrictable,cloud_restrictable"`
+ FileJson *bool `access:"write_restrictable,cloud_restrictable"`
+ FileLocation *string `access:"write_restrictable,cloud_restrictable"`
+ AdvancedLoggingConfig *string `access:"write_restrictable,cloud_restrictable"`
+}
+
+func (s *NotificationLogSettings) SetDefaults() {
+ if s.EnableConsole == nil {
+ s.EnableConsole = NewBool(true)
+ }
+
+ if s.ConsoleLevel == nil {
+ s.ConsoleLevel = NewString("DEBUG")
+ }
+
+ if s.EnableFile == nil {
+ s.EnableFile = NewBool(true)
+ }
+
+ if s.FileLevel == nil {
+ s.FileLevel = NewString("INFO")
+ }
+
+ if s.FileLocation == nil {
+ s.FileLocation = NewString("")
+ }
+
+ if s.ConsoleJson == nil {
+ s.ConsoleJson = NewBool(true)
+ }
+
+ if s.EnableColor == nil {
+ s.EnableColor = NewBool(false)
+ }
+
+ if s.FileJson == nil {
+ s.FileJson = NewBool(true)
+ }
+
+ if s.AdvancedLoggingConfig == nil {
+ s.AdvancedLoggingConfig = NewString("")
+ }
+}
+
+type PasswordSettings struct {
+ MinimumLength *int `access:"authentication_password"`
+ Lowercase *bool `access:"authentication_password"`
+ Number *bool `access:"authentication_password"`
+ Uppercase *bool `access:"authentication_password"`
+ Symbol *bool `access:"authentication_password"`
+}
+
+func (s *PasswordSettings) SetDefaults() {
+ if s.MinimumLength == nil {
+ s.MinimumLength = NewInt(10)
+ }
+
+ if s.Lowercase == nil {
+ s.Lowercase = NewBool(true)
+ }
+
+ if s.Number == nil {
+ s.Number = NewBool(true)
+ }
+
+ if s.Uppercase == nil {
+ s.Uppercase = NewBool(true)
+ }
+
+ if s.Symbol == nil {
+ s.Symbol = NewBool(true)
+ }
+}
+
+type FileSettings struct {
+ EnableFileAttachments *bool `access:"site_file_sharing_and_downloads,cloud_restrictable"`
+ EnableMobileUpload *bool `access:"site_file_sharing_and_downloads,cloud_restrictable"`
+ EnableMobileDownload *bool `access:"site_file_sharing_and_downloads,cloud_restrictable"`
+ MaxFileSize *int64 `access:"environment_file_storage,cloud_restrictable"`
+ MaxImageResolution *int64 `access:"environment_file_storage,cloud_restrictable"`
+ DriverName *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
+ Directory *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
+ EnablePublicLink *bool `access:"site_public_links,cloud_restrictable"`
+ ExtractContent *bool `access:"environment_file_storage,write_restrictable"`
+ ArchiveRecursion *bool `access:"environment_file_storage,write_restrictable"`
+ PublicLinkSalt *string `access:"site_public_links,cloud_restrictable"` // telemetry: none
+ InitialFont *string `access:"environment_file_storage,cloud_restrictable"` // telemetry: none
+ AmazonS3AccessKeyId *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ AmazonS3SecretAccessKey *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ AmazonS3Bucket *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ AmazonS3PathPrefix *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ AmazonS3Region *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ AmazonS3Endpoint *string `access:"environment_file_storage,write_restrictable,cloud_restrictable"` // telemetry: none
+ AmazonS3SSL *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
+ AmazonS3SignV2 *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
+ AmazonS3SSE *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
+ AmazonS3Trace *bool `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
+}
+
+func (s *FileSettings) SetDefaults(isUpdate bool) {
+ 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(100 * 1024 * 1024) // 100MB (IEC)
+ }
+
+ if s.MaxImageResolution == nil {
+ s.MaxImageResolution = NewInt64(7680 * 4320) // 8K, ~33MPX
+ }
+
+ if s.DriverName == nil {
+ s.DriverName = NewString(ImageDriverLocal)
+ }
+
+ if s.Directory == nil || *s.Directory == "" {
+ s.Directory = NewString(FileSettingsDefaultDirectory)
+ }
+
+ if s.EnablePublicLink == nil {
+ s.EnablePublicLink = NewBool(false)
+ }
+
+ if s.ExtractContent == nil {
+ s.ExtractContent = NewBool(true)
+ }
+
+ if s.ArchiveRecursion == nil {
+ s.ArchiveRecursion = NewBool(false)
+ }
+
+ if isUpdate {
+ // When updating an existing configuration, ensure link salt has been specified.
+ if s.PublicLinkSalt == nil || *s.PublicLinkSalt == "" {
+ s.PublicLinkSalt = NewString(NewRandomString(32))
+ }
+ } else {
+ // When generating a blank configuration, leave link salt empty to be generated on server start.
+ s.PublicLinkSalt = NewString("")
+ }
+
+ if s.InitialFont == nil {
+ // Defaults to "nunito-bold.ttf"
+ s.InitialFont = NewString("nunito-bold.ttf")
+ }
+
+ if s.AmazonS3AccessKeyId == nil {
+ s.AmazonS3AccessKeyId = NewString("")
+ }
+
+ if s.AmazonS3SecretAccessKey == nil {
+ s.AmazonS3SecretAccessKey = NewString("")
+ }
+
+ if s.AmazonS3Bucket == nil {
+ s.AmazonS3Bucket = NewString("")
+ }
+
+ if s.AmazonS3PathPrefix == nil {
+ s.AmazonS3PathPrefix = NewString("")
+ }
+
+ if s.AmazonS3Region == nil {
+ s.AmazonS3Region = NewString("")
+ }
+
+ if s.AmazonS3Endpoint == nil || *s.AmazonS3Endpoint == "" {
+ // Defaults to "s3.amazonaws.com"
+ s.AmazonS3Endpoint = NewString("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)
+ }
+}
+
+func (s *FileSettings) ToFileBackendSettings(enableComplianceFeature bool) filestore.FileBackendSettings {
+ if *s.DriverName == ImageDriverLocal {
+ return filestore.FileBackendSettings{
+ DriverName: *s.DriverName,
+ Directory: *s.Directory,
+ }
+ }
+ return filestore.FileBackendSettings{
+ DriverName: *s.DriverName,
+ AmazonS3AccessKeyId: *s.AmazonS3AccessKeyId,
+ AmazonS3SecretAccessKey: *s.AmazonS3SecretAccessKey,
+ AmazonS3Bucket: *s.AmazonS3Bucket,
+ AmazonS3PathPrefix: *s.AmazonS3PathPrefix,
+ AmazonS3Region: *s.AmazonS3Region,
+ AmazonS3Endpoint: *s.AmazonS3Endpoint,
+ AmazonS3SSL: s.AmazonS3SSL == nil || *s.AmazonS3SSL,
+ AmazonS3SignV2: s.AmazonS3SignV2 != nil && *s.AmazonS3SignV2,
+ AmazonS3SSE: s.AmazonS3SSE != nil && *s.AmazonS3SSE && enableComplianceFeature,
+ AmazonS3Trace: s.AmazonS3Trace != nil && *s.AmazonS3Trace,
+ }
+}
+
+type EmailSettings struct {
+ EnableSignUpWithEmail *bool `access:"authentication_email"`
+ EnableSignInWithEmail *bool `access:"authentication_email"`
+ EnableSignInWithUsername *bool `access:"authentication_email"`
+ SendEmailNotifications *bool `access:"site_notifications"`
+ UseChannelInEmailNotifications *bool `access:"experimental_features"`
+ RequireEmailVerification *bool `access:"authentication_email"`
+ FeedbackName *string `access:"site_notifications"`
+ FeedbackEmail *string `access:"site_notifications,cloud_restrictable"`
+ ReplyToAddress *string `access:"site_notifications,cloud_restrictable"`
+ FeedbackOrganization *string `access:"site_notifications"`
+ EnableSMTPAuth *bool `access:"environment_smtp,write_restrictable,cloud_restrictable"`
+ SMTPUsername *string `access:"environment_smtp,write_restrictable,cloud_restrictable"` // telemetry: none
+ SMTPPassword *string `access:"environment_smtp,write_restrictable,cloud_restrictable"` // telemetry: none
+ SMTPServer *string `access:"environment_smtp,write_restrictable,cloud_restrictable"` // telemetry: none
+ SMTPPort *string `access:"environment_smtp,write_restrictable,cloud_restrictable"` // telemetry: none
+ SMTPServerTimeout *int `access:"cloud_restrictable"`
+ ConnectionSecurity *string `access:"environment_smtp,write_restrictable,cloud_restrictable"`
+ SendPushNotifications *bool `access:"environment_push_notification_server"`
+ PushNotificationServer *string `access:"environment_push_notification_server"` // telemetry: none
+ PushNotificationContents *string `access:"site_notifications"`
+ PushNotificationBuffer *int // telemetry: none
+ EnableEmailBatching *bool `access:"site_notifications"`
+ EmailBatchingBufferSize *int `access:"experimental_features"`
+ EmailBatchingInterval *int `access:"experimental_features"`
+ EnablePreviewModeBanner *bool `access:"site_notifications"`
+ SkipServerCertificateVerification *bool `access:"environment_smtp,write_restrictable,cloud_restrictable"`
+ EmailNotificationContentsType *string `access:"site_notifications"`
+ LoginButtonColor *string `access:"experimental_features"`
+ LoginButtonBorderColor *string `access:"experimental_features"`
+ LoginButtonTextColor *string `access:"experimental_features"`
+}
+
+func (s *EmailSettings) SetDefaults(isUpdate bool) {
+ if s.EnableSignUpWithEmail == nil {
+ s.EnableSignUpWithEmail = NewBool(true)
+ }
+
+ if s.EnableSignInWithEmail == nil {
+ s.EnableSignInWithEmail = NewBool(*s.EnableSignUpWithEmail)
+ }
+
+ if s.EnableSignInWithUsername == nil {
+ s.EnableSignInWithUsername = NewBool(true)
+ }
+
+ if s.SendEmailNotifications == nil {
+ s.SendEmailNotifications = NewBool(true)
+ }
+
+ if s.UseChannelInEmailNotifications == nil {
+ s.UseChannelInEmailNotifications = NewBool(false)
+ }
+
+ if s.RequireEmailVerification == nil {
+ s.RequireEmailVerification = NewBool(false)
+ }
+
+ if s.FeedbackName == nil {
+ s.FeedbackName = NewString("")
+ }
+
+ if s.FeedbackEmail == nil {
+ s.FeedbackEmail = NewString("test@example.com")
+ }
+
+ if s.ReplyToAddress == nil {
+ s.ReplyToAddress = NewString("test@example.com")
+ }
+
+ if s.FeedbackOrganization == nil {
+ s.FeedbackOrganization = NewString(EmailSettingsDefaultFeedbackOrganization)
+ }
+
+ if s.EnableSMTPAuth == nil {
+ if s.ConnectionSecurity == nil || *s.ConnectionSecurity == ConnSecurityNone {
+ s.EnableSMTPAuth = NewBool(false)
+ } else {
+ s.EnableSMTPAuth = NewBool(true)
+ }
+ }
+
+ if s.SMTPUsername == nil {
+ s.SMTPUsername = NewString("")
+ }
+
+ if s.SMTPPassword == nil {
+ s.SMTPPassword = NewString("")
+ }
+
+ if s.SMTPServer == nil || *s.SMTPServer == "" {
+ s.SMTPServer = NewString("localhost")
+ }
+
+ if s.SMTPPort == nil || *s.SMTPPort == "" {
+ s.SMTPPort = NewString("10025")
+ }
+
+ if s.SMTPServerTimeout == nil || *s.SMTPServerTimeout == 0 {
+ s.SMTPServerTimeout = NewInt(10)
+ }
+
+ if s.ConnectionSecurity == nil || *s.ConnectionSecurity == ConnSecurityPlain {
+ s.ConnectionSecurity = NewString(ConnSecurityNone)
+ }
+
+ if s.SendPushNotifications == nil {
+ s.SendPushNotifications = NewBool(!isUpdate)
+ }
+
+ if s.PushNotificationServer == nil {
+ if isUpdate {
+ s.PushNotificationServer = NewString("")
+ } else {
+ s.PushNotificationServer = NewString(GenericNotificationServer)
+ }
+ }
+
+ if s.PushNotificationContents == nil {
+ s.PushNotificationContents = NewString(FullNotification)
+ }
+
+ if s.PushNotificationBuffer == nil {
+ s.PushNotificationBuffer = NewInt(1000)
+ }
+
+ if s.EnableEmailBatching == nil {
+ s.EnableEmailBatching = NewBool(false)
+ }
+
+ if s.EmailBatchingBufferSize == nil {
+ s.EmailBatchingBufferSize = NewInt(EmailBatchingBufferSize)
+ }
+
+ if s.EmailBatchingInterval == nil {
+ s.EmailBatchingInterval = NewInt(EmailBatchingInterval)
+ }
+
+ if s.EnablePreviewModeBanner == nil {
+ s.EnablePreviewModeBanner = NewBool(true)
+ }
+
+ if s.EnableSMTPAuth == nil {
+ if *s.ConnectionSecurity == ConnSecurityNone {
+ s.EnableSMTPAuth = NewBool(false)
+ } else {
+ s.EnableSMTPAuth = NewBool(true)
+ }
+ }
+
+ if *s.ConnectionSecurity == ConnSecurityPlain {
+ *s.ConnectionSecurity = ConnSecurityNone
+ }
+
+ if s.SkipServerCertificateVerification == nil {
+ s.SkipServerCertificateVerification = NewBool(false)
+ }
+
+ if s.EmailNotificationContentsType == nil {
+ s.EmailNotificationContentsType = NewString(EmailNotificationContentsFull)
+ }
+
+ 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 `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+ PerSec *int `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+ MaxBurst *int `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+ MemoryStoreSize *int `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+ VaryByRemoteAddr *bool `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+ VaryByUser *bool `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+ VaryByHeader string `access:"environment_rate_limiting,write_restrictable,cloud_restrictable"`
+}
+
+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 `access:"site_users_and_teams"`
+ ShowFullName *bool `access:"site_users_and_teams"`
+}
+
+func (s *PrivacySettings) setDefaults() {
+ if s.ShowEmailAddress == nil {
+ s.ShowEmailAddress = NewBool(true)
+ }
+
+ if s.ShowFullName == nil {
+ s.ShowFullName = NewBool(true)
+ }
+}
+
+type SupportSettings struct {
+ TermsOfServiceLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ PrivacyPolicyLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ AboutLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ HelpLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ ReportAProblemLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ SupportEmail *string `access:"site_customization"`
+ CustomTermsOfServiceEnabled *bool `access:"compliance_custom_terms_of_service"`
+ CustomTermsOfServiceReAcceptancePeriod *int `access:"compliance_custom_terms_of_service"`
+ EnableAskCommunityLink *bool `access:"site_customization"`
+}
+
+func (s *SupportSettings) SetDefaults() {
+ if !isSafeLink(s.TermsOfServiceLink) {
+ *s.TermsOfServiceLink = SupportSettingsDefaultTermsOfServiceLink
+ }
+
+ if s.TermsOfServiceLink == nil {
+ s.TermsOfServiceLink = NewString(SupportSettingsDefaultTermsOfServiceLink)
+ }
+
+ if !isSafeLink(s.PrivacyPolicyLink) {
+ *s.PrivacyPolicyLink = ""
+ }
+
+ if s.PrivacyPolicyLink == nil {
+ s.PrivacyPolicyLink = NewString(SupportSettingsDefaultPrivacyPolicyLink)
+ }
+
+ if !isSafeLink(s.AboutLink) {
+ *s.AboutLink = ""
+ }
+
+ if s.AboutLink == nil {
+ s.AboutLink = NewString(SupportSettingsDefaultAboutLink)
+ }
+
+ if !isSafeLink(s.HelpLink) {
+ *s.HelpLink = ""
+ }
+
+ if s.HelpLink == nil {
+ s.HelpLink = NewString(SupportSettingsDefaultHelpLink)
+ }
+
+ if !isSafeLink(s.ReportAProblemLink) {
+ *s.ReportAProblemLink = ""
+ }
+
+ if s.ReportAProblemLink == nil {
+ s.ReportAProblemLink = NewString(SupportSettingsDefaultReportAProblemLink)
+ }
+
+ if s.SupportEmail == nil {
+ s.SupportEmail = NewString(SupportSettingsDefaultSupportEmail)
+ }
+
+ if s.CustomTermsOfServiceEnabled == nil {
+ s.CustomTermsOfServiceEnabled = NewBool(false)
+ }
+
+ if s.CustomTermsOfServiceReAcceptancePeriod == nil {
+ s.CustomTermsOfServiceReAcceptancePeriod = NewInt(SupportSettingsDefaultReAcceptancePeriod)
+ }
+
+ if s.EnableAskCommunityLink == nil {
+ s.EnableAskCommunityLink = NewBool(true)
+ }
+}
+
+type AnnouncementSettings struct {
+ EnableBanner *bool `access:"site_announcement_banner"`
+ BannerText *string `access:"site_announcement_banner"` // telemetry: none
+ BannerColor *string `access:"site_announcement_banner"`
+ BannerTextColor *string `access:"site_announcement_banner"`
+ AllowBannerDismissal *bool `access:"site_announcement_banner"`
+ AdminNoticesEnabled *bool `access:"site_notices"`
+ UserNoticesEnabled *bool `access:"site_notices"`
+ NoticesURL *string `access:"site_notices,write_restrictable"` // telemetry: none
+ NoticesFetchFrequency *int `access:"site_notices,write_restrictable"` // telemetry: none
+ NoticesSkipCache *bool `access:"site_notices,write_restrictable"` // telemetry: none
+}
+
+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(AnnouncementSettingsDefaultBannerColor)
+ }
+
+ if s.BannerTextColor == nil {
+ s.BannerTextColor = NewString(AnnouncementSettingsDefaultBannerTextColor)
+ }
+
+ if s.AllowBannerDismissal == nil {
+ s.AllowBannerDismissal = NewBool(true)
+ }
+
+ if s.AdminNoticesEnabled == nil {
+ s.AdminNoticesEnabled = NewBool(true)
+ }
+
+ if s.UserNoticesEnabled == nil {
+ s.UserNoticesEnabled = NewBool(true)
+ }
+ if s.NoticesURL == nil {
+ s.NoticesURL = NewString(AnnouncementSettingsDefaultNoticesJsonURL)
+ }
+ if s.NoticesSkipCache == nil {
+ s.NoticesSkipCache = NewBool(false)
+ }
+ if s.NoticesFetchFrequency == nil {
+ s.NoticesFetchFrequency = NewInt(AnnouncementSettingsDefaultNoticesFetchFrequencySeconds)
+ }
+
+}
+
+type ThemeSettings struct {
+ EnableThemeSelection *bool `access:"experimental_features"`
+ DefaultTheme *string `access:"experimental_features"`
+ AllowCustomThemes *bool `access:"experimental_features"`
+ AllowedThemes []string
+}
+
+func (s *ThemeSettings) SetDefaults() {
+ if s.EnableThemeSelection == nil {
+ s.EnableThemeSelection = NewBool(true)
+ }
+
+ if s.DefaultTheme == nil {
+ s.DefaultTheme = NewString(TeamSettingsDefaultTeamText)
+ }
+
+ if s.AllowCustomThemes == nil {
+ s.AllowCustomThemes = NewBool(true)
+ }
+
+ if s.AllowedThemes == nil {
+ s.AllowedThemes = []string{}
+ }
+}
+
+type TeamSettings struct {
+ SiteName *string `access:"site_customization"`
+ MaxUsersPerTeam *int `access:"site_users_and_teams"`
+ EnableUserCreation *bool `access:"authentication_signup"`
+ EnableOpenServer *bool `access:"authentication_signup"`
+ EnableUserDeactivation *bool `access:"experimental_features"`
+ RestrictCreationToDomains *string `access:"authentication_signup"` // telemetry: none
+ EnableCustomUserStatuses *bool `access:"site_users_and_teams"`
+ EnableCustomBrand *bool `access:"site_customization"`
+ CustomBrandText *string `access:"site_customization"`
+ CustomDescriptionText *string `access:"site_customization"`
+ RestrictDirectMessage *string `access:"site_users_and_teams"`
+ UserStatusAwayTimeout *int64 `access:"experimental_features"`
+ MaxChannelsPerTeam *int64 `access:"site_users_and_teams"`
+ MaxNotificationsPerChannel *int64 `access:"environment_push_notification_server"`
+ EnableConfirmNotificationsToChannel *bool `access:"site_notifications"`
+ TeammateNameDisplay *string `access:"site_users_and_teams"`
+ ExperimentalViewArchivedChannels *bool `access:"experimental_features,site_users_and_teams"`
+ ExperimentalEnableAutomaticReplies *bool `access:"experimental_features"`
+ LockTeammateNameDisplay *bool `access:"site_users_and_teams"`
+ ExperimentalPrimaryTeam *string `access:"experimental_features"`
+ ExperimentalDefaultChannels []string `access:"experimental_features"`
+}
+
+func (s *TeamSettings) SetDefaults() {
+
+ if s.SiteName == nil || *s.SiteName == "" {
+ s.SiteName = NewString(TeamSettingsDefaultSiteName)
+ }
+
+ if s.MaxUsersPerTeam == nil {
+ s.MaxUsersPerTeam = NewInt(TeamSettingsDefaultMaxUsersPerTeam)
+ }
+
+ if s.EnableUserCreation == nil {
+ s.EnableUserCreation = NewBool(true)
+ }
+
+ if s.EnableOpenServer == nil {
+ s.EnableOpenServer = NewBool(false)
+ }
+
+ if s.RestrictCreationToDomains == nil {
+ s.RestrictCreationToDomains = NewString("")
+ }
+
+ if s.EnableCustomUserStatuses == nil {
+ s.EnableCustomUserStatuses = NewBool(true)
+ }
+
+ if s.EnableCustomBrand == nil {
+ s.EnableCustomBrand = NewBool(false)
+ }
+
+ if s.EnableUserDeactivation == nil {
+ s.EnableUserDeactivation = NewBool(false)
+ }
+
+ if s.CustomBrandText == nil {
+ s.CustomBrandText = NewString(TeamSettingsDefaultCustomBrandText)
+ }
+
+ if s.CustomDescriptionText == nil {
+ s.CustomDescriptionText = NewString(TeamSettingsDefaultCustomDescriptionText)
+ }
+
+ if s.RestrictDirectMessage == nil {
+ s.RestrictDirectMessage = NewString(DirectMessageAny)
+ }
+
+ if s.UserStatusAwayTimeout == nil {
+ s.UserStatusAwayTimeout = NewInt64(TeamSettingsDefaultUserStatusAwayTimeout)
+ }
+
+ 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.ExperimentalPrimaryTeam == nil {
+ s.ExperimentalPrimaryTeam = NewString("")
+ }
+
+ if s.ExperimentalDefaultChannels == nil {
+ s.ExperimentalDefaultChannels = []string{}
+ }
+
+ if s.EnableUserCreation == nil {
+ s.EnableUserCreation = NewBool(true)
+ }
+
+ if s.ExperimentalViewArchivedChannels == nil {
+ s.ExperimentalViewArchivedChannels = NewBool(true)
+ }
+
+ if s.LockTeammateNameDisplay == nil {
+ s.LockTeammateNameDisplay = NewBool(false)
+ }
+}
+
+type ClientRequirements struct {
+ AndroidLatestVersion string `access:"write_restrictable,cloud_restrictable"`
+ AndroidMinVersion string `access:"write_restrictable,cloud_restrictable"`
+ DesktopLatestVersion string `access:"write_restrictable,cloud_restrictable"`
+ DesktopMinVersion string `access:"write_restrictable,cloud_restrictable"`
+ IosLatestVersion string `access:"write_restrictable,cloud_restrictable"`
+ IosMinVersion string `access:"write_restrictable,cloud_restrictable"`
+}
+
+type LdapSettings struct {
+ // Basic
+ Enable *bool `access:"authentication_ldap"`
+ EnableSync *bool `access:"authentication_ldap"`
+ LdapServer *string `access:"authentication_ldap"` // telemetry: none
+ LdapPort *int `access:"authentication_ldap"` // telemetry: none
+ ConnectionSecurity *string `access:"authentication_ldap"`
+ BaseDN *string `access:"authentication_ldap"` // telemetry: none
+ BindUsername *string `access:"authentication_ldap"` // telemetry: none
+ BindPassword *string `access:"authentication_ldap"` // telemetry: none
+
+ // Filtering
+ UserFilter *string `access:"authentication_ldap"` // telemetry: none
+ GroupFilter *string `access:"authentication_ldap"`
+ GuestFilter *string `access:"authentication_ldap"`
+ EnableAdminFilter *bool
+ AdminFilter *string
+
+ // Group Mapping
+ GroupDisplayNameAttribute *string `access:"authentication_ldap"`
+ GroupIdAttribute *string `access:"authentication_ldap"`
+
+ // User Mapping
+ FirstNameAttribute *string `access:"authentication_ldap"`
+ LastNameAttribute *string `access:"authentication_ldap"`
+ EmailAttribute *string `access:"authentication_ldap"`
+ UsernameAttribute *string `access:"authentication_ldap"`
+ NicknameAttribute *string `access:"authentication_ldap"`
+ IdAttribute *string `access:"authentication_ldap"`
+ PositionAttribute *string `access:"authentication_ldap"`
+ LoginIdAttribute *string `access:"authentication_ldap"`
+ PictureAttribute *string `access:"authentication_ldap"`
+
+ // Synchronization
+ SyncIntervalMinutes *int `access:"authentication_ldap"`
+
+ // Advanced
+ SkipCertificateVerification *bool `access:"authentication_ldap"`
+ PublicCertificateFile *string `access:"authentication_ldap"`
+ PrivateKeyFile *string `access:"authentication_ldap"`
+ QueryTimeout *int `access:"authentication_ldap"`
+ MaxPageSize *int `access:"authentication_ldap"`
+
+ // Customization
+ LoginFieldName *string `access:"authentication_ldap"`
+
+ LoginButtonColor *string `access:"experimental_features"`
+ LoginButtonBorderColor *string `access:"experimental_features"`
+ LoginButtonTextColor *string `access:"experimental_features"`
+
+ Trace *bool `access:"authentication_ldap"` // telemetry: none
+}
+
+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.EnableAdminFilter == nil {
+ s.EnableAdminFilter = NewBool(false)
+ }
+
+ if s.LdapServer == nil {
+ s.LdapServer = NewString("")
+ }
+
+ if s.LdapPort == nil {
+ s.LdapPort = NewInt(389)
+ }
+
+ if s.ConnectionSecurity == nil {
+ s.ConnectionSecurity = NewString("")
+ }
+
+ if s.PublicCertificateFile == nil {
+ s.PublicCertificateFile = NewString("")
+ }
+
+ if s.PrivateKeyFile == nil {
+ s.PrivateKeyFile = 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.GuestFilter == nil {
+ s.GuestFilter = NewString("")
+ }
+
+ if s.AdminFilter == nil {
+ s.AdminFilter = NewString("")
+ }
+
+ if s.GroupFilter == nil {
+ s.GroupFilter = NewString("")
+ }
+
+ if s.GroupDisplayNameAttribute == nil {
+ s.GroupDisplayNameAttribute = NewString(LdapSettingsDefaultGroupDisplayNameAttribute)
+ }
+
+ if s.GroupIdAttribute == nil {
+ s.GroupIdAttribute = NewString(LdapSettingsDefaultGroupIdAttribute)
+ }
+
+ if s.FirstNameAttribute == nil {
+ s.FirstNameAttribute = NewString(LdapSettingsDefaultFirstNameAttribute)
+ }
+
+ if s.LastNameAttribute == nil {
+ s.LastNameAttribute = NewString(LdapSettingsDefaultLastNameAttribute)
+ }
+
+ if s.EmailAttribute == nil {
+ s.EmailAttribute = NewString(LdapSettingsDefaultEmailAttribute)
+ }
+
+ if s.UsernameAttribute == nil {
+ s.UsernameAttribute = NewString(LdapSettingsDefaultUsernameAttribute)
+ }
+
+ if s.NicknameAttribute == nil {
+ s.NicknameAttribute = NewString(LdapSettingsDefaultNicknameAttribute)
+ }
+
+ if s.IdAttribute == nil {
+ s.IdAttribute = NewString(LdapSettingsDefaultIdAttribute)
+ }
+
+ if s.PositionAttribute == nil {
+ s.PositionAttribute = NewString(LdapSettingsDefaultPositionAttribute)
+ }
+
+ if s.PictureAttribute == nil {
+ s.PictureAttribute = NewString(LdapSettingsDefaultPictureAttribute)
+ }
+
+ // 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(LdapSettingsDefaultLoginFieldName)
+ }
+
+ if s.LoginButtonColor == nil {
+ s.LoginButtonColor = NewString("#0000")
+ }
+
+ if s.LoginButtonBorderColor == nil {
+ s.LoginButtonBorderColor = NewString("#2389D7")
+ }
+
+ if s.LoginButtonTextColor == nil {
+ s.LoginButtonTextColor = NewString("#2389D7")
+ }
+
+ if s.Trace == nil {
+ s.Trace = NewBool(false)
+ }
+}
+
+type ComplianceSettings struct {
+ Enable *bool `access:"compliance_compliance_monitoring"`
+ Directory *string `access:"compliance_compliance_monitoring"` // telemetry: none
+ EnableDaily *bool `access:"compliance_compliance_monitoring"`
+ BatchSize *int `access:"compliance_compliance_monitoring"` // telemetry: none
+}
+
+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)
+ }
+
+ if s.BatchSize == nil {
+ s.BatchSize = NewInt(30000)
+ }
+}
+
+type LocalizationSettings struct {
+ DefaultServerLocale *string `access:"site_localization"`
+ DefaultClientLocale *string `access:"site_localization"`
+ AvailableLocales *string `access:"site_localization"`
+}
+
+func (s *LocalizationSettings) SetDefaults() {
+ if s.DefaultServerLocale == nil {
+ s.DefaultServerLocale = NewString(DefaultLocale)
+ }
+
+ if s.DefaultClientLocale == nil {
+ s.DefaultClientLocale = NewString(DefaultLocale)
+ }
+
+ if s.AvailableLocales == nil {
+ s.AvailableLocales = NewString("")
+ }
+}
+
+type SamlSettings struct {
+ // Basic
+ Enable *bool `access:"authentication_saml"`
+ EnableSyncWithLdap *bool `access:"authentication_saml"`
+ EnableSyncWithLdapIncludeAuth *bool `access:"authentication_saml"`
+ IgnoreGuestsLdapSync *bool `access:"authentication_saml"`
+
+ Verify *bool `access:"authentication_saml"`
+ Encrypt *bool `access:"authentication_saml"`
+ SignRequest *bool `access:"authentication_saml"`
+
+ IdpURL *string `access:"authentication_saml"` // telemetry: none
+ IdpDescriptorURL *string `access:"authentication_saml"` // telemetry: none
+ IdpMetadataURL *string `access:"authentication_saml"` // telemetry: none
+ ServiceProviderIdentifier *string `access:"authentication_saml"` // telemetry: none
+ AssertionConsumerServiceURL *string `access:"authentication_saml"` // telemetry: none
+
+ SignatureAlgorithm *string `access:"authentication_saml"`
+ CanonicalAlgorithm *string `access:"authentication_saml"`
+
+ ScopingIDPProviderId *string `access:"authentication_saml"`
+ ScopingIDPName *string `access:"authentication_saml"`
+
+ IdpCertificateFile *string `access:"authentication_saml"` // telemetry: none
+ PublicCertificateFile *string `access:"authentication_saml"` // telemetry: none
+ PrivateKeyFile *string `access:"authentication_saml"` // telemetry: none
+
+ // User Mapping
+ IdAttribute *string `access:"authentication_saml"`
+ GuestAttribute *string `access:"authentication_saml"`
+ EnableAdminAttribute *bool
+ AdminAttribute *string
+ FirstNameAttribute *string `access:"authentication_saml"`
+ LastNameAttribute *string `access:"authentication_saml"`
+ EmailAttribute *string `access:"authentication_saml"`
+ UsernameAttribute *string `access:"authentication_saml"`
+ NicknameAttribute *string `access:"authentication_saml"`
+ LocaleAttribute *string `access:"authentication_saml"`
+ PositionAttribute *string `access:"authentication_saml"`
+
+ LoginButtonText *string `access:"authentication_saml"`
+
+ LoginButtonColor *string `access:"experimental_features"`
+ LoginButtonBorderColor *string `access:"experimental_features"`
+ LoginButtonTextColor *string `access:"experimental_features"`
+}
+
+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.IgnoreGuestsLdapSync == nil {
+ s.IgnoreGuestsLdapSync = NewBool(false)
+ }
+
+ if s.EnableAdminAttribute == nil {
+ s.EnableAdminAttribute = NewBool(false)
+ }
+
+ if s.Verify == nil {
+ s.Verify = NewBool(true)
+ }
+
+ if s.Encrypt == nil {
+ s.Encrypt = NewBool(true)
+ }
+
+ if s.SignRequest == nil {
+ s.SignRequest = NewBool(false)
+ }
+
+ if s.SignatureAlgorithm == nil {
+ s.SignatureAlgorithm = NewString(SamlSettingsDefaultSignatureAlgorithm)
+ }
+
+ if s.CanonicalAlgorithm == nil {
+ s.CanonicalAlgorithm = NewString(SamlSettingsDefaultCanonicalAlgorithm)
+ }
+
+ if s.IdpURL == nil {
+ s.IdpURL = NewString("")
+ }
+
+ if s.IdpDescriptorURL == nil {
+ s.IdpDescriptorURL = NewString("")
+ }
+
+ if s.ServiceProviderIdentifier == nil {
+ if s.IdpDescriptorURL != nil {
+ s.ServiceProviderIdentifier = NewString(*s.IdpDescriptorURL)
+ } else {
+ s.ServiceProviderIdentifier = NewString("")
+ }
+ }
+
+ if s.IdpMetadataURL == nil {
+ s.IdpMetadataURL = 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(UserAuthServiceSamlText)
+ }
+
+ if s.IdAttribute == nil {
+ s.IdAttribute = NewString(SamlSettingsDefaultIdAttribute)
+ }
+
+ if s.GuestAttribute == nil {
+ s.GuestAttribute = NewString(SamlSettingsDefaultGuestAttribute)
+ }
+ if s.AdminAttribute == nil {
+ s.AdminAttribute = NewString(SamlSettingsDefaultAdminAttribute)
+ }
+ if s.FirstNameAttribute == nil {
+ s.FirstNameAttribute = NewString(SamlSettingsDefaultFirstNameAttribute)
+ }
+
+ if s.LastNameAttribute == nil {
+ s.LastNameAttribute = NewString(SamlSettingsDefaultLastNameAttribute)
+ }
+
+ if s.EmailAttribute == nil {
+ s.EmailAttribute = NewString(SamlSettingsDefaultEmailAttribute)
+ }
+
+ if s.UsernameAttribute == nil {
+ s.UsernameAttribute = NewString(SamlSettingsDefaultUsernameAttribute)
+ }
+
+ if s.NicknameAttribute == nil {
+ s.NicknameAttribute = NewString(SamlSettingsDefaultNicknameAttribute)
+ }
+
+ if s.PositionAttribute == nil {
+ s.PositionAttribute = NewString(SamlSettingsDefaultPositionAttribute)
+ }
+
+ if s.LocaleAttribute == nil {
+ s.LocaleAttribute = NewString(SamlSettingsDefaultLocaleAttribute)
+ }
+
+ 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 {
+ AppCustomURLSchemes []string `access:"site_customization,write_restrictable,cloud_restrictable"` // telemetry: none
+ AppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ AndroidAppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+ IosAppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
+}
+
+func (s *NativeAppSettings) SetDefaults() {
+ if s.AppDownloadLink == nil {
+ s.AppDownloadLink = NewString(NativeappSettingsDefaultAppDownloadLink)
+ }
+
+ if s.AndroidAppDownloadLink == nil {
+ s.AndroidAppDownloadLink = NewString(NativeappSettingsDefaultAndroidAppDownloadLink)
+ }
+
+ if s.IosAppDownloadLink == nil {
+ s.IosAppDownloadLink = NewString(NativeappSettingsDefaultIosAppDownloadLink)
+ }
+
+ if s.AppCustomURLSchemes == nil {
+ s.AppCustomURLSchemes = GetDefaultAppCustomURLSchemes()
+ }
+}
+
+type ElasticsearchSettings struct {
+ ConnectionURL *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ Username *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ Password *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ EnableIndexing *bool `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ EnableSearching *bool `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ EnableAutocomplete *bool `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ Sniff *bool `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ PostIndexReplicas *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ PostIndexShards *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ ChannelIndexReplicas *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ ChannelIndexShards *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ UserIndexReplicas *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ UserIndexShards *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ AggregatePostsAfterDays *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"` // telemetry: none
+ PostsAggregatorJobStartTime *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"` // telemetry: none
+ IndexPrefix *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ LiveIndexingBatchSize *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ BulkIndexingTimeWindowSeconds *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ RequestTimeoutSeconds *int `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ SkipTLSVerification *bool `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+ Trace *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
+}
+
+func (s *ElasticsearchSettings) SetDefaults() {
+ if s.ConnectionURL == nil {
+ s.ConnectionURL = NewString(ElasticsearchSettingsDefaultConnectionURL)
+ }
+
+ if s.Username == nil {
+ s.Username = NewString(ElasticsearchSettingsDefaultUsername)
+ }
+
+ if s.Password == nil {
+ s.Password = NewString(ElasticsearchSettingsDefaultPassword)
+ }
+
+ if s.EnableIndexing == nil {
+ s.EnableIndexing = NewBool(false)
+ }
+
+ if s.EnableSearching == nil {
+ s.EnableSearching = NewBool(false)
+ }
+
+ if s.EnableAutocomplete == nil {
+ s.EnableAutocomplete = NewBool(false)
+ }
+
+ if s.Sniff == nil {
+ s.Sniff = NewBool(true)
+ }
+
+ if s.PostIndexReplicas == nil {
+ s.PostIndexReplicas = NewInt(ElasticsearchSettingsDefaultPostIndexReplicas)
+ }
+
+ if s.PostIndexShards == nil {
+ s.PostIndexShards = NewInt(ElasticsearchSettingsDefaultPostIndexShards)
+ }
+
+ if s.ChannelIndexReplicas == nil {
+ s.ChannelIndexReplicas = NewInt(ElasticsearchSettingsDefaultChannelIndexReplicas)
+ }
+
+ if s.ChannelIndexShards == nil {
+ s.ChannelIndexShards = NewInt(ElasticsearchSettingsDefaultChannelIndexShards)
+ }
+
+ if s.UserIndexReplicas == nil {
+ s.UserIndexReplicas = NewInt(ElasticsearchSettingsDefaultUserIndexReplicas)
+ }
+
+ if s.UserIndexShards == nil {
+ s.UserIndexShards = NewInt(ElasticsearchSettingsDefaultUserIndexShards)
+ }
+
+ if s.AggregatePostsAfterDays == nil {
+ s.AggregatePostsAfterDays = NewInt(ElasticsearchSettingsDefaultAggregatePostsAfterDays)
+ }
+
+ if s.PostsAggregatorJobStartTime == nil {
+ s.PostsAggregatorJobStartTime = NewString(ElasticsearchSettingsDefaultPostsAggregatorJobStartTime)
+ }
+
+ if s.IndexPrefix == nil {
+ s.IndexPrefix = NewString(ElasticsearchSettingsDefaultIndexPrefix)
+ }
+
+ if s.LiveIndexingBatchSize == nil {
+ s.LiveIndexingBatchSize = NewInt(ElasticsearchSettingsDefaultLiveIndexingBatchSize)
+ }
+
+ if s.BulkIndexingTimeWindowSeconds == nil {
+ s.BulkIndexingTimeWindowSeconds = NewInt(ElasticsearchSettingsDefaultBulkIndexingTimeWindowSeconds)
+ }
+
+ if s.RequestTimeoutSeconds == nil {
+ s.RequestTimeoutSeconds = NewInt(ElasticsearchSettingsDefaultRequestTimeoutSeconds)
+ }
+
+ if s.SkipTLSVerification == nil {
+ s.SkipTLSVerification = NewBool(false)
+ }
+
+ if s.Trace == nil {
+ s.Trace = NewString("")
+ }
+}
+
+type BleveSettings struct {
+ IndexDir *string `access:"experimental_bleve"` // telemetry: none
+ EnableIndexing *bool `access:"experimental_bleve"`
+ EnableSearching *bool `access:"experimental_bleve"`
+ EnableAutocomplete *bool `access:"experimental_bleve"`
+ BulkIndexingTimeWindowSeconds *int `access:"experimental_bleve"`
+}
+
+func (bs *BleveSettings) SetDefaults() {
+ if bs.IndexDir == nil {
+ bs.IndexDir = NewString(BleveSettingsDefaultIndexDir)
+ }
+
+ if bs.EnableIndexing == nil {
+ bs.EnableIndexing = NewBool(false)
+ }
+
+ if bs.EnableSearching == nil {
+ bs.EnableSearching = NewBool(false)
+ }
+
+ if bs.EnableAutocomplete == nil {
+ bs.EnableAutocomplete = NewBool(false)
+ }
+
+ if bs.BulkIndexingTimeWindowSeconds == nil {
+ bs.BulkIndexingTimeWindowSeconds = NewInt(BleveSettingsDefaultBulkIndexingTimeWindowSeconds)
+ }
+}
+
+type DataRetentionSettings struct {
+ EnableMessageDeletion *bool `access:"compliance_data_retention_policy"`
+ EnableFileDeletion *bool `access:"compliance_data_retention_policy"`
+ MessageRetentionDays *int `access:"compliance_data_retention_policy"`
+ FileRetentionDays *int `access:"compliance_data_retention_policy"`
+ DeletionJobStartTime *string `access:"compliance_data_retention_policy"`
+ BatchSize *int `access:"compliance_data_retention_policy"`
+}
+
+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(DataRetentionSettingsDefaultMessageRetentionDays)
+ }
+
+ if s.FileRetentionDays == nil {
+ s.FileRetentionDays = NewInt(DataRetentionSettingsDefaultFileRetentionDays)
+ }
+
+ if s.DeletionJobStartTime == nil {
+ s.DeletionJobStartTime = NewString(DataRetentionSettingsDefaultDeletionJobStartTime)
+ }
+
+ if s.BatchSize == nil {
+ s.BatchSize = NewInt(DataRetentionSettingsDefaultBatchSize)
+ }
+}
+
+type JobSettings struct {
+ RunJobs *bool `access:"write_restrictable,cloud_restrictable"`
+ RunScheduler *bool `access:"write_restrictable,cloud_restrictable"`
+}
+
+func (s *JobSettings) SetDefaults() {
+ if s.RunJobs == nil {
+ s.RunJobs = NewBool(true)
+ }
+
+ if s.RunScheduler == nil {
+ s.RunScheduler = NewBool(true)
+ }
+}
+
+type CloudSettings struct {
+ CWSURL *string `access:"write_restrictable"`
+ CWSAPIURL *string `access:"write_restrictable"`
+}
+
+func (s *CloudSettings) SetDefaults() {
+ if s.CWSURL == nil {
+ s.CWSURL = NewString(CloudSettingsDefaultCwsURL)
+ }
+ if s.CWSAPIURL == nil {
+ s.CWSAPIURL = NewString(CloudSettingsDefaultCwsAPIURL)
+ }
+}
+
+type PluginState struct {
+ Enable bool
+}
+
+type PluginSettings struct {
+ Enable *bool `access:"plugins,write_restrictable"`
+ EnableUploads *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ AllowInsecureDownloadURL *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ EnableHealthCheck *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ Directory *string `access:"plugins,write_restrictable,cloud_restrictable"` // telemetry: none
+ ClientDirectory *string `access:"plugins,write_restrictable,cloud_restrictable"` // telemetry: none
+ Plugins map[string]map[string]interface{} `access:"plugins"` // telemetry: none
+ PluginStates map[string]*PluginState `access:"plugins"` // telemetry: none
+ EnableMarketplace *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ EnableRemoteMarketplace *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ AutomaticPrepackagedPlugins *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ RequirePluginSignature *bool `access:"plugins,write_restrictable,cloud_restrictable"`
+ MarketplaceURL *string `access:"plugins,write_restrictable,cloud_restrictable"`
+ SignaturePublicKeyFiles []string `access:"plugins,write_restrictable,cloud_restrictable"`
+ ChimeraOAuthProxyURL *string `access:"plugins,write_restrictable,cloud_restrictable"`
+}
+
+func (s *PluginSettings) SetDefaults(ls LogSettings) {
+ if s.Enable == nil {
+ s.Enable = NewBool(true)
+ }
+
+ if s.EnableUploads == nil {
+ s.EnableUploads = NewBool(false)
+ }
+
+ if s.AllowInsecureDownloadURL == nil {
+ s.AllowInsecureDownloadURL = NewBool(false)
+ }
+
+ if s.EnableHealthCheck == nil {
+ s.EnableHealthCheck = NewBool(true)
+ }
+
+ if s.Directory == nil || *s.Directory == "" {
+ s.Directory = NewString(PluginSettingsDefaultDirectory)
+ }
+
+ if s.ClientDirectory == nil || *s.ClientDirectory == "" {
+ s.ClientDirectory = NewString(PluginSettingsDefaultClientDirectory)
+ }
+
+ if s.Plugins == nil {
+ s.Plugins = make(map[string]map[string]interface{})
+ }
+
+ if s.PluginStates == nil {
+ s.PluginStates = make(map[string]*PluginState)
+ }
+
+ if s.PluginStates["com.mattermost.nps"] == nil {
+ // Enable the NPS plugin by default if diagnostics are enabled
+ s.PluginStates["com.mattermost.nps"] = &PluginState{Enable: ls.EnableDiagnostics == nil || *ls.EnableDiagnostics}
+ }
+
+ if s.PluginStates["playbooks"] == nil {
+ // Enable the playbooks plugin by default
+ s.PluginStates["playbooks"] = &PluginState{Enable: true}
+ }
+
+ if s.PluginStates["com.mattermost.plugin-channel-export"] == nil && BuildEnterpriseReady == "true" {
+ // Enable the channel export plugin by default
+ s.PluginStates["com.mattermost.plugin-channel-export"] = &PluginState{Enable: true}
+ }
+
+ if s.PluginStates["focalboard"] == nil {
+ // Enable the focalboard plugin by default
+ s.PluginStates["focalboard"] = &PluginState{Enable: true}
+ }
+
+ if s.EnableMarketplace == nil {
+ s.EnableMarketplace = NewBool(PluginSettingsDefaultEnableMarketplace)
+ }
+
+ if s.EnableRemoteMarketplace == nil {
+ s.EnableRemoteMarketplace = NewBool(true)
+ }
+
+ if s.AutomaticPrepackagedPlugins == nil {
+ s.AutomaticPrepackagedPlugins = NewBool(true)
+ }
+
+ if s.MarketplaceURL == nil || *s.MarketplaceURL == "" || *s.MarketplaceURL == PluginSettingsOldMarketplaceURL {
+ s.MarketplaceURL = NewString(PluginSettingsDefaultMarketplaceURL)
+ }
+
+ if s.RequirePluginSignature == nil {
+ s.RequirePluginSignature = NewBool(false)
+ }
+
+ if s.SignaturePublicKeyFiles == nil {
+ s.SignaturePublicKeyFiles = []string{}
+ }
+
+ if s.ChimeraOAuthProxyURL == nil {
+ s.ChimeraOAuthProxyURL = NewString("")
+ }
+}
+
+type GlobalRelayMessageExportSettings struct {
+ CustomerType *string `access:"compliance_compliance_export"` // must be either A9 or A10, dictates SMTP server url
+ SMTPUsername *string `access:"compliance_compliance_export"`
+ SMTPPassword *string `access:"compliance_compliance_export"`
+ EmailAddress *string `access:"compliance_compliance_export"` // the address to send messages to
+ SMTPServerTimeout *int `access:"compliance_compliance_export"`
+}
+
+func (s *GlobalRelayMessageExportSettings) SetDefaults() {
+ if s.CustomerType == nil {
+ s.CustomerType = NewString(GlobalrelayCustomerTypeA9)
+ }
+ if s.SMTPUsername == nil {
+ s.SMTPUsername = NewString("")
+ }
+ if s.SMTPPassword == nil {
+ s.SMTPPassword = NewString("")
+ }
+ if s.EmailAddress == nil {
+ s.EmailAddress = NewString("")
+ }
+ if s.SMTPServerTimeout == nil || *s.SMTPServerTimeout == 0 {
+ s.SMTPServerTimeout = NewInt(1800)
+ }
+}
+
+type MessageExportSettings struct {
+ EnableExport *bool `access:"compliance_compliance_export"`
+ ExportFormat *string `access:"compliance_compliance_export"`
+ DailyRunTime *string `access:"compliance_compliance_export"`
+ ExportFromTimestamp *int64 `access:"compliance_compliance_export"`
+ BatchSize *int `access:"compliance_compliance_export"`
+ DownloadExportResults *bool `access:"compliance_compliance_export"`
+
+ // formatter-specific settings - these are only expected to be non-nil if ExportFormat is set to the associated format
+ GlobalRelaySettings *GlobalRelayMessageExportSettings `access:"compliance_compliance_export"`
+}
+
+func (s *MessageExportSettings) SetDefaults() {
+ if s.EnableExport == nil {
+ s.EnableExport = NewBool(false)
+ }
+
+ if s.DownloadExportResults == nil {
+ s.DownloadExportResults = NewBool(false)
+ }
+
+ if s.ExportFormat == nil {
+ s.ExportFormat = NewString(ComplianceExportTypeActiance)
+ }
+
+ if s.DailyRunTime == nil {
+ s.DailyRunTime = NewString("01:00")
+ }
+
+ if s.ExportFromTimestamp == nil {
+ 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 `access:"site_customization"`
+ ExperimentalTimezone *bool `access:"experimental_features"`
+}
+
+func (s *DisplaySettings) SetDefaults() {
+ if s.CustomURLSchemes == nil {
+ customURLSchemes := []string{}
+ s.CustomURLSchemes = customURLSchemes
+ }
+
+ if s.ExperimentalTimezone == nil {
+ s.ExperimentalTimezone = NewBool(true)
+ }
+}
+
+type GuestAccountsSettings struct {
+ Enable *bool `access:"authentication_guest_access"`
+ AllowEmailAccounts *bool `access:"authentication_guest_access"`
+ EnforceMultifactorAuthentication *bool `access:"authentication_guest_access"`
+ RestrictCreationToDomains *string `access:"authentication_guest_access"`
+}
+
+func (s *GuestAccountsSettings) SetDefaults() {
+ if s.Enable == nil {
+ s.Enable = NewBool(false)
+ }
+
+ if s.AllowEmailAccounts == nil {
+ s.AllowEmailAccounts = NewBool(true)
+ }
+
+ if s.EnforceMultifactorAuthentication == nil {
+ s.EnforceMultifactorAuthentication = NewBool(false)
+ }
+
+ if s.RestrictCreationToDomains == nil {
+ s.RestrictCreationToDomains = NewString("")
+ }
+}
+
+type ImageProxySettings struct {
+ Enable *bool `access:"environment_image_proxy"`
+ ImageProxyType *string `access:"environment_image_proxy"`
+ RemoteImageProxyURL *string `access:"environment_image_proxy"`
+ RemoteImageProxyOptions *string `access:"environment_image_proxy"`
+}
+
+func (s *ImageProxySettings) SetDefaults() {
+ if s.Enable == nil {
+ s.Enable = NewBool(false)
+ }
+
+ if s.ImageProxyType == nil {
+ s.ImageProxyType = NewString(ImageProxyTypeLocal)
+ }
+
+ if s.RemoteImageProxyURL == nil {
+ s.RemoteImageProxyURL = NewString("")
+ }
+
+ if s.RemoteImageProxyOptions == nil {
+ s.RemoteImageProxyOptions = NewString("")
+ }
+}
+
+// ImportSettings defines configuration settings for file imports.
+type ImportSettings struct {
+ // The directory where to store the imported files.
+ Directory *string
+ // The number of days to retain the imported files before deleting them.
+ RetentionDays *int
+}
+
+func (s *ImportSettings) isValid() *AppError {
+ if *s.Directory == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.import.directory.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.RetentionDays <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.import.retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+// SetDefaults applies the default settings to the struct.
+func (s *ImportSettings) SetDefaults() {
+ if s.Directory == nil || *s.Directory == "" {
+ s.Directory = NewString(ImportSettingsDefaultDirectory)
+ }
+
+ if s.RetentionDays == nil {
+ s.RetentionDays = NewInt(ImportSettingsDefaultRetentionDays)
+ }
+}
+
+// ExportSettings defines configuration settings for file exports.
+type ExportSettings struct {
+ // The directory where to store the exported files.
+ Directory *string // telemetry: none
+ // The number of days to retain the exported files before deleting them.
+ RetentionDays *int
+}
+
+func (s *ExportSettings) isValid() *AppError {
+ if *s.Directory == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.export.directory.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.RetentionDays <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.export.retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+// SetDefaults applies the default settings to the struct.
+func (s *ExportSettings) SetDefaults() {
+ if s.Directory == nil || *s.Directory == "" {
+ s.Directory = NewString(ExportSettingsDefaultDirectory)
+ }
+
+ if s.RetentionDays == nil {
+ s.RetentionDays = NewInt(ExportSettingsDefaultRetentionDays)
+ }
+}
+
+type ConfigFunc func() *Config
+
+const ConfigAccessTagType = "access"
+const ConfigAccessTagWriteRestrictable = "write_restrictable"
+const ConfigAccessTagCloudRestrictable = "cloud_restrictable"
+
+// Allows read access if any PermissionSysconsoleRead* is allowed
+const ConfigAccessTagAnySysConsoleRead = "*_read"
+
+// Config fields support the 'access' tag with the following values corresponding to the suffix of the associated
+// PermissionSysconsole* permission Id: 'about', 'reporting', 'user_management_users',
+// 'user_management_groups', 'user_management_teams', 'user_management_channels',
+// 'user_management_permissions', 'environment_web_server', 'environment_database', 'environment_elasticsearch',
+// 'environment_file_storage', 'environment_image_proxy', 'environment_smtp', 'environment_push_notification_server',
+// 'environment_high_availability', 'environment_rate_limiting', 'environment_logging', 'environment_session_lengths',
+// 'environment_performance_monitoring', 'environment_developer', 'site', 'authentication', 'plugins',
+// 'integrations', 'compliance', 'plugins', and 'experimental'. They grant read and/or write access to the config field
+// to roles without PermissionManageSystem.
+//
+// The 'access' tag '*_read' checks for any Sysconsole read permission and grants access if any read permission is allowed.
+//
+// By default config values can be written with PermissionManageSystem, but if ExperimentalSettings.RestrictSystemAdmin is true
+// and the access tag contains the value 'write_restrictable', then even PermissionManageSystem, does not grant write access.
+//
+// PermissionManageSystem always grants read access.
+//
+// Config values with the access tag 'cloud_restrictable' mean that are marked to be filtered when it's used in a cloud licensed
+// environment with ExperimentalSettings.RestrictedSystemAdmin set to true.
+//
+// Example:
+// type HairSettings struct {
+// // Colour is writeable with either PermissionSysconsoleWriteReporting or PermissionSysconsoleWriteUserManagementGroups.
+// // It is readable by PermissionSysconsoleReadReporting and PermissionSysconsoleReadUserManagementGroups permissions.
+// // PermissionManageSystem grants read and write access.
+// Colour string `access:"reporting,user_management_groups"`
+//
+// // Length is only readable and writable via PermissionManageSystem.
+// Length string
+//
+// // Product is only writeable by PermissionManageSystem if ExperimentalSettings.RestrictSystemAdmin is false.
+// // PermissionManageSystem can always read the value.
+// Product bool `access:write_restrictable`
+// }
+type Config struct {
+ ServiceSettings ServiceSettings
+ TeamSettings TeamSettings
+ ClientRequirements ClientRequirements
+ SqlSettings SqlSettings
+ LogSettings LogSettings
+ ExperimentalAuditSettings ExperimentalAuditSettings
+ NotificationLogSettings NotificationLogSettings
+ PasswordSettings PasswordSettings
+ FileSettings FileSettings
+ EmailSettings EmailSettings
+ RateLimitSettings RateLimitSettings
+ PrivacySettings PrivacySettings
+ SupportSettings SupportSettings
+ AnnouncementSettings AnnouncementSettings
+ ThemeSettings ThemeSettings
+ GitLabSettings SSOSettings
+ GoogleSettings SSOSettings
+ Office365Settings Office365Settings
+ OpenIdSettings SSOSettings
+ LdapSettings LdapSettings
+ ComplianceSettings ComplianceSettings
+ LocalizationSettings LocalizationSettings
+ SamlSettings SamlSettings
+ NativeAppSettings NativeAppSettings
+ ClusterSettings ClusterSettings
+ MetricsSettings MetricsSettings
+ ExperimentalSettings ExperimentalSettings
+ AnalyticsSettings AnalyticsSettings
+ ElasticsearchSettings ElasticsearchSettings
+ BleveSettings BleveSettings
+ DataRetentionSettings DataRetentionSettings
+ MessageExportSettings MessageExportSettings
+ JobSettings JobSettings // telemetry: none
+ PluginSettings PluginSettings
+ DisplaySettings DisplaySettings
+ GuestAccountsSettings GuestAccountsSettings
+ ImageProxySettings ImageProxySettings
+ CloudSettings CloudSettings // telemetry: none
+ FeatureFlags *FeatureFlags `access:"*_read" json:",omitempty"`
+ ImportSettings ImportSettings // telemetry: none
+ ExportSettings ExportSettings
+}
+
+func (o *Config) Clone() *Config {
+ buf, err := json.Marshal(o)
+ if err != nil {
+ panic(err)
+ }
+ var ret Config
+ err = json.Unmarshal(buf, &ret)
+ if err != nil {
+ panic(err)
+ }
+ return &ret
+}
+
+func (o *Config) ToJSONFiltered(tagType, tagValue string) ([]byte, error) {
+ filteredConfigMap := structToMapFilteredByTag(*o, tagType, tagValue)
+ for key, value := range filteredConfigMap {
+ v, ok := value.(map[string]interface{})
+ if ok && len(v) == 0 {
+ delete(filteredConfigMap, key)
+ }
+ }
+ return json.Marshal(filteredConfigMap)
+}
+
+func (o *Config) GetSSOService(service string) *SSOSettings {
+ switch service {
+ case ServiceGitlab:
+ return &o.GitLabSettings
+ case ServiceGoogle:
+ return &o.GoogleSettings
+ case ServiceOffice365:
+ return o.Office365Settings.SSOSettings()
+ case ServiceOpenid:
+ return &o.OpenIdSettings
+ }
+
+ return nil
+}
+
+func ConfigFromJSON(data io.Reader) *Config {
+ var o *Config
+ json.NewDecoder(data).Decode(&o)
+ return o
+}
+
+// isUpdate detects a pre-existing config based on whether SiteURL has been changed
+func (o *Config) isUpdate() bool {
+ return o.ServiceSettings.SiteURL != nil
+}
+
+func (o *Config) SetDefaults() {
+ isUpdate := o.isUpdate()
+
+ o.LdapSettings.SetDefaults()
+ o.SamlSettings.SetDefaults()
+
+ if o.TeamSettings.TeammateNameDisplay == nil {
+ o.TeamSettings.TeammateNameDisplay = NewString(ShowUsername)
+
+ if *o.SamlSettings.Enable || *o.LdapSettings.Enable {
+ *o.TeamSettings.TeammateNameDisplay = ShowFullName
+ }
+ }
+
+ o.SqlSettings.SetDefaults(isUpdate)
+ o.FileSettings.SetDefaults(isUpdate)
+ o.EmailSettings.SetDefaults(isUpdate)
+ o.PrivacySettings.setDefaults()
+ o.Office365Settings.setDefaults()
+ o.Office365Settings.setDefaults()
+ o.GitLabSettings.setDefaults("", "", "", "", "")
+ o.GoogleSettings.setDefaults(GoogleSettingsDefaultScope, GoogleSettingsDefaultAuthEndpoint, GoogleSettingsDefaultTokenEndpoint, GoogleSettingsDefaultUserAPIEndpoint, "")
+ o.OpenIdSettings.setDefaults(OpenidSettingsDefaultScope, "", "", "", "#145DBF")
+ o.ServiceSettings.SetDefaults(isUpdate)
+ 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.LogSettings)
+ o.AnalyticsSettings.SetDefaults()
+ o.ComplianceSettings.SetDefaults()
+ o.LocalizationSettings.SetDefaults()
+ o.ElasticsearchSettings.SetDefaults()
+ o.BleveSettings.SetDefaults()
+ o.NativeAppSettings.SetDefaults()
+ o.DataRetentionSettings.SetDefaults()
+ o.RateLimitSettings.SetDefaults()
+ o.LogSettings.SetDefaults()
+ o.ExperimentalAuditSettings.SetDefaults()
+ o.NotificationLogSettings.SetDefaults()
+ o.JobSettings.SetDefaults()
+ o.MessageExportSettings.SetDefaults()
+ o.DisplaySettings.SetDefaults()
+ o.GuestAccountsSettings.SetDefaults()
+ o.ImageProxySettings.SetDefaults()
+ o.CloudSettings.SetDefaults()
+ if o.FeatureFlags == nil {
+ o.FeatureFlags = &FeatureFlags{}
+ o.FeatureFlags.SetDefaults()
+ }
+ o.ImportSettings.SetDefaults()
+ o.ExportSettings.SetDefaults()
+}
+
+func (o *Config) IsValid() *AppError {
+ if *o.ServiceSettings.SiteURL == "" && *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 *o.ServiceSettings.SiteURL == "" && *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 < PasswordMinimumLength || *o.PasswordSettings.MinimumLength > PasswordMaximumLength {
+ return NewAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PasswordMinimumLength, "MaxLength": PasswordMaximumLength}, "", http.StatusBadRequest)
+ }
+
+ if err := o.RateLimitSettings.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.BleveSettings.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(); err != nil {
+ return err
+ }
+
+ if err := o.DisplaySettings.isValid(); err != nil {
+ return err
+ }
+
+ if err := o.ImageProxySettings.isValid(); err != nil {
+ return err
+ }
+
+ if err := o.ImportSettings.isValid(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *TeamSettings) isValid() *AppError {
+ if *s.MaxUsersPerTeam <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaxChannelsPerTeam <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaxNotificationsPerChannel <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.max_notify_per_channel.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(*s.RestrictDirectMessage == DirectMessageAny || *s.RestrictDirectMessage == DirectMessageTeam) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(*s.TeammateNameDisplay == ShowFullName || *s.TeammateNameDisplay == ShowNicknameFullName || *s.TeammateNameDisplay == ShowUsername) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.teammate_name_display.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(*s.SiteName) > SitenameMaxLength {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SitenameMaxLength}, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *SqlSettings) isValid() *AppError {
+ if *s.AtRestEncryptKey != "" && len(*s.AtRestEncryptKey) < 32 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(*s.DriverName == DatabaseDriverMysql || *s.DriverName == DatabaseDriverPostgres) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_driver.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaxIdleConns <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.ConnMaxLifetimeMilliseconds < 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_conn_max_lifetime_milliseconds.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.ConnMaxIdleTimeMilliseconds < 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_conn_max_idle_time_milliseconds.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.QueryTimeout <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.DataSource == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaxOpenConns <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *FileSettings) isValid() *AppError {
+ if *s.MaxFileSize <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.max_file_size.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(*s.DriverName == ImageDriverLocal || *s.DriverName == ImageDriverS3) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.PublicLinkSalt != "" && len(*s.PublicLinkSalt) < 32 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.Directory == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.directory.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *EmailSettings) isValid() *AppError {
+ if !(*s.ConnectionSecurity == ConnSecurityNone || *s.ConnectionSecurity == ConnSecurityTLS || *s.ConnectionSecurity == ConnSecurityStarttls || *s.ConnectionSecurity == ConnSecurityPlain) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.EmailBatchingBufferSize <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.EmailBatchingInterval < 30 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(*s.EmailNotificationContentsType == EmailNotificationContentsFull || *s.EmailNotificationContentsType == EmailNotificationContentsGeneric) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.email_notification_contents_type.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *RateLimitSettings) isValid() *AppError {
+ if *s.MemoryStoreSize <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.PerSec <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.rate_sec.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaxBurst <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.max_burst.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *LdapSettings) isValid() *AppError {
+ if !(*s.ConnectionSecurity == ConnSecurityNone || *s.ConnectionSecurity == ConnSecurityTLS || *s.ConnectionSecurity == ConnSecurityStarttls) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.SyncIntervalMinutes <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaxPageSize < 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_max_page_size.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.Enable {
+ if *s.LdapServer == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.BaseDN == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.EmailAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_email", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.UsernameAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_username", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.IdAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.LoginIdAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.ldap_login_id", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.UserFilter != "" {
+ if _, err := ldap.CompileFilter(*s.UserFilter); err != nil {
+ return NewAppError("ValidateFilter", "ent.ldap.validate_filter.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ }
+
+ if *s.GuestFilter != "" {
+ if _, err := ldap.CompileFilter(*s.GuestFilter); err != nil {
+ return NewAppError("LdapSettings.isValid", "ent.ldap.validate_guest_filter.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ }
+
+ if *s.AdminFilter != "" {
+ if _, err := ldap.CompileFilter(*s.AdminFilter); err != nil {
+ return NewAppError("LdapSettings.isValid", "ent.ldap.validate_admin_filter.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (s *SamlSettings) isValid() *AppError {
+ if *s.Enable {
+ if *s.IdpURL == "" || !IsValidHTTPURL(*s.IdpURL) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.IdpDescriptorURL == "" || !IsValidHTTPURL(*s.IdpDescriptorURL) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_descriptor_url.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.IdpCertificateFile == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_idp_cert.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.EmailAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.UsernameAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_username_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.ServiceProviderIdentifier == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_spidentifier_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.Verify {
+ if *s.AssertionConsumerServiceURL == "" || !IsValidHTTPURL(*s.AssertionConsumerServiceURL) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ if *s.Encrypt {
+ if *s.PrivateKeyFile == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_private_key.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.PublicCertificateFile == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_public_cert.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ if *s.EmailAttribute == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(*s.SignatureAlgorithm == SamlSettingsSignatureAlgorithmSha1 || *s.SignatureAlgorithm == SamlSettingsSignatureAlgorithmSha256 || *s.SignatureAlgorithm == SamlSettingsSignatureAlgorithmSha512) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_signature_algorithm.app_error", nil, "", http.StatusBadRequest)
+ }
+ if !(*s.CanonicalAlgorithm == SamlSettingsCanonicalAlgorithmC14n || *s.CanonicalAlgorithm == SamlSettingsCanonicalAlgorithmC14n11) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_canonical_algorithm.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.GuestAttribute != "" {
+ if !(strings.Contains(*s.GuestAttribute, "=")) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_guest_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+ if len(strings.Split(*s.GuestAttribute, "=")) != 2 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_guest_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ if *s.AdminAttribute != "" {
+ if !(strings.Contains(*s.AdminAttribute, "=")) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_admin_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+ if len(strings.Split(*s.AdminAttribute, "=")) != 2 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.saml_admin_attribute.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (s *ServiceSettings) isValid() *AppError {
+ if !(*s.ConnectionSecurity == ConnSecurityNone || *s.ConnectionSecurity == ConnSecurityTLS) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.ConnectionSecurity == ConnSecurityTLS && !*s.UseLetsEncrypt {
+ appErr := NewAppError("Config.IsValid", "model.config.is_valid.tls_cert_file_missing.app_error", nil, "", http.StatusBadRequest)
+
+ if *s.TLSCertFile == "" {
+ return appErr
+ } else if _, err := os.Stat(*s.TLSCertFile); os.IsNotExist(err) {
+ return appErr
+ }
+
+ appErr = NewAppError("Config.IsValid", "model.config.is_valid.tls_key_file_missing.app_error", nil, "", http.StatusBadRequest)
+
+ if *s.TLSKeyFile == "" {
+ return appErr
+ } else if _, err := os.Stat(*s.TLSKeyFile); os.IsNotExist(err) {
+ return appErr
+ }
+ }
+
+ if len(s.TLSOverwriteCiphers) > 0 {
+ for _, cipher := range s.TLSOverwriteCiphers {
+ if _, ok := ServerTLSSupportedCiphers[cipher]; !ok {
+ return NewAppError("Config.IsValid", "model.config.is_valid.tls_overwrite_cipher.app_error", map[string]interface{}{"name": cipher}, "", http.StatusBadRequest)
+ }
+ }
+ }
+
+ if *s.ReadTimeout <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.WriteTimeout <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.TimeBetweenUserTypingUpdatesMilliseconds < 1000 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.MaximumLoginAttempts <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.SiteURL != "" {
+ if _, err := url.ParseRequestURI(*s.SiteURL); err != nil {
+ return NewAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ if *s.WebsocketURL != "" {
+ if _, err := url.ParseRequestURI(*s.WebsocketURL); err != nil {
+ return NewAppError("Config.IsValid", "model.config.is_valid.websocket_url.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ host, port, _ := net.SplitHostPort(*s.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 *s.ExperimentalGroupUnreadChannels != GroupUnreadChannelsDisabled &&
+ *s.ExperimentalGroupUnreadChannels != GroupUnreadChannelsDefaultOn &&
+ *s.ExperimentalGroupUnreadChannels != GroupUnreadChannelsDefaultOff {
+ return NewAppError("Config.IsValid", "model.config.is_valid.group_unread_channels.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.CollapsedThreads != CollapsedThreadsDisabled && !*s.ThreadAutoFollow {
+ return NewAppError("Config.IsValid", "model.config.is_valid.collapsed_threads.autofollow.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.CollapsedThreads != CollapsedThreadsDisabled &&
+ *s.CollapsedThreads != CollapsedThreadsDefaultOn &&
+ *s.CollapsedThreads != CollapsedThreadsDefaultOff {
+ return NewAppError("Config.IsValid", "model.config.is_valid.collapsed_threads.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *ElasticsearchSettings) isValid() *AppError {
+ if *s.EnableIndexing {
+ if *s.ConnectionURL == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.connection_url.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ if *s.EnableSearching && !*s.EnableIndexing {
+ return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.enable_searching.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.EnableAutocomplete && !*s.EnableIndexing {
+ return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.enable_autocomplete.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.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", *s.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 *s.LiveIndexingBatchSize < 1 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.live_indexing_batch_size.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.BulkIndexingTimeWindowSeconds < 1 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.bulk_indexing_time_window_seconds.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.RequestTimeoutSeconds < 1 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.request_timeout_seconds.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (bs *BleveSettings) isValid() *AppError {
+ if *bs.EnableIndexing {
+ if *bs.IndexDir == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.bleve_search.filename.app_error", nil, "", http.StatusBadRequest)
+ }
+ } else {
+ if *bs.EnableSearching {
+ return NewAppError("Config.IsValid", "model.config.is_valid.bleve_search.enable_searching.app_error", nil, "", http.StatusBadRequest)
+ }
+ if *bs.EnableAutocomplete {
+ return NewAppError("Config.IsValid", "model.config.is_valid.bleve_search.enable_autocomplete.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+ if *bs.BulkIndexingTimeWindowSeconds < 1 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.bleve_search.bulk_indexing_time_window_seconds.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (s *DataRetentionSettings) isValid() *AppError {
+ if *s.MessageRetentionDays <= 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.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", *s.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 (s *LocalizationSettings) isValid() *AppError {
+ if *s.AvailableLocales != "" {
+ if !strings.Contains(*s.AvailableLocales, *s.DefaultClientLocale) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.localization.available_locales.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+
+ return nil
+}
+
+func (s *MessageExportSettings) isValid() *AppError {
+ if s.EnableExport == nil {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.enable.app_error", nil, "", http.StatusBadRequest)
+ }
+ if *s.EnableExport {
+ if s.ExportFromTimestamp == nil || *s.ExportFromTimestamp < 0 || *s.ExportFromTimestamp > GetMillis() {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_from.app_error", nil, "", http.StatusBadRequest)
+ } else if s.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", *s.DailyRunTime); err != nil {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, err.Error(), http.StatusBadRequest)
+ } else if s.BatchSize == nil || *s.BatchSize < 0 {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.batch_size.app_error", nil, "", http.StatusBadRequest)
+ } else if s.ExportFormat == nil || (*s.ExportFormat != ComplianceExportTypeActiance && *s.ExportFormat != ComplianceExportTypeGlobalrelay && *s.ExportFormat != ComplianceExportTypeCsv) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_type.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.ExportFormat == ComplianceExportTypeGlobalrelay {
+ if s.GlobalRelaySettings == nil {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.config_missing.app_error", nil, "", http.StatusBadRequest)
+ } else if s.GlobalRelaySettings.CustomerType == nil || (*s.GlobalRelaySettings.CustomerType != GlobalrelayCustomerTypeA9 && *s.GlobalRelaySettings.CustomerType != GlobalrelayCustomerTypeA10) {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.customer_type.app_error", nil, "", http.StatusBadRequest)
+ } else if s.GlobalRelaySettings.EmailAddress == nil || !strings.Contains(*s.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 s.GlobalRelaySettings.SMTPUsername == nil || *s.GlobalRelaySettings.SMTPUsername == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.smtp_username.app_error", nil, "", http.StatusBadRequest)
+ } else if s.GlobalRelaySettings.SMTPPassword == nil || *s.GlobalRelaySettings.SMTPPassword == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay.smtp_password.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+ }
+ return nil
+}
+
+func (s *DisplaySettings) isValid() *AppError {
+ if len(s.CustomURLSchemes) != 0 {
+ validProtocolPattern := regexp.MustCompile(`(?i)^\s*[A-Za-z][A-Za-z0-9.+-]*\s*$`)
+
+ for _, scheme := range s.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 (s *ImageProxySettings) isValid() *AppError {
+ if *s.Enable {
+ switch *s.ImageProxyType {
+ case ImageProxyTypeLocal:
+ // No other settings to validate
+ case ImageProxyTypeAtmosCamo:
+ if *s.RemoteImageProxyURL == "" {
+ return NewAppError("Config.IsValid", "model.config.is_valid.atmos_camo_image_proxy_url.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if *s.RemoteImageProxyOptions == "" {
+ 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 (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 && *o.LdapSettings.BindPassword != "" {
+ *o.LdapSettings.BindPassword = FakeSetting
+ }
+
+ *o.FileSettings.PublicLinkSalt = FakeSetting
+
+ if *o.FileSettings.AmazonS3SecretAccessKey != "" {
+ *o.FileSettings.AmazonS3SecretAccessKey = FakeSetting
+ }
+
+ if o.EmailSettings.SMTPPassword != nil && *o.EmailSettings.SMTPPassword != "" {
+ *o.EmailSettings.SMTPPassword = FakeSetting
+ }
+
+ if *o.GitLabSettings.Secret != "" {
+ *o.GitLabSettings.Secret = FakeSetting
+ }
+
+ if o.GoogleSettings.Secret != nil && *o.GoogleSettings.Secret != "" {
+ *o.GoogleSettings.Secret = FakeSetting
+ }
+
+ if o.Office365Settings.Secret != nil && *o.Office365Settings.Secret != "" {
+ *o.Office365Settings.Secret = FakeSetting
+ }
+
+ if o.OpenIdSettings.Secret != nil && *o.OpenIdSettings.Secret != "" {
+ *o.OpenIdSettings.Secret = FakeSetting
+ }
+
+ *o.SqlSettings.DataSource = FakeSetting
+ *o.SqlSettings.AtRestEncryptKey = FakeSetting
+
+ *o.ElasticsearchSettings.Password = FakeSetting
+
+ for i := range o.SqlSettings.DataSourceReplicas {
+ o.SqlSettings.DataSourceReplicas[i] = FakeSetting
+ }
+
+ for i := range o.SqlSettings.DataSourceSearchReplicas {
+ o.SqlSettings.DataSourceSearchReplicas[i] = FakeSetting
+ }
+
+ if o.MessageExportSettings.GlobalRelaySettings.SMTPPassword != nil && *o.MessageExportSettings.GlobalRelaySettings.SMTPPassword != "" {
+ *o.MessageExportSettings.GlobalRelaySettings.SMTPPassword = FakeSetting
+ }
+
+ if o.ServiceSettings.GfycatAPISecret != nil && *o.ServiceSettings.GfycatAPISecret != "" {
+ *o.ServiceSettings.GfycatAPISecret = FakeSetting
+ }
+
+ *o.ServiceSettings.SplitKey = FakeSetting
+}
+
+// structToMapFilteredByTag converts a struct into a map removing those fields that has the tag passed
+// as argument
+func structToMapFilteredByTag(t interface{}, typeOfTag, filterTag string) map[string]interface{} {
+ defer func() {
+ if r := recover(); r != nil {
+ mlog.Warn("Panicked in structToMapFilteredByTag. This should never happen.", mlog.Any("recover", r))
+ }
+ }()
+
+ val := reflect.ValueOf(t)
+ elemField := reflect.TypeOf(t)
+
+ if val.Kind() != reflect.Struct {
+ return nil
+ }
+
+ out := map[string]interface{}{}
+
+ for i := 0; i < val.NumField(); i++ {
+ field := val.Field(i)
+
+ structField := elemField.Field(i)
+ tagPermissions := strings.Split(structField.Tag.Get(typeOfTag), ",")
+ if isTagPresent(filterTag, tagPermissions) {
+ continue
+ }
+
+ var value interface{}
+
+ switch field.Kind() {
+ case reflect.Struct:
+ value = structToMapFilteredByTag(field.Interface(), typeOfTag, filterTag)
+ case reflect.Ptr:
+ indirectType := field.Elem()
+ if indirectType.Kind() == reflect.Struct {
+ value = structToMapFilteredByTag(indirectType.Interface(), typeOfTag, filterTag)
+ } else if indirectType.Kind() != reflect.Invalid {
+ value = indirectType.Interface()
+ }
+ default:
+ value = field.Interface()
+ }
+
+ out[val.Type().Field(i).Name] = value
+ }
+
+ return out
+}
+
+func isTagPresent(tag string, tags []string) bool {
+ for _, val := range tags {
+ tagValue := strings.TrimSpace(val)
+ if tagValue != "" && tagValue == tag {
+ return true
+ }
+ }
+
+ return false
+}
+
+// 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
+}
+
+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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/custom_status.go b/vendor/github.com/mattermost/mattermost-server/v6/model/custom_status.go
new file mode 100644
index 00000000..2f5084b8
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/custom_status.go
@@ -0,0 +1,140 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "time"
+)
+
+const (
+ UserPropsKeyCustomStatus = "customStatus"
+
+ CustomStatusTextMaxRunes = 100
+ MaxRecentCustomStatuses = 5
+ DefaultCustomStatusEmoji = "speech_balloon"
+)
+
+var validCustomStatusDuration = map[string]bool{
+ "thirty_minutes": true,
+ "one_hour": true,
+ "four_hours": true,
+ "today": true,
+ "this_week": true,
+ "date_and_time": true,
+}
+
+type CustomStatus struct {
+ Emoji string `json:"emoji"`
+ Text string `json:"text"`
+ Duration string `json:"duration"`
+ ExpiresAt time.Time `json:"expires_at"`
+}
+
+func (cs *CustomStatus) PreSave() {
+ if cs.Emoji == "" {
+ cs.Emoji = DefaultCustomStatusEmoji
+ }
+
+ if cs.Duration == "" && !cs.ExpiresAt.Before(time.Now()) {
+ cs.Duration = "date_and_time"
+ }
+
+ runes := []rune(cs.Text)
+ if len(runes) > CustomStatusTextMaxRunes {
+ cs.Text = string(runes[:CustomStatusTextMaxRunes])
+ }
+}
+
+func (cs *CustomStatus) AreDurationAndExpirationTimeValid() bool {
+ if cs.Duration == "" && (cs.ExpiresAt.IsZero() || !cs.ExpiresAt.Before(time.Now())) {
+ return true
+ }
+
+ if validCustomStatusDuration[cs.Duration] && !cs.ExpiresAt.Before(time.Now()) {
+ return true
+ }
+
+ return false
+}
+
+func RuneToHexadecimalString(r rune) string {
+ return fmt.Sprintf("%04x", r)
+}
+
+type RecentCustomStatuses []CustomStatus
+
+func (rcs RecentCustomStatuses) Contains(cs *CustomStatus) (bool, error) {
+ if cs == nil {
+ return false, nil
+ }
+
+ csJSON, jsonErr := json.Marshal(cs)
+ if jsonErr != nil {
+ return false, jsonErr
+ }
+
+ // status is empty
+ if len(csJSON) == 0 || (cs.Emoji == "" && cs.Text == "") {
+ return false, nil
+ }
+
+ for _, status := range rcs {
+ js, jsonErr := json.Marshal(status)
+ if jsonErr != nil {
+ return false, jsonErr
+ }
+ if bytes.Equal(js, csJSON) {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func (rcs RecentCustomStatuses) Add(cs *CustomStatus) RecentCustomStatuses {
+ newRCS := rcs[:0]
+
+ // if same `text` exists in existing recent custom statuses, modify existing status
+ for _, status := range rcs {
+ if status.Text != cs.Text {
+ newRCS = append(newRCS, status)
+ }
+ }
+ newRCS = append(RecentCustomStatuses{*cs}, newRCS...)
+ if len(newRCS) > MaxRecentCustomStatuses {
+ newRCS = newRCS[:MaxRecentCustomStatuses]
+ }
+ return newRCS
+}
+
+func (rcs RecentCustomStatuses) Remove(cs *CustomStatus) (RecentCustomStatuses, error) {
+ if cs == nil {
+ return rcs, nil
+ }
+
+ csJSON, jsonErr := json.Marshal(cs)
+ if jsonErr != nil {
+ return rcs, jsonErr
+ }
+
+ if len(csJSON) == 0 || (cs.Emoji == "" && cs.Text == "") {
+ return rcs, nil
+ }
+
+ newRCS := rcs[:0]
+ for _, status := range rcs {
+ js, jsonErr := json.Marshal(status)
+ if jsonErr != nil {
+ return rcs, jsonErr
+ }
+ if !bytes.Equal(js, csJSON) {
+ newRCS = append(newRCS, status)
+ }
+ }
+
+ return newRCS, nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/data_retention_policy.go b/vendor/github.com/mattermost/mattermost-server/v6/model/data_retention_policy.go
new file mode 100644
index 00000000..0a1d318e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/data_retention_policy.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type GlobalRetentionPolicy 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"`
+}
+
+type RetentionPolicy struct {
+ ID string `db:"Id" json:"id"`
+ DisplayName string `json:"display_name"`
+ PostDuration *int64 `json:"post_duration"`
+}
+
+type RetentionPolicyWithTeamAndChannelIDs struct {
+ RetentionPolicy
+ TeamIDs []string `json:"team_ids"`
+ ChannelIDs []string `json:"channel_ids"`
+}
+
+type RetentionPolicyWithTeamAndChannelCounts struct {
+ RetentionPolicy
+ ChannelCount int64 `json:"channel_count"`
+ TeamCount int64 `json:"team_count"`
+}
+
+type RetentionPolicyChannel struct {
+ PolicyID string `db:"PolicyId"`
+ ChannelID string `db:"ChannelId"`
+}
+
+type RetentionPolicyTeam struct {
+ PolicyID string `db:"PolicyId"`
+ TeamID string `db:"TeamId"`
+}
+
+type RetentionPolicyWithTeamAndChannelCountsList struct {
+ Policies []*RetentionPolicyWithTeamAndChannelCounts `json:"policies"`
+ TotalCount int64 `json:"total_count"`
+}
+
+type RetentionPolicyForTeam struct {
+ TeamID string `db:"Id" json:"team_id"`
+ PostDuration int64 `json:"post_duration"`
+}
+
+type RetentionPolicyForTeamList struct {
+ Policies []*RetentionPolicyForTeam `json:"policies"`
+ TotalCount int64 `json:"total_count"`
+}
+
+type RetentionPolicyForChannel struct {
+ ChannelID string `db:"Id" json:"channel_id"`
+ PostDuration int64 `json:"post_duration"`
+}
+
+type RetentionPolicyForChannelList struct {
+ Policies []*RetentionPolicyForChannel `json:"policies"`
+ TotalCount int64 `json:"total_count"`
+}
+
+type RetentionPolicyCursor struct {
+ ChannelPoliciesDone bool
+ TeamPoliciesDone bool
+ GlobalPoliciesDone bool
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/emoji.go b/vendor/github.com/mattermost/mattermost-server/v6/model/emoji.go
new file mode 100644
index 00000000..fd0e8ab3
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/emoji.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "regexp"
+ "sort"
+)
+
+const (
+ EmojiNameMaxLength = 64
+ EmojiSortByName = "name"
+)
+
+var EmojiPattern = regexp.MustCompile(`:[a-zA-Z0-9_+-]+:`)
+
+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 GetSystemEmojiId(emojiName string) (string, bool) {
+ id, found := SystemEmojis[emojiName]
+ return id, found
+}
+
+func makeReverseEmojiMap() map[string][]string {
+ reverseEmojiMap := make(map[string][]string)
+ for key, value := range SystemEmojis {
+ emojiNames := reverseEmojiMap[value]
+ emojiNames = append(emojiNames, key)
+ sort.Strings(emojiNames)
+ reverseEmojiMap[value] = emojiNames
+ }
+
+ return reverseEmojiMap
+}
+
+var reverseSystemEmojisMap = makeReverseEmojiMap()
+
+func GetEmojiNameFromUnicode(unicode string) (emojiName string, count int) {
+ if emojiNames, found := reverseSystemEmojisMap[unicode]; found {
+ return emojiNames[0], len(emojiNames)
+ }
+
+ return "", 0
+}
+
+func (emoji *Emoji) IsValid() *AppError {
+ if !IsValidId(emoji.Id) {
+ 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 name == "" || len(name) > EmojiNameMaxLength || !IsValidAlphaNumHyphenUnderscorePlus(name) || 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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/emoji_data.go b/vendor/github.com/mattermost/mattermost-server/v6/model/emoji_data.go
new file mode 100644
index 00000000..849c2046
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/emoji_data.go
@@ -0,0 +1,7 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+// This file is automatically generated via `make emojis`. Do not modify it manually.
+
+package model
+
+var SystemEmojis = map[string]string{"grinning": "1f600", "smiley": "1f603", "smile": "1f604", "grin": "1f601", "laughing": "1f606", "satisfied": "1f606", "sweat_smile": "1f605", "rolling_on_the_floor_laughing": "1f923", "rofl": "1f923", "joy": "1f602", "slightly_smiling_face": "1f642", "upside_down_face": "1f643", "wink": "1f609", "blush": "1f60a", "innocent": "1f607", "smiling_face_with_3_hearts": "1f970", "heart_eyes": "1f60d", "star-struck": "1f929", "grinning_face_with_star_eyes": "1f929", "kissing_heart": "1f618", "kissing": "1f617", "relaxed": "263a-fe0f", "kissing_closed_eyes": "1f61a", "kissing_smiling_eyes": "1f619", "smiling_face_with_tear": "1f972", "yum": "1f60b", "stuck_out_tongue": "1f61b", "stuck_out_tongue_winking_eye": "1f61c", "zany_face": "1f92a", "grinning_face_with_one_large_and_one_small_eye": "1f92a", "stuck_out_tongue_closed_eyes": "1f61d", "money_mouth_face": "1f911", "hugging_face": "1f917", "hugs": "1f917", "face_with_hand_over_mouth": "1f92d", "smiling_face_with_smiling_eyes_and_hand_covering_mouth": "1f92d", "shushing_face": "1f92b", "face_with_finger_covering_closed_lips": "1f92b", "thinking_face": "1f914", "thinking": "1f914", "zipper_mouth_face": "1f910", "face_with_raised_eyebrow": "1f928", "face_with_one_eyebrow_raised": "1f928", "neutral_face": "1f610", "expressionless": "1f611", "no_mouth": "1f636", "smirk": "1f60f", "unamused": "1f612", "face_with_rolling_eyes": "1f644", "roll_eyes": "1f644", "grimacing": "1f62c", "lying_face": "1f925", "relieved": "1f60c", "pensive": "1f614", "sleepy": "1f62a", "drooling_face": "1f924", "sleeping": "1f634", "mask": "1f637", "face_with_thermometer": "1f912", "face_with_head_bandage": "1f915", "nauseated_face": "1f922", "face_vomiting": "1f92e", "face_with_open_mouth_vomiting": "1f92e", "sneezing_face": "1f927", "hot_face": "1f975", "cold_face": "1f976", "woozy_face": "1f974", "dizzy_face": "1f635", "exploding_head": "1f92f", "shocked_face_with_exploding_head": "1f92f", "face_with_cowboy_hat": "1f920", "cowboy_hat_face": "1f920", "partying_face": "1f973", "disguised_face": "1f978", "sunglasses": "1f60e", "nerd_face": "1f913", "face_with_monocle": "1f9d0", "confused": "1f615", "worried": "1f61f", "slightly_frowning_face": "1f641", "white_frowning_face": "2639-fe0f", "frowning_face": "2639-fe0f", "open_mouth": "1f62e", "hushed": "1f62f", "astonished": "1f632", "flushed": "1f633", "pleading_face": "1f97a", "frowning": "1f626", "anguished": "1f627", "fearful": "1f628", "cold_sweat": "1f630", "disappointed_relieved": "1f625", "cry": "1f622", "sob": "1f62d", "scream": "1f631", "confounded": "1f616", "persevere": "1f623", "disappointed": "1f61e", "sweat": "1f613", "weary": "1f629", "tired_face": "1f62b", "yawning_face": "1f971", "triumph": "1f624", "rage": "1f621", "pout": "1f621", "angry": "1f620", "face_with_symbols_on_mouth": "1f92c", "serious_face_with_symbols_covering_mouth": "1f92c", "smiling_imp": "1f608", "imp": "1f47f", "skull": "1f480", "skull_and_crossbones": "2620-fe0f", "hankey": "1f4a9", "poop": "1f4a9", "shit": "1f4a9", "clown_face": "1f921", "japanese_ogre": "1f479", "japanese_goblin": "1f47a", "ghost": "1f47b", "alien": "1f47d", "space_invader": "1f47e", "robot_face": "1f916", "robot": "1f916", "smiley_cat": "1f63a", "smile_cat": "1f638", "joy_cat": "1f639", "heart_eyes_cat": "1f63b", "smirk_cat": "1f63c", "kissing_cat": "1f63d", "scream_cat": "1f640", "crying_cat_face": "1f63f", "pouting_cat": "1f63e", "see_no_evil": "1f648", "hear_no_evil": "1f649", "speak_no_evil": "1f64a", "kiss": "1f48b", "love_letter": "1f48c", "cupid": "1f498", "gift_heart": "1f49d", "sparkling_heart": "1f496", "heartpulse": "1f497", "heartbeat": "1f493", "revolving_hearts": "1f49e", "two_hearts": "1f495", "heart_decoration": "1f49f", "heavy_heart_exclamation_mark_ornament": "2763-fe0f", "heavy_heart_exclamation": "2763-fe0f", "broken_heart": "1f494", "heart": "2764-fe0f", "orange_heart": "1f9e1", "yellow_heart": "1f49b", "green_heart": "1f49a", "blue_heart": "1f499", "purple_heart": "1f49c", "brown_heart": "1f90e", "black_heart": "1f5a4", "white_heart": "1f90d", "100": "1f4af", "anger": "1f4a2", "boom": "1f4a5", "collision": "1f4a5", "dizzy": "1f4ab", "sweat_drops": "1f4a6", "dash": "1f4a8", "hole": "1f573-fe0f", "bomb": "1f4a3", "speech_balloon": "1f4ac", "eye-in-speech-bubble": "1f441-fe0f-200d-1f5e8-fe0f", "left_speech_bubble": "1f5e8-fe0f", "right_anger_bubble": "1f5ef-fe0f", "thought_balloon": "1f4ad", "zzz": "1f4a4", "wave": "1f44b", "raised_back_of_hand": "1f91a", "raised_hand_with_fingers_splayed": "1f590-fe0f", "hand": "270b", "raised_hand": "270b", "spock-hand": "1f596", "vulcan_salute": "1f596", "ok_hand": "1f44c", "pinched_fingers": "1f90c", "pinching_hand": "1f90f", "v": "270c-fe0f", "crossed_fingers": "1f91e", "hand_with_index_and_middle_fingers_crossed": "1f91e", "i_love_you_hand_sign": "1f91f", "the_horns": "1f918", "sign_of_the_horns": "1f918", "metal": "1f918", "call_me_hand": "1f919", "point_left": "1f448", "point_right": "1f449", "point_up_2": "1f446", "middle_finger": "1f595", "reversed_hand_with_middle_finger_extended": "1f595", "fu": "1f595", "point_down": "1f447", "point_up": "261d-fe0f", "+1": "1f44d", "thumbsup": "1f44d", "-1": "1f44e", "thumbsdown": "1f44e", "fist": "270a", "fist_raised": "270a", "facepunch": "1f44a", "punch": "1f44a", "fist_oncoming": "1f44a", "left-facing_fist": "1f91b", "fist_left": "1f91b", "right-facing_fist": "1f91c", "fist_right": "1f91c", "clap": "1f44f", "raised_hands": "1f64c", "open_hands": "1f450", "palms_up_together": "1f932", "handshake": "1f91d", "pray": "1f64f", "writing_hand": "270d-fe0f", "nail_care": "1f485", "selfie": "1f933", "muscle": "1f4aa", "mechanical_arm": "1f9be", "mechanical_leg": "1f9bf", "leg": "1f9b5", "foot": "1f9b6", "ear": "1f442", "ear_with_hearing_aid": "1f9bb", "nose": "1f443", "brain": "1f9e0", "anatomical_heart": "1fac0", "lungs": "1fac1", "tooth": "1f9b7", "bone": "1f9b4", "eyes": "1f440", "eye": "1f441-fe0f", "tongue": "1f445", "lips": "1f444", "baby": "1f476", "child": "1f9d2", "boy": "1f466", "girl": "1f467", "adult": "1f9d1", "person_with_blond_hair": "1f471", "man": "1f468", "bearded_person": "1f9d4", "red_haired_man": "1f468-200d-1f9b0", "curly_haired_man": "1f468-200d-1f9b1", "white_haired_man": "1f468-200d-1f9b3", "bald_man": "1f468-200d-1f9b2", "woman": "1f469", "red_haired_woman": "1f469-200d-1f9b0", "red_haired_person": "1f9d1-200d-1f9b0", "curly_haired_woman": "1f469-200d-1f9b1", "curly_haired_person": "1f9d1-200d-1f9b1", "white_haired_woman": "1f469-200d-1f9b3", "white_haired_person": "1f9d1-200d-1f9b3", "bald_woman": "1f469-200d-1f9b2", "bald_person": "1f9d1-200d-1f9b2", "blond-haired-woman": "1f471-200d-2640-fe0f", "blonde_woman": "1f471-200d-2640-fe0f", "blond-haired-man": "1f471-200d-2642-fe0f", "blonde_man": "1f471-200d-2642-fe0f", "older_adult": "1f9d3", "older_man": "1f474", "older_woman": "1f475", "person_frowning": "1f64d", "man-frowning": "1f64d-200d-2642-fe0f", "frowning_man": "1f64d-200d-2642-fe0f", "woman-frowning": "1f64d-200d-2640-fe0f", "frowning_woman": "1f64d-200d-2640-fe0f", "person_with_pouting_face": "1f64e", "man-pouting": "1f64e-200d-2642-fe0f", "pouting_man": "1f64e-200d-2642-fe0f", "woman-pouting": "1f64e-200d-2640-fe0f", "pouting_woman": "1f64e-200d-2640-fe0f", "no_good": "1f645", "man-gesturing-no": "1f645-200d-2642-fe0f", "ng_man": "1f645-200d-2642-fe0f", "no_good_man": "1f645-200d-2642-fe0f", "woman-gesturing-no": "1f645-200d-2640-fe0f", "no_good_woman": "1f645-200d-2640-fe0f", "ng_woman": "1f645-200d-2640-fe0f", "ok_woman": "1f646", "man-gesturing-ok": "1f646-200d-2642-fe0f", "ok_man": "1f646-200d-2642-fe0f", "woman-gesturing-ok": "1f646-200d-2640-fe0f", "information_desk_person": "1f481", "man-tipping-hand": "1f481-200d-2642-fe0f", "tipping_hand_man": "1f481-200d-2642-fe0f", "woman-tipping-hand": "1f481-200d-2640-fe0f", "tipping_hand_woman": "1f481-200d-2640-fe0f", "raising_hand": "1f64b", "man-raising-hand": "1f64b-200d-2642-fe0f", "raising_hand_man": "1f64b-200d-2642-fe0f", "woman-raising-hand": "1f64b-200d-2640-fe0f", "raising_hand_woman": "1f64b-200d-2640-fe0f", "deaf_person": "1f9cf", "deaf_man": "1f9cf-200d-2642-fe0f", "deaf_woman": "1f9cf-200d-2640-fe0f", "bow": "1f647", "man-bowing": "1f647-200d-2642-fe0f", "bowing_man": "1f647-200d-2642-fe0f", "woman-bowing": "1f647-200d-2640-fe0f", "bowing_woman": "1f647-200d-2640-fe0f", "face_palm": "1f926", "man-facepalming": "1f926-200d-2642-fe0f", "man_facepalming": "1f926-200d-2642-fe0f", "woman-facepalming": "1f926-200d-2640-fe0f", "woman_facepalming": "1f926-200d-2640-fe0f", "shrug": "1f937", "man-shrugging": "1f937-200d-2642-fe0f", "man_shrugging": "1f937-200d-2642-fe0f", "woman-shrugging": "1f937-200d-2640-fe0f", "woman_shrugging": "1f937-200d-2640-fe0f", "health_worker": "1f9d1-200d-2695-fe0f", "male-doctor": "1f468-200d-2695-fe0f", "man_health_worker": "1f468-200d-2695-fe0f", "female-doctor": "1f469-200d-2695-fe0f", "woman_health_worker": "1f469-200d-2695-fe0f", "student": "1f9d1-200d-1f393", "male-student": "1f468-200d-1f393", "man_student": "1f468-200d-1f393", "female-student": "1f469-200d-1f393", "woman_student": "1f469-200d-1f393", "teacher": "1f9d1-200d-1f3eb", "male-teacher": "1f468-200d-1f3eb", "man_teacher": "1f468-200d-1f3eb", "female-teacher": "1f469-200d-1f3eb", "woman_teacher": "1f469-200d-1f3eb", "judge": "1f9d1-200d-2696-fe0f", "male-judge": "1f468-200d-2696-fe0f", "man_judge": "1f468-200d-2696-fe0f", "female-judge": "1f469-200d-2696-fe0f", "woman_judge": "1f469-200d-2696-fe0f", "farmer": "1f9d1-200d-1f33e", "male-farmer": "1f468-200d-1f33e", "man_farmer": "1f468-200d-1f33e", "female-farmer": "1f469-200d-1f33e", "woman_farmer": "1f469-200d-1f33e", "cook": "1f9d1-200d-1f373", "male-cook": "1f468-200d-1f373", "man_cook": "1f468-200d-1f373", "female-cook": "1f469-200d-1f373", "woman_cook": "1f469-200d-1f373", "mechanic": "1f9d1-200d-1f527", "male-mechanic": "1f468-200d-1f527", "man_mechanic": "1f468-200d-1f527", "female-mechanic": "1f469-200d-1f527", "woman_mechanic": "1f469-200d-1f527", "factory_worker": "1f9d1-200d-1f3ed", "male-factory-worker": "1f468-200d-1f3ed", "man_factory_worker": "1f468-200d-1f3ed", "female-factory-worker": "1f469-200d-1f3ed", "woman_factory_worker": "1f469-200d-1f3ed", "office_worker": "1f9d1-200d-1f4bc", "male-office-worker": "1f468-200d-1f4bc", "man_office_worker": "1f468-200d-1f4bc", "female-office-worker": "1f469-200d-1f4bc", "woman_office_worker": "1f469-200d-1f4bc", "scientist": "1f9d1-200d-1f52c", "male-scientist": "1f468-200d-1f52c", "man_scientist": "1f468-200d-1f52c", "female-scientist": "1f469-200d-1f52c", "woman_scientist": "1f469-200d-1f52c", "technologist": "1f9d1-200d-1f4bb", "male-technologist": "1f468-200d-1f4bb", "man_technologist": "1f468-200d-1f4bb", "female-technologist": "1f469-200d-1f4bb", "woman_technologist": "1f469-200d-1f4bb", "singer": "1f9d1-200d-1f3a4", "male-singer": "1f468-200d-1f3a4", "man_singer": "1f468-200d-1f3a4", "female-singer": "1f469-200d-1f3a4", "woman_singer": "1f469-200d-1f3a4", "artist": "1f9d1-200d-1f3a8", "male-artist": "1f468-200d-1f3a8", "man_artist": "1f468-200d-1f3a8", "female-artist": "1f469-200d-1f3a8", "woman_artist": "1f469-200d-1f3a8", "pilot": "1f9d1-200d-2708-fe0f", "male-pilot": "1f468-200d-2708-fe0f", "man_pilot": "1f468-200d-2708-fe0f", "female-pilot": "1f469-200d-2708-fe0f", "woman_pilot": "1f469-200d-2708-fe0f", "astronaut": "1f9d1-200d-1f680", "male-astronaut": "1f468-200d-1f680", "man_astronaut": "1f468-200d-1f680", "female-astronaut": "1f469-200d-1f680", "woman_astronaut": "1f469-200d-1f680", "firefighter": "1f9d1-200d-1f692", "male-firefighter": "1f468-200d-1f692", "man_firefighter": "1f468-200d-1f692", "female-firefighter": "1f469-200d-1f692", "woman_firefighter": "1f469-200d-1f692", "cop": "1f46e", "male-police-officer": "1f46e-200d-2642-fe0f", "policeman": "1f46e-200d-2642-fe0f", "female-police-officer": "1f46e-200d-2640-fe0f", "policewoman": "1f46e-200d-2640-fe0f", "sleuth_or_spy": "1f575-fe0f", "detective": "1f575-fe0f", "male-detective": "1f575-fe0f-200d-2642-fe0f", "male_detective": "1f575-fe0f-200d-2642-fe0f", "female-detective": "1f575-fe0f-200d-2640-fe0f", "female_detective": "1f575-fe0f-200d-2640-fe0f", "guardsman": "1f482", "male-guard": "1f482-200d-2642-fe0f", "female-guard": "1f482-200d-2640-fe0f", "guardswoman": "1f482-200d-2640-fe0f", "ninja": "1f977", "construction_worker": "1f477", "male-construction-worker": "1f477-200d-2642-fe0f", "construction_worker_man": "1f477-200d-2642-fe0f", "female-construction-worker": "1f477-200d-2640-fe0f", "construction_worker_woman": "1f477-200d-2640-fe0f", "prince": "1f934", "princess": "1f478", "man_with_turban": "1f473", "man-wearing-turban": "1f473-200d-2642-fe0f", "woman-wearing-turban": "1f473-200d-2640-fe0f", "woman_with_turban": "1f473-200d-2640-fe0f", "man_with_gua_pi_mao": "1f472", "person_with_headscarf": "1f9d5", "person_in_tuxedo": "1f935", "man_in_tuxedo": "1f935-200d-2642-fe0f", "woman_in_tuxedo": "1f935-200d-2640-fe0f", "bride_with_veil": "1f470", "man_with_veil": "1f470-200d-2642-fe0f", "woman_with_veil": "1f470-200d-2640-fe0f", "pregnant_woman": "1f930", "breast-feeding": "1f931", "woman_feeding_baby": "1f469-200d-1f37c", "man_feeding_baby": "1f468-200d-1f37c", "person_feeding_baby": "1f9d1-200d-1f37c", "angel": "1f47c", "santa": "1f385", "mrs_claus": "1f936", "mother_christmas": "1f936", "mx_claus": "1f9d1-200d-1f384", "superhero": "1f9b8", "male_superhero": "1f9b8-200d-2642-fe0f", "female_superhero": "1f9b8-200d-2640-fe0f", "supervillain": "1f9b9", "male_supervillain": "1f9b9-200d-2642-fe0f", "female_supervillain": "1f9b9-200d-2640-fe0f", "mage": "1f9d9", "male_mage": "1f9d9-200d-2642-fe0f", "female_mage": "1f9d9-200d-2640-fe0f", "fairy": "1f9da", "male_fairy": "1f9da-200d-2642-fe0f", "female_fairy": "1f9da-200d-2640-fe0f", "vampire": "1f9db", "male_vampire": "1f9db-200d-2642-fe0f", "female_vampire": "1f9db-200d-2640-fe0f", "merperson": "1f9dc", "merman": "1f9dc-200d-2642-fe0f", "mermaid": "1f9dc-200d-2640-fe0f", "elf": "1f9dd", "male_elf": "1f9dd-200d-2642-fe0f", "female_elf": "1f9dd-200d-2640-fe0f", "genie": "1f9de", "male_genie": "1f9de-200d-2642-fe0f", "female_genie": "1f9de-200d-2640-fe0f", "zombie": "1f9df", "male_zombie": "1f9df-200d-2642-fe0f", "female_zombie": "1f9df-200d-2640-fe0f", "massage": "1f486", "man-getting-massage": "1f486-200d-2642-fe0f", "massage_man": "1f486-200d-2642-fe0f", "woman-getting-massage": "1f486-200d-2640-fe0f", "massage_woman": "1f486-200d-2640-fe0f", "haircut": "1f487", "man-getting-haircut": "1f487-200d-2642-fe0f", "haircut_man": "1f487-200d-2642-fe0f", "woman-getting-haircut": "1f487-200d-2640-fe0f", "haircut_woman": "1f487-200d-2640-fe0f", "walking": "1f6b6", "man-walking": "1f6b6-200d-2642-fe0f", "walking_man": "1f6b6-200d-2642-fe0f", "woman-walking": "1f6b6-200d-2640-fe0f", "walking_woman": "1f6b6-200d-2640-fe0f", "standing_person": "1f9cd", "man_standing": "1f9cd-200d-2642-fe0f", "woman_standing": "1f9cd-200d-2640-fe0f", "kneeling_person": "1f9ce", "man_kneeling": "1f9ce-200d-2642-fe0f", "woman_kneeling": "1f9ce-200d-2640-fe0f", "person_with_probing_cane": "1f9d1-200d-1f9af", "man_with_probing_cane": "1f468-200d-1f9af", "woman_with_probing_cane": "1f469-200d-1f9af", "person_in_motorized_wheelchair": "1f9d1-200d-1f9bc", "man_in_motorized_wheelchair": "1f468-200d-1f9bc", "woman_in_motorized_wheelchair": "1f469-200d-1f9bc", "person_in_manual_wheelchair": "1f9d1-200d-1f9bd", "man_in_manual_wheelchair": "1f468-200d-1f9bd", "woman_in_manual_wheelchair": "1f469-200d-1f9bd", "runner": "1f3c3", "running": "1f3c3", "man-running": "1f3c3-200d-2642-fe0f", "running_man": "1f3c3-200d-2642-fe0f", "woman-running": "1f3c3-200d-2640-fe0f", "running_woman": "1f3c3-200d-2640-fe0f", "dancer": "1f483", "man_dancing": "1f57a", "man_in_business_suit_levitating": "1f574-fe0f", "business_suit_levitating": "1f574-fe0f", "dancers": "1f46f", "man-with-bunny-ears-partying": "1f46f-200d-2642-fe0f", "dancing_men": "1f46f-200d-2642-fe0f", "woman-with-bunny-ears-partying": "1f46f-200d-2640-fe0f", "dancing_women": "1f46f-200d-2640-fe0f", "person_in_steamy_room": "1f9d6", "man_in_steamy_room": "1f9d6-200d-2642-fe0f", "woman_in_steamy_room": "1f9d6-200d-2640-fe0f", "person_climbing": "1f9d7", "man_climbing": "1f9d7-200d-2642-fe0f", "woman_climbing": "1f9d7-200d-2640-fe0f", "fencer": "1f93a", "person_fencing": "1f93a", "horse_racing": "1f3c7", "skier": "26f7-fe0f", "snowboarder": "1f3c2", "golfer": "1f3cc-fe0f", "man-golfing": "1f3cc-fe0f-200d-2642-fe0f", "golfing_man": "1f3cc-fe0f-200d-2642-fe0f", "woman-golfing": "1f3cc-fe0f-200d-2640-fe0f", "golfing_woman": "1f3cc-fe0f-200d-2640-fe0f", "surfer": "1f3c4", "man-surfing": "1f3c4-200d-2642-fe0f", "surfing_man": "1f3c4-200d-2642-fe0f", "woman-surfing": "1f3c4-200d-2640-fe0f", "surfing_woman": "1f3c4-200d-2640-fe0f", "rowboat": "1f6a3", "man-rowing-boat": "1f6a3-200d-2642-fe0f", "rowing_man": "1f6a3-200d-2642-fe0f", "woman-rowing-boat": "1f6a3-200d-2640-fe0f", "rowing_woman": "1f6a3-200d-2640-fe0f", "swimmer": "1f3ca", "man-swimming": "1f3ca-200d-2642-fe0f", "swimming_man": "1f3ca-200d-2642-fe0f", "woman-swimming": "1f3ca-200d-2640-fe0f", "swimming_woman": "1f3ca-200d-2640-fe0f", "person_with_ball": "26f9-fe0f", "man-bouncing-ball": "26f9-fe0f-200d-2642-fe0f", "basketball_man": "26f9-fe0f-200d-2642-fe0f", "woman-bouncing-ball": "26f9-fe0f-200d-2640-fe0f", "basketball_woman": "26f9-fe0f-200d-2640-fe0f", "weight_lifter": "1f3cb-fe0f", "man-lifting-weights": "1f3cb-fe0f-200d-2642-fe0f", "weight_lifting_man": "1f3cb-fe0f-200d-2642-fe0f", "woman-lifting-weights": "1f3cb-fe0f-200d-2640-fe0f", "weight_lifting_woman": "1f3cb-fe0f-200d-2640-fe0f", "bicyclist": "1f6b4", "man-biking": "1f6b4-200d-2642-fe0f", "biking_man": "1f6b4-200d-2642-fe0f", "woman-biking": "1f6b4-200d-2640-fe0f", "biking_woman": "1f6b4-200d-2640-fe0f", "mountain_bicyclist": "1f6b5", "man-mountain-biking": "1f6b5-200d-2642-fe0f", "mountain_biking_man": "1f6b5-200d-2642-fe0f", "woman-mountain-biking": "1f6b5-200d-2640-fe0f", "mountain_biking_woman": "1f6b5-200d-2640-fe0f", "person_doing_cartwheel": "1f938", "man-cartwheeling": "1f938-200d-2642-fe0f", "man_cartwheeling": "1f938-200d-2642-fe0f", "woman-cartwheeling": "1f938-200d-2640-fe0f", "woman_cartwheeling": "1f938-200d-2640-fe0f", "wrestlers": "1f93c", "man-wrestling": "1f93c-200d-2642-fe0f", "men_wrestling": "1f93c-200d-2642-fe0f", "woman-wrestling": "1f93c-200d-2640-fe0f", "women_wrestling": "1f93c-200d-2640-fe0f", "water_polo": "1f93d", "man-playing-water-polo": "1f93d-200d-2642-fe0f", "man_playing_water_polo": "1f93d-200d-2642-fe0f", "woman-playing-water-polo": "1f93d-200d-2640-fe0f", "woman_playing_water_polo": "1f93d-200d-2640-fe0f", "handball": "1f93e", "man-playing-handball": "1f93e-200d-2642-fe0f", "man_playing_handball": "1f93e-200d-2642-fe0f", "woman-playing-handball": "1f93e-200d-2640-fe0f", "woman_playing_handball": "1f93e-200d-2640-fe0f", "juggling": "1f939", "man-juggling": "1f939-200d-2642-fe0f", "man_juggling": "1f939-200d-2642-fe0f", "woman-juggling": "1f939-200d-2640-fe0f", "woman_juggling": "1f939-200d-2640-fe0f", "person_in_lotus_position": "1f9d8", "man_in_lotus_position": "1f9d8-200d-2642-fe0f", "woman_in_lotus_position": "1f9d8-200d-2640-fe0f", "bath": "1f6c0", "sleeping_accommodation": "1f6cc", "sleeping_bed": "1f6cc", "people_holding_hands": "1f9d1-200d-1f91d-200d-1f9d1", "two_women_holding_hands": "1f46d", "women_holding_hands": "1f46d", "man_and_woman_holding_hands": "1f46b", "woman_and_man_holding_hands": "1f46b", "couple": "1f46b", "two_men_holding_hands": "1f46c", "men_holding_hands": "1f46c", "couplekiss": "1f48f", "woman-kiss-man": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f468", "couplekiss_man_woman": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f468", "man-kiss-man": "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", "couplekiss_man_man": "1f468-200d-2764-fe0f-200d-1f48b-200d-1f468", "woman-kiss-woman": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", "couplekiss_woman_woman": "1f469-200d-2764-fe0f-200d-1f48b-200d-1f469", "couple_with_heart": "1f491", "woman-heart-man": "1f469-200d-2764-fe0f-200d-1f468", "couple_with_heart_woman_man": "1f469-200d-2764-fe0f-200d-1f468", "man-heart-man": "1f468-200d-2764-fe0f-200d-1f468", "couple_with_heart_man_man": "1f468-200d-2764-fe0f-200d-1f468", "woman-heart-woman": "1f469-200d-2764-fe0f-200d-1f469", "couple_with_heart_woman_woman": "1f469-200d-2764-fe0f-200d-1f469", "family": "1f46a", "man-woman-boy": "1f468-200d-1f469-200d-1f466", "family_man_woman_boy": "1f468-200d-1f469-200d-1f466", "man-woman-girl": "1f468-200d-1f469-200d-1f467", "family_man_woman_girl": "1f468-200d-1f469-200d-1f467", "man-woman-girl-boy": "1f468-200d-1f469-200d-1f467-200d-1f466", "family_man_woman_girl_boy": "1f468-200d-1f469-200d-1f467-200d-1f466", "man-woman-boy-boy": "1f468-200d-1f469-200d-1f466-200d-1f466", "family_man_woman_boy_boy": "1f468-200d-1f469-200d-1f466-200d-1f466", "man-woman-girl-girl": "1f468-200d-1f469-200d-1f467-200d-1f467", "family_man_woman_girl_girl": "1f468-200d-1f469-200d-1f467-200d-1f467", "man-man-boy": "1f468-200d-1f468-200d-1f466", "family_man_man_boy": "1f468-200d-1f468-200d-1f466", "man-man-girl": "1f468-200d-1f468-200d-1f467", "family_man_man_girl": "1f468-200d-1f468-200d-1f467", "man-man-girl-boy": "1f468-200d-1f468-200d-1f467-200d-1f466", "family_man_man_girl_boy": "1f468-200d-1f468-200d-1f467-200d-1f466", "man-man-boy-boy": "1f468-200d-1f468-200d-1f466-200d-1f466", "family_man_man_boy_boy": "1f468-200d-1f468-200d-1f466-200d-1f466", "man-man-girl-girl": "1f468-200d-1f468-200d-1f467-200d-1f467", "family_man_man_girl_girl": "1f468-200d-1f468-200d-1f467-200d-1f467", "woman-woman-boy": "1f469-200d-1f469-200d-1f466", "family_woman_woman_boy": "1f469-200d-1f469-200d-1f466", "woman-woman-girl": "1f469-200d-1f469-200d-1f467", "family_woman_woman_girl": "1f469-200d-1f469-200d-1f467", "woman-woman-girl-boy": "1f469-200d-1f469-200d-1f467-200d-1f466", "family_woman_woman_girl_boy": "1f469-200d-1f469-200d-1f467-200d-1f466", "woman-woman-boy-boy": "1f469-200d-1f469-200d-1f466-200d-1f466", "family_woman_woman_boy_boy": "1f469-200d-1f469-200d-1f466-200d-1f466", "woman-woman-girl-girl": "1f469-200d-1f469-200d-1f467-200d-1f467", "family_woman_woman_girl_girl": "1f469-200d-1f469-200d-1f467-200d-1f467", "man-boy": "1f468-200d-1f466", "family_man_boy": "1f468-200d-1f466", "man-boy-boy": "1f468-200d-1f466-200d-1f466", "family_man_boy_boy": "1f468-200d-1f466-200d-1f466", "man-girl": "1f468-200d-1f467", "family_man_girl": "1f468-200d-1f467", "man-girl-boy": "1f468-200d-1f467-200d-1f466", "family_man_girl_boy": "1f468-200d-1f467-200d-1f466", "man-girl-girl": "1f468-200d-1f467-200d-1f467", "family_man_girl_girl": "1f468-200d-1f467-200d-1f467", "woman-boy": "1f469-200d-1f466", "family_woman_boy": "1f469-200d-1f466", "woman-boy-boy": "1f469-200d-1f466-200d-1f466", "family_woman_boy_boy": "1f469-200d-1f466-200d-1f466", "woman-girl": "1f469-200d-1f467", "family_woman_girl": "1f469-200d-1f467", "woman-girl-boy": "1f469-200d-1f467-200d-1f466", "family_woman_girl_boy": "1f469-200d-1f467-200d-1f466", "woman-girl-girl": "1f469-200d-1f467-200d-1f467", "family_woman_girl_girl": "1f469-200d-1f467-200d-1f467", "speaking_head_in_silhouette": "1f5e3-fe0f", "speaking_head": "1f5e3-fe0f", "bust_in_silhouette": "1f464", "busts_in_silhouette": "1f465", "people_hugging": "1fac2", "footprints": "1f463", "skin-tone-2": "1f3fb", "skin-tone-3": "1f3fc", "skin-tone-4": "1f3fd", "skin-tone-5": "1f3fe", "skin-tone-6": "1f3ff", "monkey_face": "1f435", "monkey": "1f412", "gorilla": "1f98d", "orangutan": "1f9a7", "dog": "1f436", "dog2": "1f415", "guide_dog": "1f9ae", "service_dog": "1f415-200d-1f9ba", "poodle": "1f429", "wolf": "1f43a", "fox_face": "1f98a", "raccoon": "1f99d", "cat": "1f431", "cat2": "1f408", "black_cat": "1f408-200d-2b1b", "lion_face": "1f981", "lion": "1f981", "tiger": "1f42f", "tiger2": "1f405", "leopard": "1f406", "horse": "1f434", "racehorse": "1f40e", "unicorn_face": "1f984", "unicorn": "1f984", "zebra_face": "1f993", "deer": "1f98c", "bison": "1f9ac", "cow": "1f42e", "ox": "1f402", "water_buffalo": "1f403", "cow2": "1f404", "pig": "1f437", "pig2": "1f416", "boar": "1f417", "pig_nose": "1f43d", "ram": "1f40f", "sheep": "1f411", "goat": "1f410", "dromedary_camel": "1f42a", "camel": "1f42b", "llama": "1f999", "giraffe_face": "1f992", "elephant": "1f418", "mammoth": "1f9a3", "rhinoceros": "1f98f", "hippopotamus": "1f99b", "mouse": "1f42d", "mouse2": "1f401", "rat": "1f400", "hamster": "1f439", "rabbit": "1f430", "rabbit2": "1f407", "chipmunk": "1f43f-fe0f", "beaver": "1f9ab", "hedgehog": "1f994", "bat": "1f987", "bear": "1f43b", "polar_bear": "1f43b-200d-2744-fe0f", "koala": "1f428", "panda_face": "1f43c", "sloth": "1f9a5", "otter": "1f9a6", "skunk": "1f9a8", "kangaroo": "1f998", "badger": "1f9a1", "feet": "1f43e", "paw_prints": "1f43e", "turkey": "1f983", "chicken": "1f414", "rooster": "1f413", "hatching_chick": "1f423", "baby_chick": "1f424", "hatched_chick": "1f425", "bird": "1f426", "penguin": "1f427", "dove_of_peace": "1f54a-fe0f", "dove": "1f54a-fe0f", "eagle": "1f985", "duck": "1f986", "swan": "1f9a2", "owl": "1f989", "dodo": "1f9a4", "feather": "1fab6", "flamingo": "1f9a9", "peacock": "1f99a", "parrot": "1f99c", "frog": "1f438", "crocodile": "1f40a", "turtle": "1f422", "lizard": "1f98e", "snake": "1f40d", "dragon_face": "1f432", "dragon": "1f409", "sauropod": "1f995", "t-rex": "1f996", "whale": "1f433", "whale2": "1f40b", "dolphin": "1f42c", "flipper": "1f42c", "seal": "1f9ad", "fish": "1f41f", "tropical_fish": "1f420", "blowfish": "1f421", "shark": "1f988", "octopus": "1f419", "shell": "1f41a", "snail": "1f40c", "butterfly": "1f98b", "bug": "1f41b", "ant": "1f41c", "bee": "1f41d", "honeybee": "1f41d", "beetle": "1fab2", "ladybug": "1f41e", "lady_beetle": "1f41e", "cricket": "1f997", "cockroach": "1fab3", "spider": "1f577-fe0f", "spider_web": "1f578-fe0f", "scorpion": "1f982", "mosquito": "1f99f", "fly": "1fab0", "worm": "1fab1", "microbe": "1f9a0", "bouquet": "1f490", "cherry_blossom": "1f338", "white_flower": "1f4ae", "rosette": "1f3f5-fe0f", "rose": "1f339", "wilted_flower": "1f940", "hibiscus": "1f33a", "sunflower": "1f33b", "blossom": "1f33c", "tulip": "1f337", "seedling": "1f331", "potted_plant": "1fab4", "evergreen_tree": "1f332", "deciduous_tree": "1f333", "palm_tree": "1f334", "cactus": "1f335", "ear_of_rice": "1f33e", "herb": "1f33f", "shamrock": "2618-fe0f", "four_leaf_clover": "1f340", "maple_leaf": "1f341", "fallen_leaf": "1f342", "leaves": "1f343", "grapes": "1f347", "melon": "1f348", "watermelon": "1f349", "tangerine": "1f34a", "mandarin": "1f34a", "orange": "1f34a", "lemon": "1f34b", "banana": "1f34c", "pineapple": "1f34d", "mango": "1f96d", "apple": "1f34e", "green_apple": "1f34f", "pear": "1f350", "peach": "1f351", "cherries": "1f352", "strawberry": "1f353", "blueberries": "1fad0", "kiwifruit": "1f95d", "kiwi_fruit": "1f95d", "tomato": "1f345", "olive": "1fad2", "coconut": "1f965", "avocado": "1f951", "eggplant": "1f346", "potato": "1f954", "carrot": "1f955", "corn": "1f33d", "hot_pepper": "1f336-fe0f", "bell_pepper": "1fad1", "cucumber": "1f952", "leafy_green": "1f96c", "broccoli": "1f966", "garlic": "1f9c4", "onion": "1f9c5", "mushroom": "1f344", "peanuts": "1f95c", "chestnut": "1f330", "bread": "1f35e", "croissant": "1f950", "baguette_bread": "1f956", "flatbread": "1fad3", "pretzel": "1f968", "bagel": "1f96f", "pancakes": "1f95e", "waffle": "1f9c7", "cheese_wedge": "1f9c0", "cheese": "1f9c0", "meat_on_bone": "1f356", "poultry_leg": "1f357", "cut_of_meat": "1f969", "bacon": "1f953", "hamburger": "1f354", "fries": "1f35f", "pizza": "1f355", "hotdog": "1f32d", "sandwich": "1f96a", "taco": "1f32e", "burrito": "1f32f", "tamale": "1fad4", "stuffed_flatbread": "1f959", "falafel": "1f9c6", "egg": "1f95a", "fried_egg": "1f373", "cooking": "1f373", "shallow_pan_of_food": "1f958", "stew": "1f372", "fondue": "1fad5", "bowl_with_spoon": "1f963", "green_salad": "1f957", "popcorn": "1f37f", "butter": "1f9c8", "salt": "1f9c2", "canned_food": "1f96b", "bento": "1f371", "rice_cracker": "1f358", "rice_ball": "1f359", "rice": "1f35a", "curry": "1f35b", "ramen": "1f35c", "spaghetti": "1f35d", "sweet_potato": "1f360", "oden": "1f362", "sushi": "1f363", "fried_shrimp": "1f364", "fish_cake": "1f365", "moon_cake": "1f96e", "dango": "1f361", "dumpling": "1f95f", "fortune_cookie": "1f960", "takeout_box": "1f961", "crab": "1f980", "lobster": "1f99e", "shrimp": "1f990", "squid": "1f991", "oyster": "1f9aa", "icecream": "1f366", "shaved_ice": "1f367", "ice_cream": "1f368", "doughnut": "1f369", "cookie": "1f36a", "birthday": "1f382", "cake": "1f370", "cupcake": "1f9c1", "pie": "1f967", "chocolate_bar": "1f36b", "candy": "1f36c", "lollipop": "1f36d", "custard": "1f36e", "honey_pot": "1f36f", "baby_bottle": "1f37c", "glass_of_milk": "1f95b", "milk_glass": "1f95b", "coffee": "2615", "teapot": "1fad6", "tea": "1f375", "sake": "1f376", "champagne": "1f37e", "wine_glass": "1f377", "cocktail": "1f378", "tropical_drink": "1f379", "beer": "1f37a", "beers": "1f37b", "clinking_glasses": "1f942", "tumbler_glass": "1f943", "cup_with_straw": "1f964", "bubble_tea": "1f9cb", "beverage_box": "1f9c3", "mate_drink": "1f9c9", "ice_cube": "1f9ca", "chopsticks": "1f962", "knife_fork_plate": "1f37d-fe0f", "plate_with_cutlery": "1f37d-fe0f", "fork_and_knife": "1f374", "spoon": "1f944", "hocho": "1f52a", "knife": "1f52a", "amphora": "1f3fa", "earth_africa": "1f30d", "earth_americas": "1f30e", "earth_asia": "1f30f", "globe_with_meridians": "1f310", "world_map": "1f5fa-fe0f", "japan": "1f5fe", "compass": "1f9ed", "snow_capped_mountain": "1f3d4-fe0f", "mountain_snow": "1f3d4-fe0f", "mountain": "26f0-fe0f", "volcano": "1f30b", "mount_fuji": "1f5fb", "camping": "1f3d5-fe0f", "beach_with_umbrella": "1f3d6-fe0f", "beach_umbrella": "1f3d6-fe0f", "desert": "1f3dc-fe0f", "desert_island": "1f3dd-fe0f", "national_park": "1f3de-fe0f", "stadium": "1f3df-fe0f", "classical_building": "1f3db-fe0f", "building_construction": "1f3d7-fe0f", "bricks": "1f9f1", "rock": "1faa8", "wood": "1fab5", "hut": "1f6d6", "house_buildings": "1f3d8-fe0f", "houses": "1f3d8-fe0f", "derelict_house_building": "1f3da-fe0f", "derelict_house": "1f3da-fe0f", "house": "1f3e0", "house_with_garden": "1f3e1", "office": "1f3e2", "post_office": "1f3e3", "european_post_office": "1f3e4", "hospital": "1f3e5", "bank": "1f3e6", "hotel": "1f3e8", "love_hotel": "1f3e9", "convenience_store": "1f3ea", "school": "1f3eb", "department_store": "1f3ec", "factory": "1f3ed", "japanese_castle": "1f3ef", "european_castle": "1f3f0", "wedding": "1f492", "tokyo_tower": "1f5fc", "statue_of_liberty": "1f5fd", "church": "26ea", "mosque": "1f54c", "hindu_temple": "1f6d5", "synagogue": "1f54d", "shinto_shrine": "26e9-fe0f", "kaaba": "1f54b", "fountain": "26f2", "tent": "26fa", "foggy": "1f301", "night_with_stars": "1f303", "cityscape": "1f3d9-fe0f", "sunrise_over_mountains": "1f304", "sunrise": "1f305", "city_sunset": "1f306", "city_sunrise": "1f307", "bridge_at_night": "1f309", "hotsprings": "2668-fe0f", "carousel_horse": "1f3a0", "ferris_wheel": "1f3a1", "roller_coaster": "1f3a2", "barber": "1f488", "circus_tent": "1f3aa", "steam_locomotive": "1f682", "railway_car": "1f683", "bullettrain_side": "1f684", "bullettrain_front": "1f685", "train2": "1f686", "metro": "1f687", "light_rail": "1f688", "station": "1f689", "tram": "1f68a", "monorail": "1f69d", "mountain_railway": "1f69e", "train": "1f68b", "bus": "1f68c", "oncoming_bus": "1f68d", "trolleybus": "1f68e", "minibus": "1f690", "ambulance": "1f691", "fire_engine": "1f692", "police_car": "1f693", "oncoming_police_car": "1f694", "taxi": "1f695", "oncoming_taxi": "1f696", "car": "1f697", "red_car": "1f697", "oncoming_automobile": "1f698", "blue_car": "1f699", "pickup_truck": "1f6fb", "truck": "1f69a", "articulated_lorry": "1f69b", "tractor": "1f69c", "racing_car": "1f3ce-fe0f", "racing_motorcycle": "1f3cd-fe0f", "motorcycle": "1f3cd-fe0f", "motor_scooter": "1f6f5", "manual_wheelchair": "1f9bd", "motorized_wheelchair": "1f9bc", "auto_rickshaw": "1f6fa", "bike": "1f6b2", "scooter": "1f6f4", "kick_scooter": "1f6f4", "skateboard": "1f6f9", "roller_skate": "1f6fc", "busstop": "1f68f", "motorway": "1f6e3-fe0f", "railway_track": "1f6e4-fe0f", "oil_drum": "1f6e2-fe0f", "fuelpump": "26fd", "rotating_light": "1f6a8", "traffic_light": "1f6a5", "vertical_traffic_light": "1f6a6", "octagonal_sign": "1f6d1", "stop_sign": "1f6d1", "construction": "1f6a7", "anchor": "2693", "boat": "26f5", "sailboat": "26f5", "canoe": "1f6f6", "speedboat": "1f6a4", "passenger_ship": "1f6f3-fe0f", "ferry": "26f4-fe0f", "motor_boat": "1f6e5-fe0f", "ship": "1f6a2", "airplane": "2708-fe0f", "small_airplane": "1f6e9-fe0f", "airplane_departure": "1f6eb", "flight_departure": "1f6eb", "airplane_arriving": "1f6ec", "flight_arrival": "1f6ec", "parachute": "1fa82", "seat": "1f4ba", "helicopter": "1f681", "suspension_railway": "1f69f", "mountain_cableway": "1f6a0", "aerial_tramway": "1f6a1", "satellite": "1f6f0-fe0f", "artificial_satellite": "1f6f0-fe0f", "rocket": "1f680", "flying_saucer": "1f6f8", "bellhop_bell": "1f6ce-fe0f", "luggage": "1f9f3", "hourglass": "231b", "hourglass_flowing_sand": "23f3", "watch": "231a", "alarm_clock": "23f0", "stopwatch": "23f1-fe0f", "timer_clock": "23f2-fe0f", "mantelpiece_clock": "1f570-fe0f", "clock12": "1f55b", "clock1230": "1f567", "clock1": "1f550", "clock130": "1f55c", "clock2": "1f551", "clock230": "1f55d", "clock3": "1f552", "clock330": "1f55e", "clock4": "1f553", "clock430": "1f55f", "clock5": "1f554", "clock530": "1f560", "clock6": "1f555", "clock630": "1f561", "clock7": "1f556", "clock730": "1f562", "clock8": "1f557", "clock830": "1f563", "clock9": "1f558", "clock930": "1f564", "clock10": "1f559", "clock1030": "1f565", "clock11": "1f55a", "clock1130": "1f566", "new_moon": "1f311", "waxing_crescent_moon": "1f312", "first_quarter_moon": "1f313", "moon": "1f314", "waxing_gibbous_moon": "1f314", "full_moon": "1f315", "waning_gibbous_moon": "1f316", "last_quarter_moon": "1f317", "waning_crescent_moon": "1f318", "crescent_moon": "1f319", "new_moon_with_face": "1f31a", "first_quarter_moon_with_face": "1f31b", "last_quarter_moon_with_face": "1f31c", "thermometer": "1f321-fe0f", "sunny": "2600-fe0f", "full_moon_with_face": "1f31d", "sun_with_face": "1f31e", "ringed_planet": "1fa90", "star": "2b50", "star2": "1f31f", "stars": "1f320", "milky_way": "1f30c", "cloud": "2601-fe0f", "partly_sunny": "26c5", "thunder_cloud_and_rain": "26c8-fe0f", "cloud_with_lightning_and_rain": "26c8-fe0f", "mostly_sunny": "1f324-fe0f", "sun_small_cloud": "1f324-fe0f", "sun_behind_small_cloud": "1f324-fe0f", "barely_sunny": "1f325-fe0f", "sun_behind_cloud": "1f325-fe0f", "sun_behind_large_cloud": "1f325-fe0f", "partly_sunny_rain": "1f326-fe0f", "sun_behind_rain_cloud": "1f326-fe0f", "rain_cloud": "1f327-fe0f", "cloud_with_rain": "1f327-fe0f", "snow_cloud": "1f328-fe0f", "cloud_with_snow": "1f328-fe0f", "lightning": "1f329-fe0f", "lightning_cloud": "1f329-fe0f", "cloud_with_lightning": "1f329-fe0f", "tornado": "1f32a-fe0f", "tornado_cloud": "1f32a-fe0f", "fog": "1f32b-fe0f", "wind_blowing_face": "1f32c-fe0f", "wind_face": "1f32c-fe0f", "cyclone": "1f300", "rainbow": "1f308", "closed_umbrella": "1f302", "umbrella": "2602-fe0f", "open_umbrella": "2602-fe0f", "umbrella_with_rain_drops": "2614", "umbrella_on_ground": "26f1-fe0f", "parasol_on_ground": "26f1-fe0f", "zap": "26a1", "snowflake": "2744-fe0f", "snowman": "2603-fe0f", "snowman_with_snow": "2603-fe0f", "snowman_without_snow": "26c4", "comet": "2604-fe0f", "fire": "1f525", "droplet": "1f4a7", "ocean": "1f30a", "jack_o_lantern": "1f383", "christmas_tree": "1f384", "fireworks": "1f386", "sparkler": "1f387", "firecracker": "1f9e8", "sparkles": "2728", "balloon": "1f388", "tada": "1f389", "confetti_ball": "1f38a", "tanabata_tree": "1f38b", "bamboo": "1f38d", "dolls": "1f38e", "flags": "1f38f", "wind_chime": "1f390", "rice_scene": "1f391", "red_envelope": "1f9e7", "ribbon": "1f380", "gift": "1f381", "reminder_ribbon": "1f397-fe0f", "admission_tickets": "1f39f-fe0f", "tickets": "1f39f-fe0f", "ticket": "1f3ab", "medal": "1f396-fe0f", "medal_military": "1f396-fe0f", "trophy": "1f3c6", "sports_medal": "1f3c5", "medal_sports": "1f3c5", "first_place_medal": "1f947", "1st_place_medal": "1f947", "second_place_medal": "1f948", "2nd_place_medal": "1f948", "third_place_medal": "1f949", "3rd_place_medal": "1f949", "soccer": "26bd", "baseball": "26be", "softball": "1f94e", "basketball": "1f3c0", "volleyball": "1f3d0", "football": "1f3c8", "rugby_football": "1f3c9", "tennis": "1f3be", "flying_disc": "1f94f", "bowling": "1f3b3", "cricket_bat_and_ball": "1f3cf", "field_hockey_stick_and_ball": "1f3d1", "field_hockey": "1f3d1", "ice_hockey_stick_and_puck": "1f3d2", "ice_hockey": "1f3d2", "lacrosse": "1f94d", "table_tennis_paddle_and_ball": "1f3d3", "ping_pong": "1f3d3", "badminton_racquet_and_shuttlecock": "1f3f8", "badminton": "1f3f8", "boxing_glove": "1f94a", "martial_arts_uniform": "1f94b", "goal_net": "1f945", "golf": "26f3", "ice_skate": "26f8-fe0f", "fishing_pole_and_fish": "1f3a3", "diving_mask": "1f93f", "running_shirt_with_sash": "1f3bd", "ski": "1f3bf", "sled": "1f6f7", "curling_stone": "1f94c", "dart": "1f3af", "yo-yo": "1fa80", "kite": "1fa81", "8ball": "1f3b1", "crystal_ball": "1f52e", "magic_wand": "1fa84", "nazar_amulet": "1f9ff", "video_game": "1f3ae", "joystick": "1f579-fe0f", "slot_machine": "1f3b0", "game_die": "1f3b2", "jigsaw": "1f9e9", "teddy_bear": "1f9f8", "pinata": "1fa85", "nesting_dolls": "1fa86", "spades": "2660-fe0f", "hearts": "2665-fe0f", "diamonds": "2666-fe0f", "clubs": "2663-fe0f", "chess_pawn": "265f-fe0f", "black_joker": "1f0cf", "mahjong": "1f004", "flower_playing_cards": "1f3b4", "performing_arts": "1f3ad", "frame_with_picture": "1f5bc-fe0f", "framed_picture": "1f5bc-fe0f", "art": "1f3a8", "thread": "1f9f5", "sewing_needle": "1faa1", "yarn": "1f9f6", "knot": "1faa2", "eyeglasses": "1f453", "dark_sunglasses": "1f576-fe0f", "goggles": "1f97d", "lab_coat": "1f97c", "safety_vest": "1f9ba", "necktie": "1f454", "shirt": "1f455", "tshirt": "1f455", "jeans": "1f456", "scarf": "1f9e3", "gloves": "1f9e4", "coat": "1f9e5", "socks": "1f9e6", "dress": "1f457", "kimono": "1f458", "sari": "1f97b", "one-piece_swimsuit": "1fa71", "briefs": "1fa72", "shorts": "1fa73", "bikini": "1f459", "womans_clothes": "1f45a", "purse": "1f45b", "handbag": "1f45c", "pouch": "1f45d", "shopping_bags": "1f6cd-fe0f", "shopping": "1f6cd-fe0f", "school_satchel": "1f392", "thong_sandal": "1fa74", "mans_shoe": "1f45e", "shoe": "1f45e", "athletic_shoe": "1f45f", "hiking_boot": "1f97e", "womans_flat_shoe": "1f97f", "high_heel": "1f460", "sandal": "1f461", "ballet_shoes": "1fa70", "boot": "1f462", "crown": "1f451", "womans_hat": "1f452", "tophat": "1f3a9", "mortar_board": "1f393", "billed_cap": "1f9e2", "military_helmet": "1fa96", "helmet_with_white_cross": "26d1-fe0f", "rescue_worker_helmet": "26d1-fe0f", "prayer_beads": "1f4ff", "lipstick": "1f484", "ring": "1f48d", "gem": "1f48e", "mute": "1f507", "speaker": "1f508", "sound": "1f509", "loud_sound": "1f50a", "loudspeaker": "1f4e2", "mega": "1f4e3", "postal_horn": "1f4ef", "bell": "1f514", "no_bell": "1f515", "musical_score": "1f3bc", "musical_note": "1f3b5", "notes": "1f3b6", "studio_microphone": "1f399-fe0f", "level_slider": "1f39a-fe0f", "control_knobs": "1f39b-fe0f", "microphone": "1f3a4", "headphones": "1f3a7", "radio": "1f4fb", "saxophone": "1f3b7", "accordion": "1fa97", "guitar": "1f3b8", "musical_keyboard": "1f3b9", "trumpet": "1f3ba", "violin": "1f3bb", "banjo": "1fa95", "drum_with_drumsticks": "1f941", "drum": "1f941", "long_drum": "1fa98", "iphone": "1f4f1", "calling": "1f4f2", "phone": "260e-fe0f", "telephone": "260e-fe0f", "telephone_receiver": "1f4de", "pager": "1f4df", "fax": "1f4e0", "battery": "1f50b", "electric_plug": "1f50c", "computer": "1f4bb", "desktop_computer": "1f5a5-fe0f", "printer": "1f5a8-fe0f", "keyboard": "2328-fe0f", "three_button_mouse": "1f5b1-fe0f", "computer_mouse": "1f5b1-fe0f", "trackball": "1f5b2-fe0f", "minidisc": "1f4bd", "floppy_disk": "1f4be", "cd": "1f4bf", "dvd": "1f4c0", "abacus": "1f9ee", "movie_camera": "1f3a5", "film_frames": "1f39e-fe0f", "film_strip": "1f39e-fe0f", "film_projector": "1f4fd-fe0f", "clapper": "1f3ac", "tv": "1f4fa", "camera": "1f4f7", "camera_with_flash": "1f4f8", "camera_flash": "1f4f8", "video_camera": "1f4f9", "vhs": "1f4fc", "mag": "1f50d", "mag_right": "1f50e", "candle": "1f56f-fe0f", "bulb": "1f4a1", "flashlight": "1f526", "izakaya_lantern": "1f3ee", "lantern": "1f3ee", "diya_lamp": "1fa94", "notebook_with_decorative_cover": "1f4d4", "closed_book": "1f4d5", "book": "1f4d6", "open_book": "1f4d6", "green_book": "1f4d7", "blue_book": "1f4d8", "orange_book": "1f4d9", "books": "1f4da", "notebook": "1f4d3", "ledger": "1f4d2", "page_with_curl": "1f4c3", "scroll": "1f4dc", "page_facing_up": "1f4c4", "newspaper": "1f4f0", "rolled_up_newspaper": "1f5de-fe0f", "newspaper_roll": "1f5de-fe0f", "bookmark_tabs": "1f4d1", "bookmark": "1f516", "label": "1f3f7-fe0f", "moneybag": "1f4b0", "coin": "1fa99", "yen": "1f4b4", "dollar": "1f4b5", "euro": "1f4b6", "pound": "1f4b7", "money_with_wings": "1f4b8", "credit_card": "1f4b3", "receipt": "1f9fe", "chart": "1f4b9", "email": "2709-fe0f", "envelope": "2709-fe0f", "e-mail": "1f4e7", "incoming_envelope": "1f4e8", "envelope_with_arrow": "1f4e9", "outbox_tray": "1f4e4", "inbox_tray": "1f4e5", "package": "1f4e6", "mailbox": "1f4eb", "mailbox_closed": "1f4ea", "mailbox_with_mail": "1f4ec", "mailbox_with_no_mail": "1f4ed", "postbox": "1f4ee", "ballot_box_with_ballot": "1f5f3-fe0f", "ballot_box": "1f5f3-fe0f", "pencil2": "270f-fe0f", "black_nib": "2712-fe0f", "lower_left_fountain_pen": "1f58b-fe0f", "fountain_pen": "1f58b-fe0f", "lower_left_ballpoint_pen": "1f58a-fe0f", "pen": "1f58a-fe0f", "lower_left_paintbrush": "1f58c-fe0f", "paintbrush": "1f58c-fe0f", "lower_left_crayon": "1f58d-fe0f", "crayon": "1f58d-fe0f", "memo": "1f4dd", "pencil": "1f4dd", "briefcase": "1f4bc", "file_folder": "1f4c1", "open_file_folder": "1f4c2", "card_index_dividers": "1f5c2-fe0f", "date": "1f4c5", "calendar": "1f4c6", "spiral_note_pad": "1f5d2-fe0f", "spiral_notepad": "1f5d2-fe0f", "spiral_calendar_pad": "1f5d3-fe0f", "spiral_calendar": "1f5d3-fe0f", "card_index": "1f4c7", "chart_with_upwards_trend": "1f4c8", "chart_with_downwards_trend": "1f4c9", "bar_chart": "1f4ca", "clipboard": "1f4cb", "pushpin": "1f4cc", "round_pushpin": "1f4cd", "paperclip": "1f4ce", "linked_paperclips": "1f587-fe0f", "paperclips": "1f587-fe0f", "straight_ruler": "1f4cf", "triangular_ruler": "1f4d0", "scissors": "2702-fe0f", "card_file_box": "1f5c3-fe0f", "file_cabinet": "1f5c4-fe0f", "wastebasket": "1f5d1-fe0f", "lock": "1f512", "unlock": "1f513", "lock_with_ink_pen": "1f50f", "closed_lock_with_key": "1f510", "key": "1f511", "old_key": "1f5dd-fe0f", "hammer": "1f528", "axe": "1fa93", "pick": "26cf-fe0f", "hammer_and_pick": "2692-fe0f", "hammer_and_wrench": "1f6e0-fe0f", "dagger_knife": "1f5e1-fe0f", "dagger": "1f5e1-fe0f", "crossed_swords": "2694-fe0f", "gun": "1f52b", "boomerang": "1fa83", "bow_and_arrow": "1f3f9", "shield": "1f6e1-fe0f", "carpentry_saw": "1fa9a", "wrench": "1f527", "screwdriver": "1fa9b", "nut_and_bolt": "1f529", "gear": "2699-fe0f", "compression": "1f5dc-fe0f", "clamp": "1f5dc-fe0f", "scales": "2696-fe0f", "balance_scale": "2696-fe0f", "probing_cane": "1f9af", "link": "1f517", "chains": "26d3-fe0f", "hook": "1fa9d", "toolbox": "1f9f0", "magnet": "1f9f2", "ladder": "1fa9c", "alembic": "2697-fe0f", "test_tube": "1f9ea", "petri_dish": "1f9eb", "dna": "1f9ec", "microscope": "1f52c", "telescope": "1f52d", "satellite_antenna": "1f4e1", "syringe": "1f489", "drop_of_blood": "1fa78", "pill": "1f48a", "adhesive_bandage": "1fa79", "stethoscope": "1fa7a", "door": "1f6aa", "elevator": "1f6d7", "mirror": "1fa9e", "window": "1fa9f", "bed": "1f6cf-fe0f", "couch_and_lamp": "1f6cb-fe0f", "chair": "1fa91", "toilet": "1f6bd", "plunger": "1faa0", "shower": "1f6bf", "bathtub": "1f6c1", "mouse_trap": "1faa4", "razor": "1fa92", "lotion_bottle": "1f9f4", "safety_pin": "1f9f7", "broom": "1f9f9", "basket": "1f9fa", "roll_of_paper": "1f9fb", "bucket": "1faa3", "soap": "1f9fc", "toothbrush": "1faa5", "sponge": "1f9fd", "fire_extinguisher": "1f9ef", "shopping_trolley": "1f6d2", "shopping_cart": "1f6d2", "smoking": "1f6ac", "coffin": "26b0-fe0f", "headstone": "1faa6", "funeral_urn": "26b1-fe0f", "moyai": "1f5ff", "placard": "1faa7", "atm": "1f3e7", "put_litter_in_its_place": "1f6ae", "potable_water": "1f6b0", "wheelchair": "267f", "mens": "1f6b9", "womens": "1f6ba", "restroom": "1f6bb", "baby_symbol": "1f6bc", "wc": "1f6be", "passport_control": "1f6c2", "customs": "1f6c3", "baggage_claim": "1f6c4", "left_luggage": "1f6c5", "warning": "26a0-fe0f", "children_crossing": "1f6b8", "no_entry": "26d4", "no_entry_sign": "1f6ab", "no_bicycles": "1f6b3", "no_smoking": "1f6ad", "do_not_litter": "1f6af", "non-potable_water": "1f6b1", "no_pedestrians": "1f6b7", "no_mobile_phones": "1f4f5", "underage": "1f51e", "radioactive_sign": "2622-fe0f", "radioactive": "2622-fe0f", "biohazard_sign": "2623-fe0f", "biohazard": "2623-fe0f", "arrow_up": "2b06-fe0f", "arrow_upper_right": "2197-fe0f", "arrow_right": "27a1-fe0f", "arrow_lower_right": "2198-fe0f", "arrow_down": "2b07-fe0f", "arrow_lower_left": "2199-fe0f", "arrow_left": "2b05-fe0f", "arrow_upper_left": "2196-fe0f", "arrow_up_down": "2195-fe0f", "left_right_arrow": "2194-fe0f", "leftwards_arrow_with_hook": "21a9-fe0f", "arrow_right_hook": "21aa-fe0f", "arrow_heading_up": "2934-fe0f", "arrow_heading_down": "2935-fe0f", "arrows_clockwise": "1f503", "arrows_counterclockwise": "1f504", "back": "1f519", "end": "1f51a", "on": "1f51b", "soon": "1f51c", "top": "1f51d", "place_of_worship": "1f6d0", "atom_symbol": "269b-fe0f", "om_symbol": "1f549-fe0f", "om": "1f549-fe0f", "star_of_david": "2721-fe0f", "wheel_of_dharma": "2638-fe0f", "yin_yang": "262f-fe0f", "latin_cross": "271d-fe0f", "orthodox_cross": "2626-fe0f", "star_and_crescent": "262a-fe0f", "peace_symbol": "262e-fe0f", "menorah_with_nine_branches": "1f54e", "menorah": "1f54e", "six_pointed_star": "1f52f", "aries": "2648", "taurus": "2649", "gemini": "264a", "cancer": "264b", "leo": "264c", "virgo": "264d", "libra": "264e", "scorpius": "264f", "sagittarius": "2650", "capricorn": "2651", "aquarius": "2652", "pisces": "2653", "ophiuchus": "26ce", "twisted_rightwards_arrows": "1f500", "repeat": "1f501", "repeat_one": "1f502", "arrow_forward": "25b6-fe0f", "fast_forward": "23e9", "black_right_pointing_double_triangle_with_vertical_bar": "23ed-fe0f", "next_track_button": "23ed-fe0f", "black_right_pointing_triangle_with_double_vertical_bar": "23ef-fe0f", "play_or_pause_button": "23ef-fe0f", "arrow_backward": "25c0-fe0f", "rewind": "23ea", "black_left_pointing_double_triangle_with_vertical_bar": "23ee-fe0f", "previous_track_button": "23ee-fe0f", "arrow_up_small": "1f53c", "arrow_double_up": "23eb", "arrow_down_small": "1f53d", "arrow_double_down": "23ec", "double_vertical_bar": "23f8-fe0f", "pause_button": "23f8-fe0f", "black_square_for_stop": "23f9-fe0f", "stop_button": "23f9-fe0f", "black_circle_for_record": "23fa-fe0f", "record_button": "23fa-fe0f", "eject": "23cf-fe0f", "cinema": "1f3a6", "low_brightness": "1f505", "high_brightness": "1f506", "signal_strength": "1f4f6", "vibration_mode": "1f4f3", "mobile_phone_off": "1f4f4", "female_sign": "2640-fe0f", "male_sign": "2642-fe0f", "transgender_symbol": "26a7-fe0f", "heavy_multiplication_x": "2716-fe0f", "heavy_plus_sign": "2795", "heavy_minus_sign": "2796", "heavy_division_sign": "2797", "infinity": "267e-fe0f", "bangbang": "203c-fe0f", "interrobang": "2049-fe0f", "question": "2753", "grey_question": "2754", "grey_exclamation": "2755", "exclamation": "2757", "heavy_exclamation_mark": "2757", "wavy_dash": "3030-fe0f", "currency_exchange": "1f4b1", "heavy_dollar_sign": "1f4b2", "medical_symbol": "2695-fe0f", "staff_of_aesculapius": "2695-fe0f", "recycle": "267b-fe0f", "fleur_de_lis": "269c-fe0f", "trident": "1f531", "name_badge": "1f4db", "beginner": "1f530", "o": "2b55", "white_check_mark": "2705", "ballot_box_with_check": "2611-fe0f", "heavy_check_mark": "2714-fe0f", "x": "274c", "negative_squared_cross_mark": "274e", "curly_loop": "27b0", "loop": "27bf", "part_alternation_mark": "303d-fe0f", "eight_spoked_asterisk": "2733-fe0f", "eight_pointed_black_star": "2734-fe0f", "sparkle": "2747-fe0f", "copyright": "00a9-fe0f", "registered": "00ae-fe0f", "tm": "2122-fe0f", "hash": "0023-fe0f-20e3", "keycap_star": "002a-fe0f-20e3", "asterisk": "002a-fe0f-20e3", "zero": "0030-fe0f-20e3", "one": "0031-fe0f-20e3", "two": "0032-fe0f-20e3", "three": "0033-fe0f-20e3", "four": "0034-fe0f-20e3", "five": "0035-fe0f-20e3", "six": "0036-fe0f-20e3", "seven": "0037-fe0f-20e3", "eight": "0038-fe0f-20e3", "nine": "0039-fe0f-20e3", "keycap_ten": "1f51f", "capital_abcd": "1f520", "abcd": "1f521", "1234": "1f522", "symbols": "1f523", "abc": "1f524", "a": "1f170-fe0f", "ab": "1f18e", "b": "1f171-fe0f", "cl": "1f191", "cool": "1f192", "free": "1f193", "information_source": "2139-fe0f", "id": "1f194", "m": "24c2-fe0f", "new": "1f195", "ng": "1f196", "o2": "1f17e-fe0f", "ok": "1f197", "parking": "1f17f-fe0f", "sos": "1f198", "up": "1f199", "vs": "1f19a", "koko": "1f201", "sa": "1f202-fe0f", "u6708": "1f237-fe0f", "u6709": "1f236", "u6307": "1f22f", "ideograph_advantage": "1f250", "u5272": "1f239", "u7121": "1f21a", "u7981": "1f232", "accept": "1f251", "u7533": "1f238", "u5408": "1f234", "u7a7a": "1f233", "congratulations": "3297-fe0f", "secret": "3299-fe0f", "u55b6": "1f23a", "u6e80": "1f235", "red_circle": "1f534", "large_orange_circle": "1f7e0", "large_yellow_circle": "1f7e1", "large_green_circle": "1f7e2", "large_blue_circle": "1f535", "large_purple_circle": "1f7e3", "large_brown_circle": "1f7e4", "black_circle": "26ab", "white_circle": "26aa", "large_red_square": "1f7e5", "large_orange_square": "1f7e7", "large_yellow_square": "1f7e8", "large_green_square": "1f7e9", "large_blue_square": "1f7e6", "large_purple_square": "1f7ea", "large_brown_square": "1f7eb", "black_large_square": "2b1b", "white_large_square": "2b1c", "black_medium_square": "25fc-fe0f", "white_medium_square": "25fb-fe0f", "black_medium_small_square": "25fe", "white_medium_small_square": "25fd", "black_small_square": "25aa-fe0f", "white_small_square": "25ab-fe0f", "large_orange_diamond": "1f536", "large_blue_diamond": "1f537", "small_orange_diamond": "1f538", "small_blue_diamond": "1f539", "small_red_triangle": "1f53a", "small_red_triangle_down": "1f53b", "diamond_shape_with_a_dot_inside": "1f4a0", "radio_button": "1f518", "white_square_button": "1f533", "black_square_button": "1f532", "checkered_flag": "1f3c1", "triangular_flag_on_post": "1f6a9", "crossed_flags": "1f38c", "waving_black_flag": "1f3f4", "black_flag": "1f3f4", "waving_white_flag": "1f3f3-fe0f", "white_flag": "1f3f3-fe0f", "rainbow-flag": "1f3f3-fe0f-200d-1f308", "rainbow_flag": "1f3f3-fe0f-200d-1f308", "transgender_flag": "1f3f3-fe0f-200d-26a7-fe0f", "pirate_flag": "1f3f4-200d-2620-fe0f", "flag-ac": "1f1e6-1f1e8", "flag-ad": "1f1e6-1f1e9", "andorra": "1f1e6-1f1e9", "flag-ae": "1f1e6-1f1ea", "united_arab_emirates": "1f1e6-1f1ea", "flag-af": "1f1e6-1f1eb", "afghanistan": "1f1e6-1f1eb", "flag-ag": "1f1e6-1f1ec", "antigua_barbuda": "1f1e6-1f1ec", "flag-ai": "1f1e6-1f1ee", "anguilla": "1f1e6-1f1ee", "flag-al": "1f1e6-1f1f1", "albania": "1f1e6-1f1f1", "flag-am": "1f1e6-1f1f2", "armenia": "1f1e6-1f1f2", "flag-ao": "1f1e6-1f1f4", "angola": "1f1e6-1f1f4", "flag-aq": "1f1e6-1f1f6", "antarctica": "1f1e6-1f1f6", "flag-ar": "1f1e6-1f1f7", "argentina": "1f1e6-1f1f7", "flag-as": "1f1e6-1f1f8", "american_samoa": "1f1e6-1f1f8", "flag-at": "1f1e6-1f1f9", "austria": "1f1e6-1f1f9", "flag-au": "1f1e6-1f1fa", "australia": "1f1e6-1f1fa", "flag-aw": "1f1e6-1f1fc", "aruba": "1f1e6-1f1fc", "flag-ax": "1f1e6-1f1fd", "aland_islands": "1f1e6-1f1fd", "flag-az": "1f1e6-1f1ff", "azerbaijan": "1f1e6-1f1ff", "flag-ba": "1f1e7-1f1e6", "bosnia_herzegovina": "1f1e7-1f1e6", "flag-bb": "1f1e7-1f1e7", "barbados": "1f1e7-1f1e7", "flag-bd": "1f1e7-1f1e9", "bangladesh": "1f1e7-1f1e9", "flag-be": "1f1e7-1f1ea", "belgium": "1f1e7-1f1ea", "flag-bf": "1f1e7-1f1eb", "burkina_faso": "1f1e7-1f1eb", "flag-bg": "1f1e7-1f1ec", "bulgaria": "1f1e7-1f1ec", "flag-bh": "1f1e7-1f1ed", "bahrain": "1f1e7-1f1ed", "flag-bi": "1f1e7-1f1ee", "burundi": "1f1e7-1f1ee", "flag-bj": "1f1e7-1f1ef", "benin": "1f1e7-1f1ef", "flag-bl": "1f1e7-1f1f1", "st_barthelemy": "1f1e7-1f1f1", "flag-bm": "1f1e7-1f1f2", "bermuda": "1f1e7-1f1f2", "flag-bn": "1f1e7-1f1f3", "brunei": "1f1e7-1f1f3", "flag-bo": "1f1e7-1f1f4", "bolivia": "1f1e7-1f1f4", "flag-bq": "1f1e7-1f1f6", "caribbean_netherlands": "1f1e7-1f1f6", "flag-br": "1f1e7-1f1f7", "brazil": "1f1e7-1f1f7", "flag-bs": "1f1e7-1f1f8", "bahamas": "1f1e7-1f1f8", "flag-bt": "1f1e7-1f1f9", "bhutan": "1f1e7-1f1f9", "flag-bv": "1f1e7-1f1fb", "flag-bw": "1f1e7-1f1fc", "botswana": "1f1e7-1f1fc", "flag-by": "1f1e7-1f1fe", "belarus": "1f1e7-1f1fe", "flag-bz": "1f1e7-1f1ff", "belize": "1f1e7-1f1ff", "flag-ca": "1f1e8-1f1e6", "ca": "1f1e8-1f1e6", "canada": "1f1e8-1f1e6", "flag-cc": "1f1e8-1f1e8", "cocos_islands": "1f1e8-1f1e8", "flag-cd": "1f1e8-1f1e9", "congo_kinshasa": "1f1e8-1f1e9", "flag-cf": "1f1e8-1f1eb", "central_african_republic": "1f1e8-1f1eb", "flag-cg": "1f1e8-1f1ec", "congo_brazzaville": "1f1e8-1f1ec", "flag-ch": "1f1e8-1f1ed", "switzerland": "1f1e8-1f1ed", "flag-ci": "1f1e8-1f1ee", "cote_divoire": "1f1e8-1f1ee", "flag-ck": "1f1e8-1f1f0", "cook_islands": "1f1e8-1f1f0", "flag-cl": "1f1e8-1f1f1", "chile": "1f1e8-1f1f1", "flag-cm": "1f1e8-1f1f2", "cameroon": "1f1e8-1f1f2", "cn": "1f1e8-1f1f3", "flag-cn": "1f1e8-1f1f3", "flag-co": "1f1e8-1f1f4", "colombia": "1f1e8-1f1f4", "flag-cp": "1f1e8-1f1f5", "flag-cr": "1f1e8-1f1f7", "costa_rica": "1f1e8-1f1f7", "flag-cu": "1f1e8-1f1fa", "cuba": "1f1e8-1f1fa", "flag-cv": "1f1e8-1f1fb", "cape_verde": "1f1e8-1f1fb", "flag-cw": "1f1e8-1f1fc", "curacao": "1f1e8-1f1fc", "flag-cx": "1f1e8-1f1fd", "christmas_island": "1f1e8-1f1fd", "flag-cy": "1f1e8-1f1fe", "cyprus": "1f1e8-1f1fe", "flag-cz": "1f1e8-1f1ff", "czech_republic": "1f1e8-1f1ff", "de": "1f1e9-1f1ea", "flag-de": "1f1e9-1f1ea", "flag-dg": "1f1e9-1f1ec", "flag-dj": "1f1e9-1f1ef", "djibouti": "1f1e9-1f1ef", "flag-dk": "1f1e9-1f1f0", "denmark": "1f1e9-1f1f0", "flag-dm": "1f1e9-1f1f2", "dominica": "1f1e9-1f1f2", "flag-do": "1f1e9-1f1f4", "dominican_republic": "1f1e9-1f1f4", "flag-dz": "1f1e9-1f1ff", "algeria": "1f1e9-1f1ff", "flag-ea": "1f1ea-1f1e6", "flag-ec": "1f1ea-1f1e8", "ecuador": "1f1ea-1f1e8", "flag-ee": "1f1ea-1f1ea", "estonia": "1f1ea-1f1ea", "flag-eg": "1f1ea-1f1ec", "egypt": "1f1ea-1f1ec", "flag-eh": "1f1ea-1f1ed", "western_sahara": "1f1ea-1f1ed", "flag-er": "1f1ea-1f1f7", "eritrea": "1f1ea-1f1f7", "es": "1f1ea-1f1f8", "flag-es": "1f1ea-1f1f8", "flag-et": "1f1ea-1f1f9", "ethiopia": "1f1ea-1f1f9", "flag-eu": "1f1ea-1f1fa", "eu": "1f1ea-1f1fa", "european_union": "1f1ea-1f1fa", "flag-fi": "1f1eb-1f1ee", "finland": "1f1eb-1f1ee", "flag-fj": "1f1eb-1f1ef", "fiji": "1f1eb-1f1ef", "flag-fk": "1f1eb-1f1f0", "falkland_islands": "1f1eb-1f1f0", "flag-fm": "1f1eb-1f1f2", "micronesia": "1f1eb-1f1f2", "flag-fo": "1f1eb-1f1f4", "faroe_islands": "1f1eb-1f1f4", "fr": "1f1eb-1f1f7", "flag-fr": "1f1eb-1f1f7", "flag-ga": "1f1ec-1f1e6", "gabon": "1f1ec-1f1e6", "gb": "1f1ec-1f1e7", "uk": "1f1ec-1f1e7", "flag-gb": "1f1ec-1f1e7", "flag-gd": "1f1ec-1f1e9", "grenada": "1f1ec-1f1e9", "flag-ge": "1f1ec-1f1ea", "georgia": "1f1ec-1f1ea", "flag-gf": "1f1ec-1f1eb", "french_guiana": "1f1ec-1f1eb", "flag-gg": "1f1ec-1f1ec", "guernsey": "1f1ec-1f1ec", "flag-gh": "1f1ec-1f1ed", "ghana": "1f1ec-1f1ed", "flag-gi": "1f1ec-1f1ee", "gibraltar": "1f1ec-1f1ee", "flag-gl": "1f1ec-1f1f1", "greenland": "1f1ec-1f1f1", "flag-gm": "1f1ec-1f1f2", "gambia": "1f1ec-1f1f2", "flag-gn": "1f1ec-1f1f3", "guinea": "1f1ec-1f1f3", "flag-gp": "1f1ec-1f1f5", "guadeloupe": "1f1ec-1f1f5", "flag-gq": "1f1ec-1f1f6", "equatorial_guinea": "1f1ec-1f1f6", "flag-gr": "1f1ec-1f1f7", "greece": "1f1ec-1f1f7", "flag-gs": "1f1ec-1f1f8", "south_georgia_south_sandwich_islands": "1f1ec-1f1f8", "flag-gt": "1f1ec-1f1f9", "guatemala": "1f1ec-1f1f9", "flag-gu": "1f1ec-1f1fa", "guam": "1f1ec-1f1fa", "flag-gw": "1f1ec-1f1fc", "guinea_bissau": "1f1ec-1f1fc", "flag-gy": "1f1ec-1f1fe", "guyana": "1f1ec-1f1fe", "flag-hk": "1f1ed-1f1f0", "hong_kong": "1f1ed-1f1f0", "flag-hm": "1f1ed-1f1f2", "flag-hn": "1f1ed-1f1f3", "honduras": "1f1ed-1f1f3", "flag-hr": "1f1ed-1f1f7", "croatia": "1f1ed-1f1f7", "flag-ht": "1f1ed-1f1f9", "haiti": "1f1ed-1f1f9", "flag-hu": "1f1ed-1f1fa", "hungary": "1f1ed-1f1fa", "flag-ic": "1f1ee-1f1e8", "canary_islands": "1f1ee-1f1e8", "flag-id": "1f1ee-1f1e9", "indonesia": "1f1ee-1f1e9", "flag-ie": "1f1ee-1f1ea", "ireland": "1f1ee-1f1ea", "flag-il": "1f1ee-1f1f1", "israel": "1f1ee-1f1f1", "flag-im": "1f1ee-1f1f2", "isle_of_man": "1f1ee-1f1f2", "flag-in": "1f1ee-1f1f3", "india": "1f1ee-1f1f3", "flag-io": "1f1ee-1f1f4", "british_indian_ocean_territory": "1f1ee-1f1f4", "flag-iq": "1f1ee-1f1f6", "iraq": "1f1ee-1f1f6", "flag-ir": "1f1ee-1f1f7", "iran": "1f1ee-1f1f7", "flag-is": "1f1ee-1f1f8", "iceland": "1f1ee-1f1f8", "it": "1f1ee-1f1f9", "flag-it": "1f1ee-1f1f9", "flag-je": "1f1ef-1f1ea", "jersey": "1f1ef-1f1ea", "flag-jm": "1f1ef-1f1f2", "jamaica": "1f1ef-1f1f2", "flag-jo": "1f1ef-1f1f4", "jordan": "1f1ef-1f1f4", "jp": "1f1ef-1f1f5", "flag-jp": "1f1ef-1f1f5", "flag-ke": "1f1f0-1f1ea", "kenya": "1f1f0-1f1ea", "flag-kg": "1f1f0-1f1ec", "kyrgyzstan": "1f1f0-1f1ec", "flag-kh": "1f1f0-1f1ed", "cambodia": "1f1f0-1f1ed", "flag-ki": "1f1f0-1f1ee", "kiribati": "1f1f0-1f1ee", "flag-km": "1f1f0-1f1f2", "comoros": "1f1f0-1f1f2", "flag-kn": "1f1f0-1f1f3", "st_kitts_nevis": "1f1f0-1f1f3", "flag-kp": "1f1f0-1f1f5", "north_korea": "1f1f0-1f1f5", "kr": "1f1f0-1f1f7", "flag-kr": "1f1f0-1f1f7", "flag-kw": "1f1f0-1f1fc", "kuwait": "1f1f0-1f1fc", "flag-ky": "1f1f0-1f1fe", "cayman_islands": "1f1f0-1f1fe", "flag-kz": "1f1f0-1f1ff", "kazakhstan": "1f1f0-1f1ff", "flag-la": "1f1f1-1f1e6", "laos": "1f1f1-1f1e6", "flag-lb": "1f1f1-1f1e7", "lebanon": "1f1f1-1f1e7", "flag-lc": "1f1f1-1f1e8", "st_lucia": "1f1f1-1f1e8", "flag-li": "1f1f1-1f1ee", "liechtenstein": "1f1f1-1f1ee", "flag-lk": "1f1f1-1f1f0", "sri_lanka": "1f1f1-1f1f0", "flag-lr": "1f1f1-1f1f7", "liberia": "1f1f1-1f1f7", "flag-ls": "1f1f1-1f1f8", "lesotho": "1f1f1-1f1f8", "flag-lt": "1f1f1-1f1f9", "lithuania": "1f1f1-1f1f9", "flag-lu": "1f1f1-1f1fa", "luxembourg": "1f1f1-1f1fa", "flag-lv": "1f1f1-1f1fb", "latvia": "1f1f1-1f1fb", "flag-ly": "1f1f1-1f1fe", "libya": "1f1f1-1f1fe", "flag-ma": "1f1f2-1f1e6", "morocco": "1f1f2-1f1e6", "flag-mc": "1f1f2-1f1e8", "monaco": "1f1f2-1f1e8", "flag-md": "1f1f2-1f1e9", "moldova": "1f1f2-1f1e9", "flag-me": "1f1f2-1f1ea", "montenegro": "1f1f2-1f1ea", "flag-mf": "1f1f2-1f1eb", "flag-mg": "1f1f2-1f1ec", "madagascar": "1f1f2-1f1ec", "flag-mh": "1f1f2-1f1ed", "marshall_islands": "1f1f2-1f1ed", "flag-mk": "1f1f2-1f1f0", "macedonia": "1f1f2-1f1f0", "flag-ml": "1f1f2-1f1f1", "mali": "1f1f2-1f1f1", "flag-mm": "1f1f2-1f1f2", "myanmar": "1f1f2-1f1f2", "flag-mn": "1f1f2-1f1f3", "mongolia": "1f1f2-1f1f3", "flag-mo": "1f1f2-1f1f4", "macau": "1f1f2-1f1f4", "flag-mp": "1f1f2-1f1f5", "northern_mariana_islands": "1f1f2-1f1f5", "flag-mq": "1f1f2-1f1f6", "martinique": "1f1f2-1f1f6", "flag-mr": "1f1f2-1f1f7", "mauritania": "1f1f2-1f1f7", "flag-ms": "1f1f2-1f1f8", "montserrat": "1f1f2-1f1f8", "flag-mt": "1f1f2-1f1f9", "malta": "1f1f2-1f1f9", "flag-mu": "1f1f2-1f1fa", "mauritius": "1f1f2-1f1fa", "flag-mv": "1f1f2-1f1fb", "maldives": "1f1f2-1f1fb", "flag-mw": "1f1f2-1f1fc", "malawi": "1f1f2-1f1fc", "flag-mx": "1f1f2-1f1fd", "mexico": "1f1f2-1f1fd", "flag-my": "1f1f2-1f1fe", "malaysia": "1f1f2-1f1fe", "flag-mz": "1f1f2-1f1ff", "mozambique": "1f1f2-1f1ff", "flag-na": "1f1f3-1f1e6", "namibia": "1f1f3-1f1e6", "flag-nc": "1f1f3-1f1e8", "new_caledonia": "1f1f3-1f1e8", "flag-ne": "1f1f3-1f1ea", "niger": "1f1f3-1f1ea", "flag-nf": "1f1f3-1f1eb", "norfolk_island": "1f1f3-1f1eb", "flag-ng": "1f1f3-1f1ec", "nigeria": "1f1f3-1f1ec", "flag-ni": "1f1f3-1f1ee", "nicaragua": "1f1f3-1f1ee", "flag-nl": "1f1f3-1f1f1", "netherlands": "1f1f3-1f1f1", "flag-no": "1f1f3-1f1f4", "norway": "1f1f3-1f1f4", "flag-np": "1f1f3-1f1f5", "nepal": "1f1f3-1f1f5", "flag-nr": "1f1f3-1f1f7", "nauru": "1f1f3-1f1f7", "flag-nu": "1f1f3-1f1fa", "niue": "1f1f3-1f1fa", "flag-nz": "1f1f3-1f1ff", "new_zealand": "1f1f3-1f1ff", "flag-om": "1f1f4-1f1f2", "oman": "1f1f4-1f1f2", "flag-pa": "1f1f5-1f1e6", "panama": "1f1f5-1f1e6", "flag-pe": "1f1f5-1f1ea", "peru": "1f1f5-1f1ea", "flag-pf": "1f1f5-1f1eb", "french_polynesia": "1f1f5-1f1eb", "flag-pg": "1f1f5-1f1ec", "papua_new_guinea": "1f1f5-1f1ec", "flag-ph": "1f1f5-1f1ed", "philippines": "1f1f5-1f1ed", "flag-pk": "1f1f5-1f1f0", "pakistan": "1f1f5-1f1f0", "pk": "1f1f5-1f1f0", "flag-pl": "1f1f5-1f1f1", "poland": "1f1f5-1f1f1", "flag-pm": "1f1f5-1f1f2", "st_pierre_miquelon": "1f1f5-1f1f2", "flag-pn": "1f1f5-1f1f3", "pitcairn_islands": "1f1f5-1f1f3", "flag-pr": "1f1f5-1f1f7", "puerto_rico": "1f1f5-1f1f7", "flag-ps": "1f1f5-1f1f8", "palestinian_territories": "1f1f5-1f1f8", "flag-pt": "1f1f5-1f1f9", "portugal": "1f1f5-1f1f9", "flag-pw": "1f1f5-1f1fc", "palau": "1f1f5-1f1fc", "flag-py": "1f1f5-1f1fe", "paraguay": "1f1f5-1f1fe", "flag-qa": "1f1f6-1f1e6", "qatar": "1f1f6-1f1e6", "flag-re": "1f1f7-1f1ea", "reunion": "1f1f7-1f1ea", "flag-ro": "1f1f7-1f1f4", "romania": "1f1f7-1f1f4", "flag-rs": "1f1f7-1f1f8", "serbia": "1f1f7-1f1f8", "ru": "1f1f7-1f1fa", "flag-ru": "1f1f7-1f1fa", "flag-rw": "1f1f7-1f1fc", "rwanda": "1f1f7-1f1fc", "flag-sa": "1f1f8-1f1e6", "saudi_arabia": "1f1f8-1f1e6", "flag-sb": "1f1f8-1f1e7", "solomon_islands": "1f1f8-1f1e7", "flag-sc": "1f1f8-1f1e8", "seychelles": "1f1f8-1f1e8", "flag-sd": "1f1f8-1f1e9", "sudan": "1f1f8-1f1e9", "flag-se": "1f1f8-1f1ea", "sweden": "1f1f8-1f1ea", "flag-sg": "1f1f8-1f1ec", "singapore": "1f1f8-1f1ec", "flag-sh": "1f1f8-1f1ed", "st_helena": "1f1f8-1f1ed", "flag-si": "1f1f8-1f1ee", "slovenia": "1f1f8-1f1ee", "flag-sj": "1f1f8-1f1ef", "flag-sk": "1f1f8-1f1f0", "slovakia": "1f1f8-1f1f0", "flag-sl": "1f1f8-1f1f1", "sierra_leone": "1f1f8-1f1f1", "flag-sm": "1f1f8-1f1f2", "san_marino": "1f1f8-1f1f2", "flag-sn": "1f1f8-1f1f3", "senegal": "1f1f8-1f1f3", "flag-so": "1f1f8-1f1f4", "somalia": "1f1f8-1f1f4", "flag-sr": "1f1f8-1f1f7", "suriname": "1f1f8-1f1f7", "flag-ss": "1f1f8-1f1f8", "south_sudan": "1f1f8-1f1f8", "flag-st": "1f1f8-1f1f9", "sao_tome_principe": "1f1f8-1f1f9", "flag-sv": "1f1f8-1f1fb", "el_salvador": "1f1f8-1f1fb", "flag-sx": "1f1f8-1f1fd", "sint_maarten": "1f1f8-1f1fd", "flag-sy": "1f1f8-1f1fe", "syria": "1f1f8-1f1fe", "flag-sz": "1f1f8-1f1ff", "swaziland": "1f1f8-1f1ff", "flag-ta": "1f1f9-1f1e6", "flag-tc": "1f1f9-1f1e8", "turks_caicos_islands": "1f1f9-1f1e8", "flag-td": "1f1f9-1f1e9", "chad": "1f1f9-1f1e9", "flag-tf": "1f1f9-1f1eb", "french_southern_territories": "1f1f9-1f1eb", "flag-tg": "1f1f9-1f1ec", "togo": "1f1f9-1f1ec", "flag-th": "1f1f9-1f1ed", "thailand": "1f1f9-1f1ed", "flag-tj": "1f1f9-1f1ef", "tajikistan": "1f1f9-1f1ef", "flag-tk": "1f1f9-1f1f0", "tokelau": "1f1f9-1f1f0", "flag-tl": "1f1f9-1f1f1", "timor_leste": "1f1f9-1f1f1", "flag-tm": "1f1f9-1f1f2", "turkmenistan": "1f1f9-1f1f2", "flag-tn": "1f1f9-1f1f3", "tunisia": "1f1f9-1f1f3", "flag-to": "1f1f9-1f1f4", "tonga": "1f1f9-1f1f4", "flag-tr": "1f1f9-1f1f7", "tr": "1f1f9-1f1f7", "flag-tt": "1f1f9-1f1f9", "trinidad_tobago": "1f1f9-1f1f9", "flag-tv": "1f1f9-1f1fb", "tuvalu": "1f1f9-1f1fb", "flag-tw": "1f1f9-1f1fc", "taiwan": "1f1f9-1f1fc", "flag-tz": "1f1f9-1f1ff", "tanzania": "1f1f9-1f1ff", "flag-ua": "1f1fa-1f1e6", "ukraine": "1f1fa-1f1e6", "flag-ug": "1f1fa-1f1ec", "uganda": "1f1fa-1f1ec", "flag-um": "1f1fa-1f1f2", "flag-un": "1f1fa-1f1f3", "us": "1f1fa-1f1f8", "flag-us": "1f1fa-1f1f8", "flag-uy": "1f1fa-1f1fe", "uruguay": "1f1fa-1f1fe", "flag-uz": "1f1fa-1f1ff", "uzbekistan": "1f1fa-1f1ff", "flag-va": "1f1fb-1f1e6", "vatican_city": "1f1fb-1f1e6", "flag-vc": "1f1fb-1f1e8", "st_vincent_grenadines": "1f1fb-1f1e8", "flag-ve": "1f1fb-1f1ea", "venezuela": "1f1fb-1f1ea", "flag-vg": "1f1fb-1f1ec", "british_virgin_islands": "1f1fb-1f1ec", "flag-vi": "1f1fb-1f1ee", "us_virgin_islands": "1f1fb-1f1ee", "flag-vn": "1f1fb-1f1f3", "vietnam": "1f1fb-1f1f3", "flag-vu": "1f1fb-1f1fa", "vanuatu": "1f1fb-1f1fa", "flag-wf": "1f1fc-1f1eb", "wallis_futuna": "1f1fc-1f1eb", "flag-ws": "1f1fc-1f1f8", "samoa": "1f1fc-1f1f8", "flag-xk": "1f1fd-1f1f0", "kosovo": "1f1fd-1f1f0", "flag-ye": "1f1fe-1f1ea", "yemen": "1f1fe-1f1ea", "flag-yt": "1f1fe-1f1f9", "mayotte": "1f1fe-1f1f9", "flag-za": "1f1ff-1f1e6", "south_africa": "1f1ff-1f1e6", "za": "1f1ff-1f1e6", "flag-zm": "1f1ff-1f1f2", "zambia": "1f1ff-1f1f2", "flag-zw": "1f1ff-1f1fc", "zimbabwe": "1f1ff-1f1fc", "flag-england": "1f3f4-e0067-e0062-e0065-e006e-e0067-e007f", "flag-scotland": "1f3f4-e0067-e0062-e0073-e0063-e0074-e007f", "flag-wales": "1f3f4-e0067-e0062-e0077-e006c-e0073-e007f", "santa_light_skin_tone": "1f385-1f3fb", "santa_medium_light_skin_tone": "1f385-1f3fc", "santa_medium_skin_tone": "1f385-1f3fd", "santa_medium_dark_skin_tone": "1f385-1f3fe", "santa_dark_skin_tone": "1f385-1f3ff", "snowboarder_light_skin_tone": "1f3c2-1f3fb", "snowboarder_medium_light_skin_tone": "1f3c2-1f3fc", "snowboarder_medium_skin_tone": "1f3c2-1f3fd", "snowboarder_medium_dark_skin_tone": "1f3c2-1f3fe", "snowboarder_dark_skin_tone": "1f3c2-1f3ff", "woman-running_light_skin_tone": "1f3c3-1f3fb-200d-2640-fe0f", "running_woman_light_skin_tone": "1f3c3-1f3fb-200d-2640-fe0f", "woman-running_medium_light_skin_tone": "1f3c3-1f3fc-200d-2640-fe0f", "running_woman_medium_light_skin_tone": "1f3c3-1f3fc-200d-2640-fe0f", "woman-running_medium_skin_tone": "1f3c3-1f3fd-200d-2640-fe0f", "running_woman_medium_skin_tone": "1f3c3-1f3fd-200d-2640-fe0f", "woman-running_medium_dark_skin_tone": "1f3c3-1f3fe-200d-2640-fe0f", "running_woman_medium_dark_skin_tone": "1f3c3-1f3fe-200d-2640-fe0f", "woman-running_dark_skin_tone": "1f3c3-1f3ff-200d-2640-fe0f", "running_woman_dark_skin_tone": "1f3c3-1f3ff-200d-2640-fe0f", "man-running_light_skin_tone": "1f3c3-1f3fb-200d-2642-fe0f", "running_man_light_skin_tone": "1f3c3-1f3fb-200d-2642-fe0f", "man-running_medium_light_skin_tone": "1f3c3-1f3fc-200d-2642-fe0f", "running_man_medium_light_skin_tone": "1f3c3-1f3fc-200d-2642-fe0f", "man-running_medium_skin_tone": "1f3c3-1f3fd-200d-2642-fe0f", "running_man_medium_skin_tone": "1f3c3-1f3fd-200d-2642-fe0f", "man-running_medium_dark_skin_tone": "1f3c3-1f3fe-200d-2642-fe0f", "running_man_medium_dark_skin_tone": "1f3c3-1f3fe-200d-2642-fe0f", "man-running_dark_skin_tone": "1f3c3-1f3ff-200d-2642-fe0f", "running_man_dark_skin_tone": "1f3c3-1f3ff-200d-2642-fe0f", "runner_light_skin_tone": "1f3c3-1f3fb", "running_light_skin_tone": "1f3c3-1f3fb", "runner_medium_light_skin_tone": "1f3c3-1f3fc", "running_medium_light_skin_tone": "1f3c3-1f3fc", "runner_medium_skin_tone": "1f3c3-1f3fd", "running_medium_skin_tone": "1f3c3-1f3fd", "runner_medium_dark_skin_tone": "1f3c3-1f3fe", "running_medium_dark_skin_tone": "1f3c3-1f3fe", "runner_dark_skin_tone": "1f3c3-1f3ff", "running_dark_skin_tone": "1f3c3-1f3ff", "woman-surfing_light_skin_tone": "1f3c4-1f3fb-200d-2640-fe0f", "surfing_woman_light_skin_tone": "1f3c4-1f3fb-200d-2640-fe0f", "woman-surfing_medium_light_skin_tone": "1f3c4-1f3fc-200d-2640-fe0f", "surfing_woman_medium_light_skin_tone": "1f3c4-1f3fc-200d-2640-fe0f", "woman-surfing_medium_skin_tone": "1f3c4-1f3fd-200d-2640-fe0f", "surfing_woman_medium_skin_tone": "1f3c4-1f3fd-200d-2640-fe0f", "woman-surfing_medium_dark_skin_tone": "1f3c4-1f3fe-200d-2640-fe0f", "surfing_woman_medium_dark_skin_tone": "1f3c4-1f3fe-200d-2640-fe0f", "woman-surfing_dark_skin_tone": "1f3c4-1f3ff-200d-2640-fe0f", "surfing_woman_dark_skin_tone": "1f3c4-1f3ff-200d-2640-fe0f", "man-surfing_light_skin_tone": "1f3c4-1f3fb-200d-2642-fe0f", "surfing_man_light_skin_tone": "1f3c4-1f3fb-200d-2642-fe0f", "man-surfing_medium_light_skin_tone": "1f3c4-1f3fc-200d-2642-fe0f", "surfing_man_medium_light_skin_tone": "1f3c4-1f3fc-200d-2642-fe0f", "man-surfing_medium_skin_tone": "1f3c4-1f3fd-200d-2642-fe0f", "surfing_man_medium_skin_tone": "1f3c4-1f3fd-200d-2642-fe0f", "man-surfing_medium_dark_skin_tone": "1f3c4-1f3fe-200d-2642-fe0f", "surfing_man_medium_dark_skin_tone": "1f3c4-1f3fe-200d-2642-fe0f", "man-surfing_dark_skin_tone": "1f3c4-1f3ff-200d-2642-fe0f", "surfing_man_dark_skin_tone": "1f3c4-1f3ff-200d-2642-fe0f", "surfer_light_skin_tone": "1f3c4-1f3fb", "surfer_medium_light_skin_tone": "1f3c4-1f3fc", "surfer_medium_skin_tone": "1f3c4-1f3fd", "surfer_medium_dark_skin_tone": "1f3c4-1f3fe", "surfer_dark_skin_tone": "1f3c4-1f3ff", "horse_racing_light_skin_tone": "1f3c7-1f3fb", "horse_racing_medium_light_skin_tone": "1f3c7-1f3fc", "horse_racing_medium_skin_tone": "1f3c7-1f3fd", "horse_racing_medium_dark_skin_tone": "1f3c7-1f3fe", "horse_racing_dark_skin_tone": "1f3c7-1f3ff", "woman-swimming_light_skin_tone": "1f3ca-1f3fb-200d-2640-fe0f", "swimming_woman_light_skin_tone": "1f3ca-1f3fb-200d-2640-fe0f", "woman-swimming_medium_light_skin_tone": "1f3ca-1f3fc-200d-2640-fe0f", "swimming_woman_medium_light_skin_tone": "1f3ca-1f3fc-200d-2640-fe0f", "woman-swimming_medium_skin_tone": "1f3ca-1f3fd-200d-2640-fe0f", "swimming_woman_medium_skin_tone": "1f3ca-1f3fd-200d-2640-fe0f", "woman-swimming_medium_dark_skin_tone": "1f3ca-1f3fe-200d-2640-fe0f", "swimming_woman_medium_dark_skin_tone": "1f3ca-1f3fe-200d-2640-fe0f", "woman-swimming_dark_skin_tone": "1f3ca-1f3ff-200d-2640-fe0f", "swimming_woman_dark_skin_tone": "1f3ca-1f3ff-200d-2640-fe0f", "man-swimming_light_skin_tone": "1f3ca-1f3fb-200d-2642-fe0f", "swimming_man_light_skin_tone": "1f3ca-1f3fb-200d-2642-fe0f", "man-swimming_medium_light_skin_tone": "1f3ca-1f3fc-200d-2642-fe0f", "swimming_man_medium_light_skin_tone": "1f3ca-1f3fc-200d-2642-fe0f", "man-swimming_medium_skin_tone": "1f3ca-1f3fd-200d-2642-fe0f", "swimming_man_medium_skin_tone": "1f3ca-1f3fd-200d-2642-fe0f", "man-swimming_medium_dark_skin_tone": "1f3ca-1f3fe-200d-2642-fe0f", "swimming_man_medium_dark_skin_tone": "1f3ca-1f3fe-200d-2642-fe0f", "man-swimming_dark_skin_tone": "1f3ca-1f3ff-200d-2642-fe0f", "swimming_man_dark_skin_tone": "1f3ca-1f3ff-200d-2642-fe0f", "swimmer_light_skin_tone": "1f3ca-1f3fb", "swimmer_medium_light_skin_tone": "1f3ca-1f3fc", "swimmer_medium_skin_tone": "1f3ca-1f3fd", "swimmer_medium_dark_skin_tone": "1f3ca-1f3fe", "swimmer_dark_skin_tone": "1f3ca-1f3ff", "woman-lifting-weights_light_skin_tone": "1f3cb-1f3fb-200d-2640-fe0f", "weight_lifting_woman_light_skin_tone": "1f3cb-1f3fb-200d-2640-fe0f", "woman-lifting-weights_medium_light_skin_tone": "1f3cb-1f3fc-200d-2640-fe0f", "weight_lifting_woman_medium_light_skin_tone": "1f3cb-1f3fc-200d-2640-fe0f", "woman-lifting-weights_medium_skin_tone": "1f3cb-1f3fd-200d-2640-fe0f", "weight_lifting_woman_medium_skin_tone": "1f3cb-1f3fd-200d-2640-fe0f", "woman-lifting-weights_medium_dark_skin_tone": "1f3cb-1f3fe-200d-2640-fe0f", "weight_lifting_woman_medium_dark_skin_tone": "1f3cb-1f3fe-200d-2640-fe0f", "woman-lifting-weights_dark_skin_tone": "1f3cb-1f3ff-200d-2640-fe0f", "weight_lifting_woman_dark_skin_tone": "1f3cb-1f3ff-200d-2640-fe0f", "man-lifting-weights_light_skin_tone": "1f3cb-1f3fb-200d-2642-fe0f", "weight_lifting_man_light_skin_tone": "1f3cb-1f3fb-200d-2642-fe0f", "man-lifting-weights_medium_light_skin_tone": "1f3cb-1f3fc-200d-2642-fe0f", "weight_lifting_man_medium_light_skin_tone": "1f3cb-1f3fc-200d-2642-fe0f", "man-lifting-weights_medium_skin_tone": "1f3cb-1f3fd-200d-2642-fe0f", "weight_lifting_man_medium_skin_tone": "1f3cb-1f3fd-200d-2642-fe0f", "man-lifting-weights_medium_dark_skin_tone": "1f3cb-1f3fe-200d-2642-fe0f", "weight_lifting_man_medium_dark_skin_tone": "1f3cb-1f3fe-200d-2642-fe0f", "man-lifting-weights_dark_skin_tone": "1f3cb-1f3ff-200d-2642-fe0f", "weight_lifting_man_dark_skin_tone": "1f3cb-1f3ff-200d-2642-fe0f", "weight_lifter_light_skin_tone": "1f3cb-1f3fb", "weight_lifter_medium_light_skin_tone": "1f3cb-1f3fc", "weight_lifter_medium_skin_tone": "1f3cb-1f3fd", "weight_lifter_medium_dark_skin_tone": "1f3cb-1f3fe", "weight_lifter_dark_skin_tone": "1f3cb-1f3ff", "woman-golfing_light_skin_tone": "1f3cc-1f3fb-200d-2640-fe0f", "golfing_woman_light_skin_tone": "1f3cc-1f3fb-200d-2640-fe0f", "woman-golfing_medium_light_skin_tone": "1f3cc-1f3fc-200d-2640-fe0f", "golfing_woman_medium_light_skin_tone": "1f3cc-1f3fc-200d-2640-fe0f", "woman-golfing_medium_skin_tone": "1f3cc-1f3fd-200d-2640-fe0f", "golfing_woman_medium_skin_tone": "1f3cc-1f3fd-200d-2640-fe0f", "woman-golfing_medium_dark_skin_tone": "1f3cc-1f3fe-200d-2640-fe0f", "golfing_woman_medium_dark_skin_tone": "1f3cc-1f3fe-200d-2640-fe0f", "woman-golfing_dark_skin_tone": "1f3cc-1f3ff-200d-2640-fe0f", "golfing_woman_dark_skin_tone": "1f3cc-1f3ff-200d-2640-fe0f", "man-golfing_light_skin_tone": "1f3cc-1f3fb-200d-2642-fe0f", "golfing_man_light_skin_tone": "1f3cc-1f3fb-200d-2642-fe0f", "man-golfing_medium_light_skin_tone": "1f3cc-1f3fc-200d-2642-fe0f", "golfing_man_medium_light_skin_tone": "1f3cc-1f3fc-200d-2642-fe0f", "man-golfing_medium_skin_tone": "1f3cc-1f3fd-200d-2642-fe0f", "golfing_man_medium_skin_tone": "1f3cc-1f3fd-200d-2642-fe0f", "man-golfing_medium_dark_skin_tone": "1f3cc-1f3fe-200d-2642-fe0f", "golfing_man_medium_dark_skin_tone": "1f3cc-1f3fe-200d-2642-fe0f", "man-golfing_dark_skin_tone": "1f3cc-1f3ff-200d-2642-fe0f", "golfing_man_dark_skin_tone": "1f3cc-1f3ff-200d-2642-fe0f", "golfer_light_skin_tone": "1f3cc-1f3fb", "golfer_medium_light_skin_tone": "1f3cc-1f3fc", "golfer_medium_skin_tone": "1f3cc-1f3fd", "golfer_medium_dark_skin_tone": "1f3cc-1f3fe", "golfer_dark_skin_tone": "1f3cc-1f3ff", "ear_light_skin_tone": "1f442-1f3fb", "ear_medium_light_skin_tone": "1f442-1f3fc", "ear_medium_skin_tone": "1f442-1f3fd", "ear_medium_dark_skin_tone": "1f442-1f3fe", "ear_dark_skin_tone": "1f442-1f3ff", "nose_light_skin_tone": "1f443-1f3fb", "nose_medium_light_skin_tone": "1f443-1f3fc", "nose_medium_skin_tone": "1f443-1f3fd", "nose_medium_dark_skin_tone": "1f443-1f3fe", "nose_dark_skin_tone": "1f443-1f3ff", "point_up_2_light_skin_tone": "1f446-1f3fb", "point_up_2_medium_light_skin_tone": "1f446-1f3fc", "point_up_2_medium_skin_tone": "1f446-1f3fd", "point_up_2_medium_dark_skin_tone": "1f446-1f3fe", "point_up_2_dark_skin_tone": "1f446-1f3ff", "point_down_light_skin_tone": "1f447-1f3fb", "point_down_medium_light_skin_tone": "1f447-1f3fc", "point_down_medium_skin_tone": "1f447-1f3fd", "point_down_medium_dark_skin_tone": "1f447-1f3fe", "point_down_dark_skin_tone": "1f447-1f3ff", "point_left_light_skin_tone": "1f448-1f3fb", "point_left_medium_light_skin_tone": "1f448-1f3fc", "point_left_medium_skin_tone": "1f448-1f3fd", "point_left_medium_dark_skin_tone": "1f448-1f3fe", "point_left_dark_skin_tone": "1f448-1f3ff", "point_right_light_skin_tone": "1f449-1f3fb", "point_right_medium_light_skin_tone": "1f449-1f3fc", "point_right_medium_skin_tone": "1f449-1f3fd", "point_right_medium_dark_skin_tone": "1f449-1f3fe", "point_right_dark_skin_tone": "1f449-1f3ff", "facepunch_light_skin_tone": "1f44a-1f3fb", "punch_light_skin_tone": "1f44a-1f3fb", "fist_oncoming_light_skin_tone": "1f44a-1f3fb", "facepunch_medium_light_skin_tone": "1f44a-1f3fc", "punch_medium_light_skin_tone": "1f44a-1f3fc", "fist_oncoming_medium_light_skin_tone": "1f44a-1f3fc", "facepunch_medium_skin_tone": "1f44a-1f3fd", "punch_medium_skin_tone": "1f44a-1f3fd", "fist_oncoming_medium_skin_tone": "1f44a-1f3fd", "facepunch_medium_dark_skin_tone": "1f44a-1f3fe", "punch_medium_dark_skin_tone": "1f44a-1f3fe", "fist_oncoming_medium_dark_skin_tone": "1f44a-1f3fe", "facepunch_dark_skin_tone": "1f44a-1f3ff", "punch_dark_skin_tone": "1f44a-1f3ff", "fist_oncoming_dark_skin_tone": "1f44a-1f3ff", "wave_light_skin_tone": "1f44b-1f3fb", "wave_medium_light_skin_tone": "1f44b-1f3fc", "wave_medium_skin_tone": "1f44b-1f3fd", "wave_medium_dark_skin_tone": "1f44b-1f3fe", "wave_dark_skin_tone": "1f44b-1f3ff", "ok_hand_light_skin_tone": "1f44c-1f3fb", "ok_hand_medium_light_skin_tone": "1f44c-1f3fc", "ok_hand_medium_skin_tone": "1f44c-1f3fd", "ok_hand_medium_dark_skin_tone": "1f44c-1f3fe", "ok_hand_dark_skin_tone": "1f44c-1f3ff", "+1_light_skin_tone": "1f44d-1f3fb", "thumbsup_light_skin_tone": "1f44d-1f3fb", "+1_medium_light_skin_tone": "1f44d-1f3fc", "thumbsup_medium_light_skin_tone": "1f44d-1f3fc", "+1_medium_skin_tone": "1f44d-1f3fd", "thumbsup_medium_skin_tone": "1f44d-1f3fd", "+1_medium_dark_skin_tone": "1f44d-1f3fe", "thumbsup_medium_dark_skin_tone": "1f44d-1f3fe", "+1_dark_skin_tone": "1f44d-1f3ff", "thumbsup_dark_skin_tone": "1f44d-1f3ff", "-1_light_skin_tone": "1f44e-1f3fb", "thumbsdown_light_skin_tone": "1f44e-1f3fb", "-1_medium_light_skin_tone": "1f44e-1f3fc", "thumbsdown_medium_light_skin_tone": "1f44e-1f3fc", "-1_medium_skin_tone": "1f44e-1f3fd", "thumbsdown_medium_skin_tone": "1f44e-1f3fd", "-1_medium_dark_skin_tone": "1f44e-1f3fe", "thumbsdown_medium_dark_skin_tone": "1f44e-1f3fe", "-1_dark_skin_tone": "1f44e-1f3ff", "thumbsdown_dark_skin_tone": "1f44e-1f3ff", "clap_light_skin_tone": "1f44f-1f3fb", "clap_medium_light_skin_tone": "1f44f-1f3fc", "clap_medium_skin_tone": "1f44f-1f3fd", "clap_medium_dark_skin_tone": "1f44f-1f3fe", "clap_dark_skin_tone": "1f44f-1f3ff", "open_hands_light_skin_tone": "1f450-1f3fb", "open_hands_medium_light_skin_tone": "1f450-1f3fc", "open_hands_medium_skin_tone": "1f450-1f3fd", "open_hands_medium_dark_skin_tone": "1f450-1f3fe", "open_hands_dark_skin_tone": "1f450-1f3ff", "boy_light_skin_tone": "1f466-1f3fb", "boy_medium_light_skin_tone": "1f466-1f3fc", "boy_medium_skin_tone": "1f466-1f3fd", "boy_medium_dark_skin_tone": "1f466-1f3fe", "boy_dark_skin_tone": "1f466-1f3ff", "girl_light_skin_tone": "1f467-1f3fb", "girl_medium_light_skin_tone": "1f467-1f3fc", "girl_medium_skin_tone": "1f467-1f3fd", "girl_medium_dark_skin_tone": "1f467-1f3fe", "girl_dark_skin_tone": "1f467-1f3ff", "male-farmer_light_skin_tone": "1f468-1f3fb-200d-1f33e", "man_farmer_light_skin_tone": "1f468-1f3fb-200d-1f33e", "male-farmer_medium_light_skin_tone": "1f468-1f3fc-200d-1f33e", "man_farmer_medium_light_skin_tone": "1f468-1f3fc-200d-1f33e", "male-farmer_medium_skin_tone": "1f468-1f3fd-200d-1f33e", "man_farmer_medium_skin_tone": "1f468-1f3fd-200d-1f33e", "male-farmer_medium_dark_skin_tone": "1f468-1f3fe-200d-1f33e", "man_farmer_medium_dark_skin_tone": "1f468-1f3fe-200d-1f33e", "male-farmer_dark_skin_tone": "1f468-1f3ff-200d-1f33e", "man_farmer_dark_skin_tone": "1f468-1f3ff-200d-1f33e", "male-cook_light_skin_tone": "1f468-1f3fb-200d-1f373", "man_cook_light_skin_tone": "1f468-1f3fb-200d-1f373", "male-cook_medium_light_skin_tone": "1f468-1f3fc-200d-1f373", "man_cook_medium_light_skin_tone": "1f468-1f3fc-200d-1f373", "male-cook_medium_skin_tone": "1f468-1f3fd-200d-1f373", "man_cook_medium_skin_tone": "1f468-1f3fd-200d-1f373", "male-cook_medium_dark_skin_tone": "1f468-1f3fe-200d-1f373", "man_cook_medium_dark_skin_tone": "1f468-1f3fe-200d-1f373", "male-cook_dark_skin_tone": "1f468-1f3ff-200d-1f373", "man_cook_dark_skin_tone": "1f468-1f3ff-200d-1f373", "man_feeding_baby_light_skin_tone": "1f468-1f3fb-200d-1f37c", "man_feeding_baby_medium_light_skin_tone": "1f468-1f3fc-200d-1f37c", "man_feeding_baby_medium_skin_tone": "1f468-1f3fd-200d-1f37c", "man_feeding_baby_medium_dark_skin_tone": "1f468-1f3fe-200d-1f37c", "man_feeding_baby_dark_skin_tone": "1f468-1f3ff-200d-1f37c", "male-student_light_skin_tone": "1f468-1f3fb-200d-1f393", "man_student_light_skin_tone": "1f468-1f3fb-200d-1f393", "male-student_medium_light_skin_tone": "1f468-1f3fc-200d-1f393", "man_student_medium_light_skin_tone": "1f468-1f3fc-200d-1f393", "male-student_medium_skin_tone": "1f468-1f3fd-200d-1f393", "man_student_medium_skin_tone": "1f468-1f3fd-200d-1f393", "male-student_medium_dark_skin_tone": "1f468-1f3fe-200d-1f393", "man_student_medium_dark_skin_tone": "1f468-1f3fe-200d-1f393", "male-student_dark_skin_tone": "1f468-1f3ff-200d-1f393", "man_student_dark_skin_tone": "1f468-1f3ff-200d-1f393", "male-singer_light_skin_tone": "1f468-1f3fb-200d-1f3a4", "man_singer_light_skin_tone": "1f468-1f3fb-200d-1f3a4", "male-singer_medium_light_skin_tone": "1f468-1f3fc-200d-1f3a4", "man_singer_medium_light_skin_tone": "1f468-1f3fc-200d-1f3a4", "male-singer_medium_skin_tone": "1f468-1f3fd-200d-1f3a4", "man_singer_medium_skin_tone": "1f468-1f3fd-200d-1f3a4", "male-singer_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3a4", "man_singer_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3a4", "male-singer_dark_skin_tone": "1f468-1f3ff-200d-1f3a4", "man_singer_dark_skin_tone": "1f468-1f3ff-200d-1f3a4", "male-artist_light_skin_tone": "1f468-1f3fb-200d-1f3a8", "man_artist_light_skin_tone": "1f468-1f3fb-200d-1f3a8", "male-artist_medium_light_skin_tone": "1f468-1f3fc-200d-1f3a8", "man_artist_medium_light_skin_tone": "1f468-1f3fc-200d-1f3a8", "male-artist_medium_skin_tone": "1f468-1f3fd-200d-1f3a8", "man_artist_medium_skin_tone": "1f468-1f3fd-200d-1f3a8", "male-artist_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3a8", "man_artist_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3a8", "male-artist_dark_skin_tone": "1f468-1f3ff-200d-1f3a8", "man_artist_dark_skin_tone": "1f468-1f3ff-200d-1f3a8", "male-teacher_light_skin_tone": "1f468-1f3fb-200d-1f3eb", "man_teacher_light_skin_tone": "1f468-1f3fb-200d-1f3eb", "male-teacher_medium_light_skin_tone": "1f468-1f3fc-200d-1f3eb", "man_teacher_medium_light_skin_tone": "1f468-1f3fc-200d-1f3eb", "male-teacher_medium_skin_tone": "1f468-1f3fd-200d-1f3eb", "man_teacher_medium_skin_tone": "1f468-1f3fd-200d-1f3eb", "male-teacher_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3eb", "man_teacher_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3eb", "male-teacher_dark_skin_tone": "1f468-1f3ff-200d-1f3eb", "man_teacher_dark_skin_tone": "1f468-1f3ff-200d-1f3eb", "male-factory-worker_light_skin_tone": "1f468-1f3fb-200d-1f3ed", "man_factory_worker_light_skin_tone": "1f468-1f3fb-200d-1f3ed", "male-factory-worker_medium_light_skin_tone": "1f468-1f3fc-200d-1f3ed", "man_factory_worker_medium_light_skin_tone": "1f468-1f3fc-200d-1f3ed", "male-factory-worker_medium_skin_tone": "1f468-1f3fd-200d-1f3ed", "man_factory_worker_medium_skin_tone": "1f468-1f3fd-200d-1f3ed", "male-factory-worker_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3ed", "man_factory_worker_medium_dark_skin_tone": "1f468-1f3fe-200d-1f3ed", "male-factory-worker_dark_skin_tone": "1f468-1f3ff-200d-1f3ed", "man_factory_worker_dark_skin_tone": "1f468-1f3ff-200d-1f3ed", "male-technologist_light_skin_tone": "1f468-1f3fb-200d-1f4bb", "man_technologist_light_skin_tone": "1f468-1f3fb-200d-1f4bb", "male-technologist_medium_light_skin_tone": "1f468-1f3fc-200d-1f4bb", "man_technologist_medium_light_skin_tone": "1f468-1f3fc-200d-1f4bb", "male-technologist_medium_skin_tone": "1f468-1f3fd-200d-1f4bb", "man_technologist_medium_skin_tone": "1f468-1f3fd-200d-1f4bb", "male-technologist_medium_dark_skin_tone": "1f468-1f3fe-200d-1f4bb", "man_technologist_medium_dark_skin_tone": "1f468-1f3fe-200d-1f4bb", "male-technologist_dark_skin_tone": "1f468-1f3ff-200d-1f4bb", "man_technologist_dark_skin_tone": "1f468-1f3ff-200d-1f4bb", "male-office-worker_light_skin_tone": "1f468-1f3fb-200d-1f4bc", "man_office_worker_light_skin_tone": "1f468-1f3fb-200d-1f4bc", "male-office-worker_medium_light_skin_tone": "1f468-1f3fc-200d-1f4bc", "man_office_worker_medium_light_skin_tone": "1f468-1f3fc-200d-1f4bc", "male-office-worker_medium_skin_tone": "1f468-1f3fd-200d-1f4bc", "man_office_worker_medium_skin_tone": "1f468-1f3fd-200d-1f4bc", "male-office-worker_medium_dark_skin_tone": "1f468-1f3fe-200d-1f4bc", "man_office_worker_medium_dark_skin_tone": "1f468-1f3fe-200d-1f4bc", "male-office-worker_dark_skin_tone": "1f468-1f3ff-200d-1f4bc", "man_office_worker_dark_skin_tone": "1f468-1f3ff-200d-1f4bc", "male-mechanic_light_skin_tone": "1f468-1f3fb-200d-1f527", "man_mechanic_light_skin_tone": "1f468-1f3fb-200d-1f527", "male-mechanic_medium_light_skin_tone": "1f468-1f3fc-200d-1f527", "man_mechanic_medium_light_skin_tone": "1f468-1f3fc-200d-1f527", "male-mechanic_medium_skin_tone": "1f468-1f3fd-200d-1f527", "man_mechanic_medium_skin_tone": "1f468-1f3fd-200d-1f527", "male-mechanic_medium_dark_skin_tone": "1f468-1f3fe-200d-1f527", "man_mechanic_medium_dark_skin_tone": "1f468-1f3fe-200d-1f527", "male-mechanic_dark_skin_tone": "1f468-1f3ff-200d-1f527", "man_mechanic_dark_skin_tone": "1f468-1f3ff-200d-1f527", "male-scientist_light_skin_tone": "1f468-1f3fb-200d-1f52c", "man_scientist_light_skin_tone": "1f468-1f3fb-200d-1f52c", "male-scientist_medium_light_skin_tone": "1f468-1f3fc-200d-1f52c", "man_scientist_medium_light_skin_tone": "1f468-1f3fc-200d-1f52c", "male-scientist_medium_skin_tone": "1f468-1f3fd-200d-1f52c", "man_scientist_medium_skin_tone": "1f468-1f3fd-200d-1f52c", "male-scientist_medium_dark_skin_tone": "1f468-1f3fe-200d-1f52c", "man_scientist_medium_dark_skin_tone": "1f468-1f3fe-200d-1f52c", "male-scientist_dark_skin_tone": "1f468-1f3ff-200d-1f52c", "man_scientist_dark_skin_tone": "1f468-1f3ff-200d-1f52c", "male-astronaut_light_skin_tone": "1f468-1f3fb-200d-1f680", "man_astronaut_light_skin_tone": "1f468-1f3fb-200d-1f680", "male-astronaut_medium_light_skin_tone": "1f468-1f3fc-200d-1f680", "man_astronaut_medium_light_skin_tone": "1f468-1f3fc-200d-1f680", "male-astronaut_medium_skin_tone": "1f468-1f3fd-200d-1f680", "man_astronaut_medium_skin_tone": "1f468-1f3fd-200d-1f680", "male-astronaut_medium_dark_skin_tone": "1f468-1f3fe-200d-1f680", "man_astronaut_medium_dark_skin_tone": "1f468-1f3fe-200d-1f680", "male-astronaut_dark_skin_tone": "1f468-1f3ff-200d-1f680", "man_astronaut_dark_skin_tone": "1f468-1f3ff-200d-1f680", "male-firefighter_light_skin_tone": "1f468-1f3fb-200d-1f692", "man_firefighter_light_skin_tone": "1f468-1f3fb-200d-1f692", "male-firefighter_medium_light_skin_tone": "1f468-1f3fc-200d-1f692", "man_firefighter_medium_light_skin_tone": "1f468-1f3fc-200d-1f692", "male-firefighter_medium_skin_tone": "1f468-1f3fd-200d-1f692", "man_firefighter_medium_skin_tone": "1f468-1f3fd-200d-1f692", "male-firefighter_medium_dark_skin_tone": "1f468-1f3fe-200d-1f692", "man_firefighter_medium_dark_skin_tone": "1f468-1f3fe-200d-1f692", "male-firefighter_dark_skin_tone": "1f468-1f3ff-200d-1f692", "man_firefighter_dark_skin_tone": "1f468-1f3ff-200d-1f692", "man_with_probing_cane_light_skin_tone": "1f468-1f3fb-200d-1f9af", "man_with_probing_cane_medium_light_skin_tone": "1f468-1f3fc-200d-1f9af", "man_with_probing_cane_medium_skin_tone": "1f468-1f3fd-200d-1f9af", "man_with_probing_cane_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9af", "man_with_probing_cane_dark_skin_tone": "1f468-1f3ff-200d-1f9af", "red_haired_man_light_skin_tone": "1f468-1f3fb-200d-1f9b0", "red_haired_man_medium_light_skin_tone": "1f468-1f3fc-200d-1f9b0", "red_haired_man_medium_skin_tone": "1f468-1f3fd-200d-1f9b0", "red_haired_man_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9b0", "red_haired_man_dark_skin_tone": "1f468-1f3ff-200d-1f9b0", "curly_haired_man_light_skin_tone": "1f468-1f3fb-200d-1f9b1", "curly_haired_man_medium_light_skin_tone": "1f468-1f3fc-200d-1f9b1", "curly_haired_man_medium_skin_tone": "1f468-1f3fd-200d-1f9b1", "curly_haired_man_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9b1", "curly_haired_man_dark_skin_tone": "1f468-1f3ff-200d-1f9b1", "bald_man_light_skin_tone": "1f468-1f3fb-200d-1f9b2", "bald_man_medium_light_skin_tone": "1f468-1f3fc-200d-1f9b2", "bald_man_medium_skin_tone": "1f468-1f3fd-200d-1f9b2", "bald_man_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9b2", "bald_man_dark_skin_tone": "1f468-1f3ff-200d-1f9b2", "white_haired_man_light_skin_tone": "1f468-1f3fb-200d-1f9b3", "white_haired_man_medium_light_skin_tone": "1f468-1f3fc-200d-1f9b3", "white_haired_man_medium_skin_tone": "1f468-1f3fd-200d-1f9b3", "white_haired_man_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9b3", "white_haired_man_dark_skin_tone": "1f468-1f3ff-200d-1f9b3", "man_in_motorized_wheelchair_light_skin_tone": "1f468-1f3fb-200d-1f9bc", "man_in_motorized_wheelchair_medium_light_skin_tone": "1f468-1f3fc-200d-1f9bc", "man_in_motorized_wheelchair_medium_skin_tone": "1f468-1f3fd-200d-1f9bc", "man_in_motorized_wheelchair_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9bc", "man_in_motorized_wheelchair_dark_skin_tone": "1f468-1f3ff-200d-1f9bc", "man_in_manual_wheelchair_light_skin_tone": "1f468-1f3fb-200d-1f9bd", "man_in_manual_wheelchair_medium_light_skin_tone": "1f468-1f3fc-200d-1f9bd", "man_in_manual_wheelchair_medium_skin_tone": "1f468-1f3fd-200d-1f9bd", "man_in_manual_wheelchair_medium_dark_skin_tone": "1f468-1f3fe-200d-1f9bd", "man_in_manual_wheelchair_dark_skin_tone": "1f468-1f3ff-200d-1f9bd", "male-doctor_light_skin_tone": "1f468-1f3fb-200d-2695-fe0f", "man_health_worker_light_skin_tone": "1f468-1f3fb-200d-2695-fe0f", "male-doctor_medium_light_skin_tone": "1f468-1f3fc-200d-2695-fe0f", "man_health_worker_medium_light_skin_tone": "1f468-1f3fc-200d-2695-fe0f", "male-doctor_medium_skin_tone": "1f468-1f3fd-200d-2695-fe0f", "man_health_worker_medium_skin_tone": "1f468-1f3fd-200d-2695-fe0f", "male-doctor_medium_dark_skin_tone": "1f468-1f3fe-200d-2695-fe0f", "man_health_worker_medium_dark_skin_tone": "1f468-1f3fe-200d-2695-fe0f", "male-doctor_dark_skin_tone": "1f468-1f3ff-200d-2695-fe0f", "man_health_worker_dark_skin_tone": "1f468-1f3ff-200d-2695-fe0f", "male-judge_light_skin_tone": "1f468-1f3fb-200d-2696-fe0f", "man_judge_light_skin_tone": "1f468-1f3fb-200d-2696-fe0f", "male-judge_medium_light_skin_tone": "1f468-1f3fc-200d-2696-fe0f", "man_judge_medium_light_skin_tone": "1f468-1f3fc-200d-2696-fe0f", "male-judge_medium_skin_tone": "1f468-1f3fd-200d-2696-fe0f", "man_judge_medium_skin_tone": "1f468-1f3fd-200d-2696-fe0f", "male-judge_medium_dark_skin_tone": "1f468-1f3fe-200d-2696-fe0f", "man_judge_medium_dark_skin_tone": "1f468-1f3fe-200d-2696-fe0f", "male-judge_dark_skin_tone": "1f468-1f3ff-200d-2696-fe0f", "man_judge_dark_skin_tone": "1f468-1f3ff-200d-2696-fe0f", "male-pilot_light_skin_tone": "1f468-1f3fb-200d-2708-fe0f", "man_pilot_light_skin_tone": "1f468-1f3fb-200d-2708-fe0f", "male-pilot_medium_light_skin_tone": "1f468-1f3fc-200d-2708-fe0f", "man_pilot_medium_light_skin_tone": "1f468-1f3fc-200d-2708-fe0f", "male-pilot_medium_skin_tone": "1f468-1f3fd-200d-2708-fe0f", "man_pilot_medium_skin_tone": "1f468-1f3fd-200d-2708-fe0f", "male-pilot_medium_dark_skin_tone": "1f468-1f3fe-200d-2708-fe0f", "man_pilot_medium_dark_skin_tone": "1f468-1f3fe-200d-2708-fe0f", "male-pilot_dark_skin_tone": "1f468-1f3ff-200d-2708-fe0f", "man_pilot_dark_skin_tone": "1f468-1f3ff-200d-2708-fe0f", "man_light_skin_tone": "1f468-1f3fb", "man_medium_light_skin_tone": "1f468-1f3fc", "man_medium_skin_tone": "1f468-1f3fd", "man_medium_dark_skin_tone": "1f468-1f3fe", "man_dark_skin_tone": "1f468-1f3ff", "female-farmer_light_skin_tone": "1f469-1f3fb-200d-1f33e", "woman_farmer_light_skin_tone": "1f469-1f3fb-200d-1f33e", "female-farmer_medium_light_skin_tone": "1f469-1f3fc-200d-1f33e", "woman_farmer_medium_light_skin_tone": "1f469-1f3fc-200d-1f33e", "female-farmer_medium_skin_tone": "1f469-1f3fd-200d-1f33e", "woman_farmer_medium_skin_tone": "1f469-1f3fd-200d-1f33e", "female-farmer_medium_dark_skin_tone": "1f469-1f3fe-200d-1f33e", "woman_farmer_medium_dark_skin_tone": "1f469-1f3fe-200d-1f33e", "female-farmer_dark_skin_tone": "1f469-1f3ff-200d-1f33e", "woman_farmer_dark_skin_tone": "1f469-1f3ff-200d-1f33e", "female-cook_light_skin_tone": "1f469-1f3fb-200d-1f373", "woman_cook_light_skin_tone": "1f469-1f3fb-200d-1f373", "female-cook_medium_light_skin_tone": "1f469-1f3fc-200d-1f373", "woman_cook_medium_light_skin_tone": "1f469-1f3fc-200d-1f373", "female-cook_medium_skin_tone": "1f469-1f3fd-200d-1f373", "woman_cook_medium_skin_tone": "1f469-1f3fd-200d-1f373", "female-cook_medium_dark_skin_tone": "1f469-1f3fe-200d-1f373", "woman_cook_medium_dark_skin_tone": "1f469-1f3fe-200d-1f373", "female-cook_dark_skin_tone": "1f469-1f3ff-200d-1f373", "woman_cook_dark_skin_tone": "1f469-1f3ff-200d-1f373", "woman_feeding_baby_light_skin_tone": "1f469-1f3fb-200d-1f37c", "woman_feeding_baby_medium_light_skin_tone": "1f469-1f3fc-200d-1f37c", "woman_feeding_baby_medium_skin_tone": "1f469-1f3fd-200d-1f37c", "woman_feeding_baby_medium_dark_skin_tone": "1f469-1f3fe-200d-1f37c", "woman_feeding_baby_dark_skin_tone": "1f469-1f3ff-200d-1f37c", "female-student_light_skin_tone": "1f469-1f3fb-200d-1f393", "woman_student_light_skin_tone": "1f469-1f3fb-200d-1f393", "female-student_medium_light_skin_tone": "1f469-1f3fc-200d-1f393", "woman_student_medium_light_skin_tone": "1f469-1f3fc-200d-1f393", "female-student_medium_skin_tone": "1f469-1f3fd-200d-1f393", "woman_student_medium_skin_tone": "1f469-1f3fd-200d-1f393", "female-student_medium_dark_skin_tone": "1f469-1f3fe-200d-1f393", "woman_student_medium_dark_skin_tone": "1f469-1f3fe-200d-1f393", "female-student_dark_skin_tone": "1f469-1f3ff-200d-1f393", "woman_student_dark_skin_tone": "1f469-1f3ff-200d-1f393", "female-singer_light_skin_tone": "1f469-1f3fb-200d-1f3a4", "woman_singer_light_skin_tone": "1f469-1f3fb-200d-1f3a4", "female-singer_medium_light_skin_tone": "1f469-1f3fc-200d-1f3a4", "woman_singer_medium_light_skin_tone": "1f469-1f3fc-200d-1f3a4", "female-singer_medium_skin_tone": "1f469-1f3fd-200d-1f3a4", "woman_singer_medium_skin_tone": "1f469-1f3fd-200d-1f3a4", "female-singer_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3a4", "woman_singer_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3a4", "female-singer_dark_skin_tone": "1f469-1f3ff-200d-1f3a4", "woman_singer_dark_skin_tone": "1f469-1f3ff-200d-1f3a4", "female-artist_light_skin_tone": "1f469-1f3fb-200d-1f3a8", "woman_artist_light_skin_tone": "1f469-1f3fb-200d-1f3a8", "female-artist_medium_light_skin_tone": "1f469-1f3fc-200d-1f3a8", "woman_artist_medium_light_skin_tone": "1f469-1f3fc-200d-1f3a8", "female-artist_medium_skin_tone": "1f469-1f3fd-200d-1f3a8", "woman_artist_medium_skin_tone": "1f469-1f3fd-200d-1f3a8", "female-artist_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3a8", "woman_artist_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3a8", "female-artist_dark_skin_tone": "1f469-1f3ff-200d-1f3a8", "woman_artist_dark_skin_tone": "1f469-1f3ff-200d-1f3a8", "female-teacher_light_skin_tone": "1f469-1f3fb-200d-1f3eb", "woman_teacher_light_skin_tone": "1f469-1f3fb-200d-1f3eb", "female-teacher_medium_light_skin_tone": "1f469-1f3fc-200d-1f3eb", "woman_teacher_medium_light_skin_tone": "1f469-1f3fc-200d-1f3eb", "female-teacher_medium_skin_tone": "1f469-1f3fd-200d-1f3eb", "woman_teacher_medium_skin_tone": "1f469-1f3fd-200d-1f3eb", "female-teacher_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3eb", "woman_teacher_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3eb", "female-teacher_dark_skin_tone": "1f469-1f3ff-200d-1f3eb", "woman_teacher_dark_skin_tone": "1f469-1f3ff-200d-1f3eb", "female-factory-worker_light_skin_tone": "1f469-1f3fb-200d-1f3ed", "woman_factory_worker_light_skin_tone": "1f469-1f3fb-200d-1f3ed", "female-factory-worker_medium_light_skin_tone": "1f469-1f3fc-200d-1f3ed", "woman_factory_worker_medium_light_skin_tone": "1f469-1f3fc-200d-1f3ed", "female-factory-worker_medium_skin_tone": "1f469-1f3fd-200d-1f3ed", "woman_factory_worker_medium_skin_tone": "1f469-1f3fd-200d-1f3ed", "female-factory-worker_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3ed", "woman_factory_worker_medium_dark_skin_tone": "1f469-1f3fe-200d-1f3ed", "female-factory-worker_dark_skin_tone": "1f469-1f3ff-200d-1f3ed", "woman_factory_worker_dark_skin_tone": "1f469-1f3ff-200d-1f3ed", "female-technologist_light_skin_tone": "1f469-1f3fb-200d-1f4bb", "woman_technologist_light_skin_tone": "1f469-1f3fb-200d-1f4bb", "female-technologist_medium_light_skin_tone": "1f469-1f3fc-200d-1f4bb", "woman_technologist_medium_light_skin_tone": "1f469-1f3fc-200d-1f4bb", "female-technologist_medium_skin_tone": "1f469-1f3fd-200d-1f4bb", "woman_technologist_medium_skin_tone": "1f469-1f3fd-200d-1f4bb", "female-technologist_medium_dark_skin_tone": "1f469-1f3fe-200d-1f4bb", "woman_technologist_medium_dark_skin_tone": "1f469-1f3fe-200d-1f4bb", "female-technologist_dark_skin_tone": "1f469-1f3ff-200d-1f4bb", "woman_technologist_dark_skin_tone": "1f469-1f3ff-200d-1f4bb", "female-office-worker_light_skin_tone": "1f469-1f3fb-200d-1f4bc", "woman_office_worker_light_skin_tone": "1f469-1f3fb-200d-1f4bc", "female-office-worker_medium_light_skin_tone": "1f469-1f3fc-200d-1f4bc", "woman_office_worker_medium_light_skin_tone": "1f469-1f3fc-200d-1f4bc", "female-office-worker_medium_skin_tone": "1f469-1f3fd-200d-1f4bc", "woman_office_worker_medium_skin_tone": "1f469-1f3fd-200d-1f4bc", "female-office-worker_medium_dark_skin_tone": "1f469-1f3fe-200d-1f4bc", "woman_office_worker_medium_dark_skin_tone": "1f469-1f3fe-200d-1f4bc", "female-office-worker_dark_skin_tone": "1f469-1f3ff-200d-1f4bc", "woman_office_worker_dark_skin_tone": "1f469-1f3ff-200d-1f4bc", "female-mechanic_light_skin_tone": "1f469-1f3fb-200d-1f527", "woman_mechanic_light_skin_tone": "1f469-1f3fb-200d-1f527", "female-mechanic_medium_light_skin_tone": "1f469-1f3fc-200d-1f527", "woman_mechanic_medium_light_skin_tone": "1f469-1f3fc-200d-1f527", "female-mechanic_medium_skin_tone": "1f469-1f3fd-200d-1f527", "woman_mechanic_medium_skin_tone": "1f469-1f3fd-200d-1f527", "female-mechanic_medium_dark_skin_tone": "1f469-1f3fe-200d-1f527", "woman_mechanic_medium_dark_skin_tone": "1f469-1f3fe-200d-1f527", "female-mechanic_dark_skin_tone": "1f469-1f3ff-200d-1f527", "woman_mechanic_dark_skin_tone": "1f469-1f3ff-200d-1f527", "female-scientist_light_skin_tone": "1f469-1f3fb-200d-1f52c", "woman_scientist_light_skin_tone": "1f469-1f3fb-200d-1f52c", "female-scientist_medium_light_skin_tone": "1f469-1f3fc-200d-1f52c", "woman_scientist_medium_light_skin_tone": "1f469-1f3fc-200d-1f52c", "female-scientist_medium_skin_tone": "1f469-1f3fd-200d-1f52c", "woman_scientist_medium_skin_tone": "1f469-1f3fd-200d-1f52c", "female-scientist_medium_dark_skin_tone": "1f469-1f3fe-200d-1f52c", "woman_scientist_medium_dark_skin_tone": "1f469-1f3fe-200d-1f52c", "female-scientist_dark_skin_tone": "1f469-1f3ff-200d-1f52c", "woman_scientist_dark_skin_tone": "1f469-1f3ff-200d-1f52c", "female-astronaut_light_skin_tone": "1f469-1f3fb-200d-1f680", "woman_astronaut_light_skin_tone": "1f469-1f3fb-200d-1f680", "female-astronaut_medium_light_skin_tone": "1f469-1f3fc-200d-1f680", "woman_astronaut_medium_light_skin_tone": "1f469-1f3fc-200d-1f680", "female-astronaut_medium_skin_tone": "1f469-1f3fd-200d-1f680", "woman_astronaut_medium_skin_tone": "1f469-1f3fd-200d-1f680", "female-astronaut_medium_dark_skin_tone": "1f469-1f3fe-200d-1f680", "woman_astronaut_medium_dark_skin_tone": "1f469-1f3fe-200d-1f680", "female-astronaut_dark_skin_tone": "1f469-1f3ff-200d-1f680", "woman_astronaut_dark_skin_tone": "1f469-1f3ff-200d-1f680", "female-firefighter_light_skin_tone": "1f469-1f3fb-200d-1f692", "woman_firefighter_light_skin_tone": "1f469-1f3fb-200d-1f692", "female-firefighter_medium_light_skin_tone": "1f469-1f3fc-200d-1f692", "woman_firefighter_medium_light_skin_tone": "1f469-1f3fc-200d-1f692", "female-firefighter_medium_skin_tone": "1f469-1f3fd-200d-1f692", "woman_firefighter_medium_skin_tone": "1f469-1f3fd-200d-1f692", "female-firefighter_medium_dark_skin_tone": "1f469-1f3fe-200d-1f692", "woman_firefighter_medium_dark_skin_tone": "1f469-1f3fe-200d-1f692", "female-firefighter_dark_skin_tone": "1f469-1f3ff-200d-1f692", "woman_firefighter_dark_skin_tone": "1f469-1f3ff-200d-1f692", "woman_with_probing_cane_light_skin_tone": "1f469-1f3fb-200d-1f9af", "woman_with_probing_cane_medium_light_skin_tone": "1f469-1f3fc-200d-1f9af", "woman_with_probing_cane_medium_skin_tone": "1f469-1f3fd-200d-1f9af", "woman_with_probing_cane_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9af", "woman_with_probing_cane_dark_skin_tone": "1f469-1f3ff-200d-1f9af", "red_haired_woman_light_skin_tone": "1f469-1f3fb-200d-1f9b0", "red_haired_woman_medium_light_skin_tone": "1f469-1f3fc-200d-1f9b0", "red_haired_woman_medium_skin_tone": "1f469-1f3fd-200d-1f9b0", "red_haired_woman_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9b0", "red_haired_woman_dark_skin_tone": "1f469-1f3ff-200d-1f9b0", "curly_haired_woman_light_skin_tone": "1f469-1f3fb-200d-1f9b1", "curly_haired_woman_medium_light_skin_tone": "1f469-1f3fc-200d-1f9b1", "curly_haired_woman_medium_skin_tone": "1f469-1f3fd-200d-1f9b1", "curly_haired_woman_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9b1", "curly_haired_woman_dark_skin_tone": "1f469-1f3ff-200d-1f9b1", "bald_woman_light_skin_tone": "1f469-1f3fb-200d-1f9b2", "bald_woman_medium_light_skin_tone": "1f469-1f3fc-200d-1f9b2", "bald_woman_medium_skin_tone": "1f469-1f3fd-200d-1f9b2", "bald_woman_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9b2", "bald_woman_dark_skin_tone": "1f469-1f3ff-200d-1f9b2", "white_haired_woman_light_skin_tone": "1f469-1f3fb-200d-1f9b3", "white_haired_woman_medium_light_skin_tone": "1f469-1f3fc-200d-1f9b3", "white_haired_woman_medium_skin_tone": "1f469-1f3fd-200d-1f9b3", "white_haired_woman_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9b3", "white_haired_woman_dark_skin_tone": "1f469-1f3ff-200d-1f9b3", "woman_in_motorized_wheelchair_light_skin_tone": "1f469-1f3fb-200d-1f9bc", "woman_in_motorized_wheelchair_medium_light_skin_tone": "1f469-1f3fc-200d-1f9bc", "woman_in_motorized_wheelchair_medium_skin_tone": "1f469-1f3fd-200d-1f9bc", "woman_in_motorized_wheelchair_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9bc", "woman_in_motorized_wheelchair_dark_skin_tone": "1f469-1f3ff-200d-1f9bc", "woman_in_manual_wheelchair_light_skin_tone": "1f469-1f3fb-200d-1f9bd", "woman_in_manual_wheelchair_medium_light_skin_tone": "1f469-1f3fc-200d-1f9bd", "woman_in_manual_wheelchair_medium_skin_tone": "1f469-1f3fd-200d-1f9bd", "woman_in_manual_wheelchair_medium_dark_skin_tone": "1f469-1f3fe-200d-1f9bd", "woman_in_manual_wheelchair_dark_skin_tone": "1f469-1f3ff-200d-1f9bd", "female-doctor_light_skin_tone": "1f469-1f3fb-200d-2695-fe0f", "woman_health_worker_light_skin_tone": "1f469-1f3fb-200d-2695-fe0f", "female-doctor_medium_light_skin_tone": "1f469-1f3fc-200d-2695-fe0f", "woman_health_worker_medium_light_skin_tone": "1f469-1f3fc-200d-2695-fe0f", "female-doctor_medium_skin_tone": "1f469-1f3fd-200d-2695-fe0f", "woman_health_worker_medium_skin_tone": "1f469-1f3fd-200d-2695-fe0f", "female-doctor_medium_dark_skin_tone": "1f469-1f3fe-200d-2695-fe0f", "woman_health_worker_medium_dark_skin_tone": "1f469-1f3fe-200d-2695-fe0f", "female-doctor_dark_skin_tone": "1f469-1f3ff-200d-2695-fe0f", "woman_health_worker_dark_skin_tone": "1f469-1f3ff-200d-2695-fe0f", "female-judge_light_skin_tone": "1f469-1f3fb-200d-2696-fe0f", "woman_judge_light_skin_tone": "1f469-1f3fb-200d-2696-fe0f", "female-judge_medium_light_skin_tone": "1f469-1f3fc-200d-2696-fe0f", "woman_judge_medium_light_skin_tone": "1f469-1f3fc-200d-2696-fe0f", "female-judge_medium_skin_tone": "1f469-1f3fd-200d-2696-fe0f", "woman_judge_medium_skin_tone": "1f469-1f3fd-200d-2696-fe0f", "female-judge_medium_dark_skin_tone": "1f469-1f3fe-200d-2696-fe0f", "woman_judge_medium_dark_skin_tone": "1f469-1f3fe-200d-2696-fe0f", "female-judge_dark_skin_tone": "1f469-1f3ff-200d-2696-fe0f", "woman_judge_dark_skin_tone": "1f469-1f3ff-200d-2696-fe0f", "female-pilot_light_skin_tone": "1f469-1f3fb-200d-2708-fe0f", "woman_pilot_light_skin_tone": "1f469-1f3fb-200d-2708-fe0f", "female-pilot_medium_light_skin_tone": "1f469-1f3fc-200d-2708-fe0f", "woman_pilot_medium_light_skin_tone": "1f469-1f3fc-200d-2708-fe0f", "female-pilot_medium_skin_tone": "1f469-1f3fd-200d-2708-fe0f", "woman_pilot_medium_skin_tone": "1f469-1f3fd-200d-2708-fe0f", "female-pilot_medium_dark_skin_tone": "1f469-1f3fe-200d-2708-fe0f", "woman_pilot_medium_dark_skin_tone": "1f469-1f3fe-200d-2708-fe0f", "female-pilot_dark_skin_tone": "1f469-1f3ff-200d-2708-fe0f", "woman_pilot_dark_skin_tone": "1f469-1f3ff-200d-2708-fe0f", "woman_light_skin_tone": "1f469-1f3fb", "woman_medium_light_skin_tone": "1f469-1f3fc", "woman_medium_skin_tone": "1f469-1f3fd", "woman_medium_dark_skin_tone": "1f469-1f3fe", "woman_dark_skin_tone": "1f469-1f3ff", "man_and_woman_holding_hands_light_skin_tone": "1f46b-1f3fb", "woman_and_man_holding_hands_light_skin_tone": "1f46b-1f3fb", "couple_light_skin_tone": "1f46b-1f3fb", "man_and_woman_holding_hands_medium_light_skin_tone": "1f46b-1f3fc", "woman_and_man_holding_hands_medium_light_skin_tone": "1f46b-1f3fc", "couple_medium_light_skin_tone": "1f46b-1f3fc", "man_and_woman_holding_hands_medium_skin_tone": "1f46b-1f3fd", "woman_and_man_holding_hands_medium_skin_tone": "1f46b-1f3fd", "couple_medium_skin_tone": "1f46b-1f3fd", "man_and_woman_holding_hands_medium_dark_skin_tone": "1f46b-1f3fe", "woman_and_man_holding_hands_medium_dark_skin_tone": "1f46b-1f3fe", "couple_medium_dark_skin_tone": "1f46b-1f3fe", "man_and_woman_holding_hands_dark_skin_tone": "1f46b-1f3ff", "woman_and_man_holding_hands_dark_skin_tone": "1f46b-1f3ff", "couple_dark_skin_tone": "1f46b-1f3ff", "man_and_woman_holding_hands_light_skin_tone_medium_light_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc", "woman_and_man_holding_hands_light_skin_tone_medium_light_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc", "couple_light_skin_tone_medium_light_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc", "man_and_woman_holding_hands_light_skin_tone_medium_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd", "woman_and_man_holding_hands_light_skin_tone_medium_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd", "couple_light_skin_tone_medium_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd", "man_and_woman_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe", "woman_and_man_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe", "couple_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe", "man_and_woman_holding_hands_light_skin_tone_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff", "woman_and_man_holding_hands_light_skin_tone_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff", "couple_light_skin_tone_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff", "man_and_woman_holding_hands_medium_light_skin_tone_light_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb", "woman_and_man_holding_hands_medium_light_skin_tone_light_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb", "couple_medium_light_skin_tone_light_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb", "man_and_woman_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd", "woman_and_man_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd", "couple_medium_light_skin_tone_medium_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd", "man_and_woman_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe", "woman_and_man_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe", "couple_medium_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe", "man_and_woman_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff", "woman_and_man_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff", "couple_medium_light_skin_tone_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff", "man_and_woman_holding_hands_medium_skin_tone_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb", "woman_and_man_holding_hands_medium_skin_tone_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb", "couple_medium_skin_tone_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb", "man_and_woman_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc", "woman_and_man_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc", "couple_medium_skin_tone_medium_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc", "man_and_woman_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe", "woman_and_man_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe", "couple_medium_skin_tone_medium_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe", "man_and_woman_holding_hands_medium_skin_tone_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff", "woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff", "couple_medium_skin_tone_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff", "man_and_woman_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb", "woman_and_man_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb", "couple_medium_dark_skin_tone_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb", "man_and_woman_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc", "woman_and_man_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc", "couple_medium_dark_skin_tone_medium_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc", "man_and_woman_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd", "woman_and_man_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd", "couple_medium_dark_skin_tone_medium_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd", "man_and_woman_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff", "woman_and_man_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff", "couple_medium_dark_skin_tone_dark_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff", "man_and_woman_holding_hands_dark_skin_tone_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb", "woman_and_man_holding_hands_dark_skin_tone_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb", "couple_dark_skin_tone_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb", "man_and_woman_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc", "woman_and_man_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc", "couple_dark_skin_tone_medium_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc", "man_and_woman_holding_hands_dark_skin_tone_medium_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd", "woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd", "couple_dark_skin_tone_medium_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd", "man_and_woman_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe", "woman_and_man_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe", "couple_dark_skin_tone_medium_dark_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe", "two_men_holding_hands_light_skin_tone": "1f46c-1f3fb", "men_holding_hands_light_skin_tone": "1f46c-1f3fb", "two_men_holding_hands_medium_light_skin_tone": "1f46c-1f3fc", "men_holding_hands_medium_light_skin_tone": "1f46c-1f3fc", "two_men_holding_hands_medium_skin_tone": "1f46c-1f3fd", "men_holding_hands_medium_skin_tone": "1f46c-1f3fd", "two_men_holding_hands_medium_dark_skin_tone": "1f46c-1f3fe", "men_holding_hands_medium_dark_skin_tone": "1f46c-1f3fe", "two_men_holding_hands_dark_skin_tone": "1f46c-1f3ff", "men_holding_hands_dark_skin_tone": "1f46c-1f3ff", "two_men_holding_hands_light_skin_tone_medium_light_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc", "men_holding_hands_light_skin_tone_medium_light_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc", "two_men_holding_hands_light_skin_tone_medium_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd", "men_holding_hands_light_skin_tone_medium_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd", "two_men_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe", "men_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe", "two_men_holding_hands_light_skin_tone_dark_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff", "men_holding_hands_light_skin_tone_dark_skin_tone": "1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff", "two_men_holding_hands_medium_light_skin_tone_light_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb", "men_holding_hands_medium_light_skin_tone_light_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb", "two_men_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd", "men_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd", "two_men_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe", "men_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe", "two_men_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff", "men_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff", "two_men_holding_hands_medium_skin_tone_light_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb", "men_holding_hands_medium_skin_tone_light_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb", "two_men_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc", "men_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc", "two_men_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe", "men_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe", "two_men_holding_hands_medium_skin_tone_dark_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff", "men_holding_hands_medium_skin_tone_dark_skin_tone": "1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff", "two_men_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb", "men_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb", "two_men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc", "men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc", "two_men_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd", "men_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd", "two_men_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff", "men_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff", "two_men_holding_hands_dark_skin_tone_light_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb", "men_holding_hands_dark_skin_tone_light_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb", "two_men_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc", "men_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc", "two_men_holding_hands_dark_skin_tone_medium_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd", "men_holding_hands_dark_skin_tone_medium_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd", "two_men_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe", "men_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe", "two_women_holding_hands_light_skin_tone": "1f46d-1f3fb", "women_holding_hands_light_skin_tone": "1f46d-1f3fb", "two_women_holding_hands_medium_light_skin_tone": "1f46d-1f3fc", "women_holding_hands_medium_light_skin_tone": "1f46d-1f3fc", "two_women_holding_hands_medium_skin_tone": "1f46d-1f3fd", "women_holding_hands_medium_skin_tone": "1f46d-1f3fd", "two_women_holding_hands_medium_dark_skin_tone": "1f46d-1f3fe", "women_holding_hands_medium_dark_skin_tone": "1f46d-1f3fe", "two_women_holding_hands_dark_skin_tone": "1f46d-1f3ff", "women_holding_hands_dark_skin_tone": "1f46d-1f3ff", "two_women_holding_hands_light_skin_tone_medium_light_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc", "women_holding_hands_light_skin_tone_medium_light_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc", "two_women_holding_hands_light_skin_tone_medium_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd", "women_holding_hands_light_skin_tone_medium_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd", "two_women_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe", "women_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe", "two_women_holding_hands_light_skin_tone_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff", "women_holding_hands_light_skin_tone_dark_skin_tone": "1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff", "two_women_holding_hands_medium_light_skin_tone_light_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb", "women_holding_hands_medium_light_skin_tone_light_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb", "two_women_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd", "women_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd", "two_women_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe", "women_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe", "two_women_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff", "women_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff", "two_women_holding_hands_medium_skin_tone_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb", "women_holding_hands_medium_skin_tone_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb", "two_women_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc", "women_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc", "two_women_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe", "women_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe", "two_women_holding_hands_medium_skin_tone_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff", "women_holding_hands_medium_skin_tone_dark_skin_tone": "1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff", "two_women_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb", "women_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb", "two_women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc", "women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc", "two_women_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd", "women_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd", "two_women_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff", "women_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff", "two_women_holding_hands_dark_skin_tone_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb", "women_holding_hands_dark_skin_tone_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb", "two_women_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc", "women_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc", "two_women_holding_hands_dark_skin_tone_medium_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd", "women_holding_hands_dark_skin_tone_medium_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd", "two_women_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe", "women_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe", "female-police-officer_light_skin_tone": "1f46e-1f3fb-200d-2640-fe0f", "policewoman_light_skin_tone": "1f46e-1f3fb-200d-2640-fe0f", "female-police-officer_medium_light_skin_tone": "1f46e-1f3fc-200d-2640-fe0f", "policewoman_medium_light_skin_tone": "1f46e-1f3fc-200d-2640-fe0f", "female-police-officer_medium_skin_tone": "1f46e-1f3fd-200d-2640-fe0f", "policewoman_medium_skin_tone": "1f46e-1f3fd-200d-2640-fe0f", "female-police-officer_medium_dark_skin_tone": "1f46e-1f3fe-200d-2640-fe0f", "policewoman_medium_dark_skin_tone": "1f46e-1f3fe-200d-2640-fe0f", "female-police-officer_dark_skin_tone": "1f46e-1f3ff-200d-2640-fe0f", "policewoman_dark_skin_tone": "1f46e-1f3ff-200d-2640-fe0f", "male-police-officer_light_skin_tone": "1f46e-1f3fb-200d-2642-fe0f", "policeman_light_skin_tone": "1f46e-1f3fb-200d-2642-fe0f", "male-police-officer_medium_light_skin_tone": "1f46e-1f3fc-200d-2642-fe0f", "policeman_medium_light_skin_tone": "1f46e-1f3fc-200d-2642-fe0f", "male-police-officer_medium_skin_tone": "1f46e-1f3fd-200d-2642-fe0f", "policeman_medium_skin_tone": "1f46e-1f3fd-200d-2642-fe0f", "male-police-officer_medium_dark_skin_tone": "1f46e-1f3fe-200d-2642-fe0f", "policeman_medium_dark_skin_tone": "1f46e-1f3fe-200d-2642-fe0f", "male-police-officer_dark_skin_tone": "1f46e-1f3ff-200d-2642-fe0f", "policeman_dark_skin_tone": "1f46e-1f3ff-200d-2642-fe0f", "cop_light_skin_tone": "1f46e-1f3fb", "cop_medium_light_skin_tone": "1f46e-1f3fc", "cop_medium_skin_tone": "1f46e-1f3fd", "cop_medium_dark_skin_tone": "1f46e-1f3fe", "cop_dark_skin_tone": "1f46e-1f3ff", "woman_with_veil_light_skin_tone": "1f470-1f3fb-200d-2640-fe0f", "woman_with_veil_medium_light_skin_tone": "1f470-1f3fc-200d-2640-fe0f", "woman_with_veil_medium_skin_tone": "1f470-1f3fd-200d-2640-fe0f", "woman_with_veil_medium_dark_skin_tone": "1f470-1f3fe-200d-2640-fe0f", "woman_with_veil_dark_skin_tone": "1f470-1f3ff-200d-2640-fe0f", "man_with_veil_light_skin_tone": "1f470-1f3fb-200d-2642-fe0f", "man_with_veil_medium_light_skin_tone": "1f470-1f3fc-200d-2642-fe0f", "man_with_veil_medium_skin_tone": "1f470-1f3fd-200d-2642-fe0f", "man_with_veil_medium_dark_skin_tone": "1f470-1f3fe-200d-2642-fe0f", "man_with_veil_dark_skin_tone": "1f470-1f3ff-200d-2642-fe0f", "bride_with_veil_light_skin_tone": "1f470-1f3fb", "bride_with_veil_medium_light_skin_tone": "1f470-1f3fc", "bride_with_veil_medium_skin_tone": "1f470-1f3fd", "bride_with_veil_medium_dark_skin_tone": "1f470-1f3fe", "bride_with_veil_dark_skin_tone": "1f470-1f3ff", "blond-haired-woman_light_skin_tone": "1f471-1f3fb-200d-2640-fe0f", "blonde_woman_light_skin_tone": "1f471-1f3fb-200d-2640-fe0f", "blond-haired-woman_medium_light_skin_tone": "1f471-1f3fc-200d-2640-fe0f", "blonde_woman_medium_light_skin_tone": "1f471-1f3fc-200d-2640-fe0f", "blond-haired-woman_medium_skin_tone": "1f471-1f3fd-200d-2640-fe0f", "blonde_woman_medium_skin_tone": "1f471-1f3fd-200d-2640-fe0f", "blond-haired-woman_medium_dark_skin_tone": "1f471-1f3fe-200d-2640-fe0f", "blonde_woman_medium_dark_skin_tone": "1f471-1f3fe-200d-2640-fe0f", "blond-haired-woman_dark_skin_tone": "1f471-1f3ff-200d-2640-fe0f", "blonde_woman_dark_skin_tone": "1f471-1f3ff-200d-2640-fe0f", "blond-haired-man_light_skin_tone": "1f471-1f3fb-200d-2642-fe0f", "blonde_man_light_skin_tone": "1f471-1f3fb-200d-2642-fe0f", "blond-haired-man_medium_light_skin_tone": "1f471-1f3fc-200d-2642-fe0f", "blonde_man_medium_light_skin_tone": "1f471-1f3fc-200d-2642-fe0f", "blond-haired-man_medium_skin_tone": "1f471-1f3fd-200d-2642-fe0f", "blonde_man_medium_skin_tone": "1f471-1f3fd-200d-2642-fe0f", "blond-haired-man_medium_dark_skin_tone": "1f471-1f3fe-200d-2642-fe0f", "blonde_man_medium_dark_skin_tone": "1f471-1f3fe-200d-2642-fe0f", "blond-haired-man_dark_skin_tone": "1f471-1f3ff-200d-2642-fe0f", "blonde_man_dark_skin_tone": "1f471-1f3ff-200d-2642-fe0f", "person_with_blond_hair_light_skin_tone": "1f471-1f3fb", "person_with_blond_hair_medium_light_skin_tone": "1f471-1f3fc", "person_with_blond_hair_medium_skin_tone": "1f471-1f3fd", "person_with_blond_hair_medium_dark_skin_tone": "1f471-1f3fe", "person_with_blond_hair_dark_skin_tone": "1f471-1f3ff", "man_with_gua_pi_mao_light_skin_tone": "1f472-1f3fb", "man_with_gua_pi_mao_medium_light_skin_tone": "1f472-1f3fc", "man_with_gua_pi_mao_medium_skin_tone": "1f472-1f3fd", "man_with_gua_pi_mao_medium_dark_skin_tone": "1f472-1f3fe", "man_with_gua_pi_mao_dark_skin_tone": "1f472-1f3ff", "woman-wearing-turban_light_skin_tone": "1f473-1f3fb-200d-2640-fe0f", "woman_with_turban_light_skin_tone": "1f473-1f3fb-200d-2640-fe0f", "woman-wearing-turban_medium_light_skin_tone": "1f473-1f3fc-200d-2640-fe0f", "woman_with_turban_medium_light_skin_tone": "1f473-1f3fc-200d-2640-fe0f", "woman-wearing-turban_medium_skin_tone": "1f473-1f3fd-200d-2640-fe0f", "woman_with_turban_medium_skin_tone": "1f473-1f3fd-200d-2640-fe0f", "woman-wearing-turban_medium_dark_skin_tone": "1f473-1f3fe-200d-2640-fe0f", "woman_with_turban_medium_dark_skin_tone": "1f473-1f3fe-200d-2640-fe0f", "woman-wearing-turban_dark_skin_tone": "1f473-1f3ff-200d-2640-fe0f", "woman_with_turban_dark_skin_tone": "1f473-1f3ff-200d-2640-fe0f", "man-wearing-turban_light_skin_tone": "1f473-1f3fb-200d-2642-fe0f", "man-wearing-turban_medium_light_skin_tone": "1f473-1f3fc-200d-2642-fe0f", "man-wearing-turban_medium_skin_tone": "1f473-1f3fd-200d-2642-fe0f", "man-wearing-turban_medium_dark_skin_tone": "1f473-1f3fe-200d-2642-fe0f", "man-wearing-turban_dark_skin_tone": "1f473-1f3ff-200d-2642-fe0f", "man_with_turban_light_skin_tone": "1f473-1f3fb", "man_with_turban_medium_light_skin_tone": "1f473-1f3fc", "man_with_turban_medium_skin_tone": "1f473-1f3fd", "man_with_turban_medium_dark_skin_tone": "1f473-1f3fe", "man_with_turban_dark_skin_tone": "1f473-1f3ff", "older_man_light_skin_tone": "1f474-1f3fb", "older_man_medium_light_skin_tone": "1f474-1f3fc", "older_man_medium_skin_tone": "1f474-1f3fd", "older_man_medium_dark_skin_tone": "1f474-1f3fe", "older_man_dark_skin_tone": "1f474-1f3ff", "older_woman_light_skin_tone": "1f475-1f3fb", "older_woman_medium_light_skin_tone": "1f475-1f3fc", "older_woman_medium_skin_tone": "1f475-1f3fd", "older_woman_medium_dark_skin_tone": "1f475-1f3fe", "older_woman_dark_skin_tone": "1f475-1f3ff", "baby_light_skin_tone": "1f476-1f3fb", "baby_medium_light_skin_tone": "1f476-1f3fc", "baby_medium_skin_tone": "1f476-1f3fd", "baby_medium_dark_skin_tone": "1f476-1f3fe", "baby_dark_skin_tone": "1f476-1f3ff", "female-construction-worker_light_skin_tone": "1f477-1f3fb-200d-2640-fe0f", "construction_worker_woman_light_skin_tone": "1f477-1f3fb-200d-2640-fe0f", "female-construction-worker_medium_light_skin_tone": "1f477-1f3fc-200d-2640-fe0f", "construction_worker_woman_medium_light_skin_tone": "1f477-1f3fc-200d-2640-fe0f", "female-construction-worker_medium_skin_tone": "1f477-1f3fd-200d-2640-fe0f", "construction_worker_woman_medium_skin_tone": "1f477-1f3fd-200d-2640-fe0f", "female-construction-worker_medium_dark_skin_tone": "1f477-1f3fe-200d-2640-fe0f", "construction_worker_woman_medium_dark_skin_tone": "1f477-1f3fe-200d-2640-fe0f", "female-construction-worker_dark_skin_tone": "1f477-1f3ff-200d-2640-fe0f", "construction_worker_woman_dark_skin_tone": "1f477-1f3ff-200d-2640-fe0f", "male-construction-worker_light_skin_tone": "1f477-1f3fb-200d-2642-fe0f", "construction_worker_man_light_skin_tone": "1f477-1f3fb-200d-2642-fe0f", "male-construction-worker_medium_light_skin_tone": "1f477-1f3fc-200d-2642-fe0f", "construction_worker_man_medium_light_skin_tone": "1f477-1f3fc-200d-2642-fe0f", "male-construction-worker_medium_skin_tone": "1f477-1f3fd-200d-2642-fe0f", "construction_worker_man_medium_skin_tone": "1f477-1f3fd-200d-2642-fe0f", "male-construction-worker_medium_dark_skin_tone": "1f477-1f3fe-200d-2642-fe0f", "construction_worker_man_medium_dark_skin_tone": "1f477-1f3fe-200d-2642-fe0f", "male-construction-worker_dark_skin_tone": "1f477-1f3ff-200d-2642-fe0f", "construction_worker_man_dark_skin_tone": "1f477-1f3ff-200d-2642-fe0f", "construction_worker_light_skin_tone": "1f477-1f3fb", "construction_worker_medium_light_skin_tone": "1f477-1f3fc", "construction_worker_medium_skin_tone": "1f477-1f3fd", "construction_worker_medium_dark_skin_tone": "1f477-1f3fe", "construction_worker_dark_skin_tone": "1f477-1f3ff", "princess_light_skin_tone": "1f478-1f3fb", "princess_medium_light_skin_tone": "1f478-1f3fc", "princess_medium_skin_tone": "1f478-1f3fd", "princess_medium_dark_skin_tone": "1f478-1f3fe", "princess_dark_skin_tone": "1f478-1f3ff", "angel_light_skin_tone": "1f47c-1f3fb", "angel_medium_light_skin_tone": "1f47c-1f3fc", "angel_medium_skin_tone": "1f47c-1f3fd", "angel_medium_dark_skin_tone": "1f47c-1f3fe", "angel_dark_skin_tone": "1f47c-1f3ff", "woman-tipping-hand_light_skin_tone": "1f481-1f3fb-200d-2640-fe0f", "tipping_hand_woman_light_skin_tone": "1f481-1f3fb-200d-2640-fe0f", "woman-tipping-hand_medium_light_skin_tone": "1f481-1f3fc-200d-2640-fe0f", "tipping_hand_woman_medium_light_skin_tone": "1f481-1f3fc-200d-2640-fe0f", "woman-tipping-hand_medium_skin_tone": "1f481-1f3fd-200d-2640-fe0f", "tipping_hand_woman_medium_skin_tone": "1f481-1f3fd-200d-2640-fe0f", "woman-tipping-hand_medium_dark_skin_tone": "1f481-1f3fe-200d-2640-fe0f", "tipping_hand_woman_medium_dark_skin_tone": "1f481-1f3fe-200d-2640-fe0f", "woman-tipping-hand_dark_skin_tone": "1f481-1f3ff-200d-2640-fe0f", "tipping_hand_woman_dark_skin_tone": "1f481-1f3ff-200d-2640-fe0f", "man-tipping-hand_light_skin_tone": "1f481-1f3fb-200d-2642-fe0f", "tipping_hand_man_light_skin_tone": "1f481-1f3fb-200d-2642-fe0f", "man-tipping-hand_medium_light_skin_tone": "1f481-1f3fc-200d-2642-fe0f", "tipping_hand_man_medium_light_skin_tone": "1f481-1f3fc-200d-2642-fe0f", "man-tipping-hand_medium_skin_tone": "1f481-1f3fd-200d-2642-fe0f", "tipping_hand_man_medium_skin_tone": "1f481-1f3fd-200d-2642-fe0f", "man-tipping-hand_medium_dark_skin_tone": "1f481-1f3fe-200d-2642-fe0f", "tipping_hand_man_medium_dark_skin_tone": "1f481-1f3fe-200d-2642-fe0f", "man-tipping-hand_dark_skin_tone": "1f481-1f3ff-200d-2642-fe0f", "tipping_hand_man_dark_skin_tone": "1f481-1f3ff-200d-2642-fe0f", "information_desk_person_light_skin_tone": "1f481-1f3fb", "information_desk_person_medium_light_skin_tone": "1f481-1f3fc", "information_desk_person_medium_skin_tone": "1f481-1f3fd", "information_desk_person_medium_dark_skin_tone": "1f481-1f3fe", "information_desk_person_dark_skin_tone": "1f481-1f3ff", "female-guard_light_skin_tone": "1f482-1f3fb-200d-2640-fe0f", "guardswoman_light_skin_tone": "1f482-1f3fb-200d-2640-fe0f", "female-guard_medium_light_skin_tone": "1f482-1f3fc-200d-2640-fe0f", "guardswoman_medium_light_skin_tone": "1f482-1f3fc-200d-2640-fe0f", "female-guard_medium_skin_tone": "1f482-1f3fd-200d-2640-fe0f", "guardswoman_medium_skin_tone": "1f482-1f3fd-200d-2640-fe0f", "female-guard_medium_dark_skin_tone": "1f482-1f3fe-200d-2640-fe0f", "guardswoman_medium_dark_skin_tone": "1f482-1f3fe-200d-2640-fe0f", "female-guard_dark_skin_tone": "1f482-1f3ff-200d-2640-fe0f", "guardswoman_dark_skin_tone": "1f482-1f3ff-200d-2640-fe0f", "male-guard_light_skin_tone": "1f482-1f3fb-200d-2642-fe0f", "male-guard_medium_light_skin_tone": "1f482-1f3fc-200d-2642-fe0f", "male-guard_medium_skin_tone": "1f482-1f3fd-200d-2642-fe0f", "male-guard_medium_dark_skin_tone": "1f482-1f3fe-200d-2642-fe0f", "male-guard_dark_skin_tone": "1f482-1f3ff-200d-2642-fe0f", "guardsman_light_skin_tone": "1f482-1f3fb", "guardsman_medium_light_skin_tone": "1f482-1f3fc", "guardsman_medium_skin_tone": "1f482-1f3fd", "guardsman_medium_dark_skin_tone": "1f482-1f3fe", "guardsman_dark_skin_tone": "1f482-1f3ff", "dancer_light_skin_tone": "1f483-1f3fb", "dancer_medium_light_skin_tone": "1f483-1f3fc", "dancer_medium_skin_tone": "1f483-1f3fd", "dancer_medium_dark_skin_tone": "1f483-1f3fe", "dancer_dark_skin_tone": "1f483-1f3ff", "nail_care_light_skin_tone": "1f485-1f3fb", "nail_care_medium_light_skin_tone": "1f485-1f3fc", "nail_care_medium_skin_tone": "1f485-1f3fd", "nail_care_medium_dark_skin_tone": "1f485-1f3fe", "nail_care_dark_skin_tone": "1f485-1f3ff", "woman-getting-massage_light_skin_tone": "1f486-1f3fb-200d-2640-fe0f", "massage_woman_light_skin_tone": "1f486-1f3fb-200d-2640-fe0f", "woman-getting-massage_medium_light_skin_tone": "1f486-1f3fc-200d-2640-fe0f", "massage_woman_medium_light_skin_tone": "1f486-1f3fc-200d-2640-fe0f", "woman-getting-massage_medium_skin_tone": "1f486-1f3fd-200d-2640-fe0f", "massage_woman_medium_skin_tone": "1f486-1f3fd-200d-2640-fe0f", "woman-getting-massage_medium_dark_skin_tone": "1f486-1f3fe-200d-2640-fe0f", "massage_woman_medium_dark_skin_tone": "1f486-1f3fe-200d-2640-fe0f", "woman-getting-massage_dark_skin_tone": "1f486-1f3ff-200d-2640-fe0f", "massage_woman_dark_skin_tone": "1f486-1f3ff-200d-2640-fe0f", "man-getting-massage_light_skin_tone": "1f486-1f3fb-200d-2642-fe0f", "massage_man_light_skin_tone": "1f486-1f3fb-200d-2642-fe0f", "man-getting-massage_medium_light_skin_tone": "1f486-1f3fc-200d-2642-fe0f", "massage_man_medium_light_skin_tone": "1f486-1f3fc-200d-2642-fe0f", "man-getting-massage_medium_skin_tone": "1f486-1f3fd-200d-2642-fe0f", "massage_man_medium_skin_tone": "1f486-1f3fd-200d-2642-fe0f", "man-getting-massage_medium_dark_skin_tone": "1f486-1f3fe-200d-2642-fe0f", "massage_man_medium_dark_skin_tone": "1f486-1f3fe-200d-2642-fe0f", "man-getting-massage_dark_skin_tone": "1f486-1f3ff-200d-2642-fe0f", "massage_man_dark_skin_tone": "1f486-1f3ff-200d-2642-fe0f", "massage_light_skin_tone": "1f486-1f3fb", "massage_medium_light_skin_tone": "1f486-1f3fc", "massage_medium_skin_tone": "1f486-1f3fd", "massage_medium_dark_skin_tone": "1f486-1f3fe", "massage_dark_skin_tone": "1f486-1f3ff", "woman-getting-haircut_light_skin_tone": "1f487-1f3fb-200d-2640-fe0f", "haircut_woman_light_skin_tone": "1f487-1f3fb-200d-2640-fe0f", "woman-getting-haircut_medium_light_skin_tone": "1f487-1f3fc-200d-2640-fe0f", "haircut_woman_medium_light_skin_tone": "1f487-1f3fc-200d-2640-fe0f", "woman-getting-haircut_medium_skin_tone": "1f487-1f3fd-200d-2640-fe0f", "haircut_woman_medium_skin_tone": "1f487-1f3fd-200d-2640-fe0f", "woman-getting-haircut_medium_dark_skin_tone": "1f487-1f3fe-200d-2640-fe0f", "haircut_woman_medium_dark_skin_tone": "1f487-1f3fe-200d-2640-fe0f", "woman-getting-haircut_dark_skin_tone": "1f487-1f3ff-200d-2640-fe0f", "haircut_woman_dark_skin_tone": "1f487-1f3ff-200d-2640-fe0f", "man-getting-haircut_light_skin_tone": "1f487-1f3fb-200d-2642-fe0f", "haircut_man_light_skin_tone": "1f487-1f3fb-200d-2642-fe0f", "man-getting-haircut_medium_light_skin_tone": "1f487-1f3fc-200d-2642-fe0f", "haircut_man_medium_light_skin_tone": "1f487-1f3fc-200d-2642-fe0f", "man-getting-haircut_medium_skin_tone": "1f487-1f3fd-200d-2642-fe0f", "haircut_man_medium_skin_tone": "1f487-1f3fd-200d-2642-fe0f", "man-getting-haircut_medium_dark_skin_tone": "1f487-1f3fe-200d-2642-fe0f", "haircut_man_medium_dark_skin_tone": "1f487-1f3fe-200d-2642-fe0f", "man-getting-haircut_dark_skin_tone": "1f487-1f3ff-200d-2642-fe0f", "haircut_man_dark_skin_tone": "1f487-1f3ff-200d-2642-fe0f", "haircut_light_skin_tone": "1f487-1f3fb", "haircut_medium_light_skin_tone": "1f487-1f3fc", "haircut_medium_skin_tone": "1f487-1f3fd", "haircut_medium_dark_skin_tone": "1f487-1f3fe", "haircut_dark_skin_tone": "1f487-1f3ff", "muscle_light_skin_tone": "1f4aa-1f3fb", "muscle_medium_light_skin_tone": "1f4aa-1f3fc", "muscle_medium_skin_tone": "1f4aa-1f3fd", "muscle_medium_dark_skin_tone": "1f4aa-1f3fe", "muscle_dark_skin_tone": "1f4aa-1f3ff", "man_in_business_suit_levitating_light_skin_tone": "1f574-1f3fb", "business_suit_levitating_light_skin_tone": "1f574-1f3fb", "man_in_business_suit_levitating_medium_light_skin_tone": "1f574-1f3fc", "business_suit_levitating_medium_light_skin_tone": "1f574-1f3fc", "man_in_business_suit_levitating_medium_skin_tone": "1f574-1f3fd", "business_suit_levitating_medium_skin_tone": "1f574-1f3fd", "man_in_business_suit_levitating_medium_dark_skin_tone": "1f574-1f3fe", "business_suit_levitating_medium_dark_skin_tone": "1f574-1f3fe", "man_in_business_suit_levitating_dark_skin_tone": "1f574-1f3ff", "business_suit_levitating_dark_skin_tone": "1f574-1f3ff", "female-detective_light_skin_tone": "1f575-1f3fb-200d-2640-fe0f", "female_detective_light_skin_tone": "1f575-1f3fb-200d-2640-fe0f", "female-detective_medium_light_skin_tone": "1f575-1f3fc-200d-2640-fe0f", "female_detective_medium_light_skin_tone": "1f575-1f3fc-200d-2640-fe0f", "female-detective_medium_skin_tone": "1f575-1f3fd-200d-2640-fe0f", "female_detective_medium_skin_tone": "1f575-1f3fd-200d-2640-fe0f", "female-detective_medium_dark_skin_tone": "1f575-1f3fe-200d-2640-fe0f", "female_detective_medium_dark_skin_tone": "1f575-1f3fe-200d-2640-fe0f", "female-detective_dark_skin_tone": "1f575-1f3ff-200d-2640-fe0f", "female_detective_dark_skin_tone": "1f575-1f3ff-200d-2640-fe0f", "male-detective_light_skin_tone": "1f575-1f3fb-200d-2642-fe0f", "male_detective_light_skin_tone": "1f575-1f3fb-200d-2642-fe0f", "male-detective_medium_light_skin_tone": "1f575-1f3fc-200d-2642-fe0f", "male_detective_medium_light_skin_tone": "1f575-1f3fc-200d-2642-fe0f", "male-detective_medium_skin_tone": "1f575-1f3fd-200d-2642-fe0f", "male_detective_medium_skin_tone": "1f575-1f3fd-200d-2642-fe0f", "male-detective_medium_dark_skin_tone": "1f575-1f3fe-200d-2642-fe0f", "male_detective_medium_dark_skin_tone": "1f575-1f3fe-200d-2642-fe0f", "male-detective_dark_skin_tone": "1f575-1f3ff-200d-2642-fe0f", "male_detective_dark_skin_tone": "1f575-1f3ff-200d-2642-fe0f", "sleuth_or_spy_light_skin_tone": "1f575-1f3fb", "sleuth_or_spy_medium_light_skin_tone": "1f575-1f3fc", "sleuth_or_spy_medium_skin_tone": "1f575-1f3fd", "sleuth_or_spy_medium_dark_skin_tone": "1f575-1f3fe", "sleuth_or_spy_dark_skin_tone": "1f575-1f3ff", "man_dancing_light_skin_tone": "1f57a-1f3fb", "man_dancing_medium_light_skin_tone": "1f57a-1f3fc", "man_dancing_medium_skin_tone": "1f57a-1f3fd", "man_dancing_medium_dark_skin_tone": "1f57a-1f3fe", "man_dancing_dark_skin_tone": "1f57a-1f3ff", "raised_hand_with_fingers_splayed_light_skin_tone": "1f590-1f3fb", "raised_hand_with_fingers_splayed_medium_light_skin_tone": "1f590-1f3fc", "raised_hand_with_fingers_splayed_medium_skin_tone": "1f590-1f3fd", "raised_hand_with_fingers_splayed_medium_dark_skin_tone": "1f590-1f3fe", "raised_hand_with_fingers_splayed_dark_skin_tone": "1f590-1f3ff", "middle_finger_light_skin_tone": "1f595-1f3fb", "reversed_hand_with_middle_finger_extended_light_skin_tone": "1f595-1f3fb", "middle_finger_medium_light_skin_tone": "1f595-1f3fc", "reversed_hand_with_middle_finger_extended_medium_light_skin_tone": "1f595-1f3fc", "middle_finger_medium_skin_tone": "1f595-1f3fd", "reversed_hand_with_middle_finger_extended_medium_skin_tone": "1f595-1f3fd", "middle_finger_medium_dark_skin_tone": "1f595-1f3fe", "reversed_hand_with_middle_finger_extended_medium_dark_skin_tone": "1f595-1f3fe", "middle_finger_dark_skin_tone": "1f595-1f3ff", "reversed_hand_with_middle_finger_extended_dark_skin_tone": "1f595-1f3ff", "spock-hand_light_skin_tone": "1f596-1f3fb", "vulcan_salute_light_skin_tone": "1f596-1f3fb", "spock-hand_medium_light_skin_tone": "1f596-1f3fc", "vulcan_salute_medium_light_skin_tone": "1f596-1f3fc", "spock-hand_medium_skin_tone": "1f596-1f3fd", "vulcan_salute_medium_skin_tone": "1f596-1f3fd", "spock-hand_medium_dark_skin_tone": "1f596-1f3fe", "vulcan_salute_medium_dark_skin_tone": "1f596-1f3fe", "spock-hand_dark_skin_tone": "1f596-1f3ff", "vulcan_salute_dark_skin_tone": "1f596-1f3ff", "woman-gesturing-no_light_skin_tone": "1f645-1f3fb-200d-2640-fe0f", "no_good_woman_light_skin_tone": "1f645-1f3fb-200d-2640-fe0f", "woman-gesturing-no_medium_light_skin_tone": "1f645-1f3fc-200d-2640-fe0f", "no_good_woman_medium_light_skin_tone": "1f645-1f3fc-200d-2640-fe0f", "woman-gesturing-no_medium_skin_tone": "1f645-1f3fd-200d-2640-fe0f", "no_good_woman_medium_skin_tone": "1f645-1f3fd-200d-2640-fe0f", "woman-gesturing-no_medium_dark_skin_tone": "1f645-1f3fe-200d-2640-fe0f", "no_good_woman_medium_dark_skin_tone": "1f645-1f3fe-200d-2640-fe0f", "woman-gesturing-no_dark_skin_tone": "1f645-1f3ff-200d-2640-fe0f", "no_good_woman_dark_skin_tone": "1f645-1f3ff-200d-2640-fe0f", "man-gesturing-no_light_skin_tone": "1f645-1f3fb-200d-2642-fe0f", "no_good_man_light_skin_tone": "1f645-1f3fb-200d-2642-fe0f", "man-gesturing-no_medium_light_skin_tone": "1f645-1f3fc-200d-2642-fe0f", "no_good_man_medium_light_skin_tone": "1f645-1f3fc-200d-2642-fe0f", "man-gesturing-no_medium_skin_tone": "1f645-1f3fd-200d-2642-fe0f", "no_good_man_medium_skin_tone": "1f645-1f3fd-200d-2642-fe0f", "man-gesturing-no_medium_dark_skin_tone": "1f645-1f3fe-200d-2642-fe0f", "no_good_man_medium_dark_skin_tone": "1f645-1f3fe-200d-2642-fe0f", "man-gesturing-no_dark_skin_tone": "1f645-1f3ff-200d-2642-fe0f", "no_good_man_dark_skin_tone": "1f645-1f3ff-200d-2642-fe0f", "no_good_light_skin_tone": "1f645-1f3fb", "no_good_medium_light_skin_tone": "1f645-1f3fc", "no_good_medium_skin_tone": "1f645-1f3fd", "no_good_medium_dark_skin_tone": "1f645-1f3fe", "no_good_dark_skin_tone": "1f645-1f3ff", "woman-gesturing-ok_light_skin_tone": "1f646-1f3fb-200d-2640-fe0f", "woman-gesturing-ok_medium_light_skin_tone": "1f646-1f3fc-200d-2640-fe0f", "woman-gesturing-ok_medium_skin_tone": "1f646-1f3fd-200d-2640-fe0f", "woman-gesturing-ok_medium_dark_skin_tone": "1f646-1f3fe-200d-2640-fe0f", "woman-gesturing-ok_dark_skin_tone": "1f646-1f3ff-200d-2640-fe0f", "man-gesturing-ok_light_skin_tone": "1f646-1f3fb-200d-2642-fe0f", "ok_man_light_skin_tone": "1f646-1f3fb-200d-2642-fe0f", "man-gesturing-ok_medium_light_skin_tone": "1f646-1f3fc-200d-2642-fe0f", "ok_man_medium_light_skin_tone": "1f646-1f3fc-200d-2642-fe0f", "man-gesturing-ok_medium_skin_tone": "1f646-1f3fd-200d-2642-fe0f", "ok_man_medium_skin_tone": "1f646-1f3fd-200d-2642-fe0f", "man-gesturing-ok_medium_dark_skin_tone": "1f646-1f3fe-200d-2642-fe0f", "ok_man_medium_dark_skin_tone": "1f646-1f3fe-200d-2642-fe0f", "man-gesturing-ok_dark_skin_tone": "1f646-1f3ff-200d-2642-fe0f", "ok_man_dark_skin_tone": "1f646-1f3ff-200d-2642-fe0f", "ok_woman_light_skin_tone": "1f646-1f3fb", "ok_woman_medium_light_skin_tone": "1f646-1f3fc", "ok_woman_medium_skin_tone": "1f646-1f3fd", "ok_woman_medium_dark_skin_tone": "1f646-1f3fe", "ok_woman_dark_skin_tone": "1f646-1f3ff", "woman-bowing_light_skin_tone": "1f647-1f3fb-200d-2640-fe0f", "bowing_woman_light_skin_tone": "1f647-1f3fb-200d-2640-fe0f", "woman-bowing_medium_light_skin_tone": "1f647-1f3fc-200d-2640-fe0f", "bowing_woman_medium_light_skin_tone": "1f647-1f3fc-200d-2640-fe0f", "woman-bowing_medium_skin_tone": "1f647-1f3fd-200d-2640-fe0f", "bowing_woman_medium_skin_tone": "1f647-1f3fd-200d-2640-fe0f", "woman-bowing_medium_dark_skin_tone": "1f647-1f3fe-200d-2640-fe0f", "bowing_woman_medium_dark_skin_tone": "1f647-1f3fe-200d-2640-fe0f", "woman-bowing_dark_skin_tone": "1f647-1f3ff-200d-2640-fe0f", "bowing_woman_dark_skin_tone": "1f647-1f3ff-200d-2640-fe0f", "man-bowing_light_skin_tone": "1f647-1f3fb-200d-2642-fe0f", "bowing_man_light_skin_tone": "1f647-1f3fb-200d-2642-fe0f", "man-bowing_medium_light_skin_tone": "1f647-1f3fc-200d-2642-fe0f", "bowing_man_medium_light_skin_tone": "1f647-1f3fc-200d-2642-fe0f", "man-bowing_medium_skin_tone": "1f647-1f3fd-200d-2642-fe0f", "bowing_man_medium_skin_tone": "1f647-1f3fd-200d-2642-fe0f", "man-bowing_medium_dark_skin_tone": "1f647-1f3fe-200d-2642-fe0f", "bowing_man_medium_dark_skin_tone": "1f647-1f3fe-200d-2642-fe0f", "man-bowing_dark_skin_tone": "1f647-1f3ff-200d-2642-fe0f", "bowing_man_dark_skin_tone": "1f647-1f3ff-200d-2642-fe0f", "bow_light_skin_tone": "1f647-1f3fb", "bow_medium_light_skin_tone": "1f647-1f3fc", "bow_medium_skin_tone": "1f647-1f3fd", "bow_medium_dark_skin_tone": "1f647-1f3fe", "bow_dark_skin_tone": "1f647-1f3ff", "woman-raising-hand_light_skin_tone": "1f64b-1f3fb-200d-2640-fe0f", "raising_hand_woman_light_skin_tone": "1f64b-1f3fb-200d-2640-fe0f", "woman-raising-hand_medium_light_skin_tone": "1f64b-1f3fc-200d-2640-fe0f", "raising_hand_woman_medium_light_skin_tone": "1f64b-1f3fc-200d-2640-fe0f", "woman-raising-hand_medium_skin_tone": "1f64b-1f3fd-200d-2640-fe0f", "raising_hand_woman_medium_skin_tone": "1f64b-1f3fd-200d-2640-fe0f", "woman-raising-hand_medium_dark_skin_tone": "1f64b-1f3fe-200d-2640-fe0f", "raising_hand_woman_medium_dark_skin_tone": "1f64b-1f3fe-200d-2640-fe0f", "woman-raising-hand_dark_skin_tone": "1f64b-1f3ff-200d-2640-fe0f", "raising_hand_woman_dark_skin_tone": "1f64b-1f3ff-200d-2640-fe0f", "man-raising-hand_light_skin_tone": "1f64b-1f3fb-200d-2642-fe0f", "raising_hand_man_light_skin_tone": "1f64b-1f3fb-200d-2642-fe0f", "man-raising-hand_medium_light_skin_tone": "1f64b-1f3fc-200d-2642-fe0f", "raising_hand_man_medium_light_skin_tone": "1f64b-1f3fc-200d-2642-fe0f", "man-raising-hand_medium_skin_tone": "1f64b-1f3fd-200d-2642-fe0f", "raising_hand_man_medium_skin_tone": "1f64b-1f3fd-200d-2642-fe0f", "man-raising-hand_medium_dark_skin_tone": "1f64b-1f3fe-200d-2642-fe0f", "raising_hand_man_medium_dark_skin_tone": "1f64b-1f3fe-200d-2642-fe0f", "man-raising-hand_dark_skin_tone": "1f64b-1f3ff-200d-2642-fe0f", "raising_hand_man_dark_skin_tone": "1f64b-1f3ff-200d-2642-fe0f", "raising_hand_light_skin_tone": "1f64b-1f3fb", "raising_hand_medium_light_skin_tone": "1f64b-1f3fc", "raising_hand_medium_skin_tone": "1f64b-1f3fd", "raising_hand_medium_dark_skin_tone": "1f64b-1f3fe", "raising_hand_dark_skin_tone": "1f64b-1f3ff", "raised_hands_light_skin_tone": "1f64c-1f3fb", "raised_hands_medium_light_skin_tone": "1f64c-1f3fc", "raised_hands_medium_skin_tone": "1f64c-1f3fd", "raised_hands_medium_dark_skin_tone": "1f64c-1f3fe", "raised_hands_dark_skin_tone": "1f64c-1f3ff", "woman-frowning_light_skin_tone": "1f64d-1f3fb-200d-2640-fe0f", "frowning_woman_light_skin_tone": "1f64d-1f3fb-200d-2640-fe0f", "woman-frowning_medium_light_skin_tone": "1f64d-1f3fc-200d-2640-fe0f", "frowning_woman_medium_light_skin_tone": "1f64d-1f3fc-200d-2640-fe0f", "woman-frowning_medium_skin_tone": "1f64d-1f3fd-200d-2640-fe0f", "frowning_woman_medium_skin_tone": "1f64d-1f3fd-200d-2640-fe0f", "woman-frowning_medium_dark_skin_tone": "1f64d-1f3fe-200d-2640-fe0f", "frowning_woman_medium_dark_skin_tone": "1f64d-1f3fe-200d-2640-fe0f", "woman-frowning_dark_skin_tone": "1f64d-1f3ff-200d-2640-fe0f", "frowning_woman_dark_skin_tone": "1f64d-1f3ff-200d-2640-fe0f", "man-frowning_light_skin_tone": "1f64d-1f3fb-200d-2642-fe0f", "frowning_man_light_skin_tone": "1f64d-1f3fb-200d-2642-fe0f", "man-frowning_medium_light_skin_tone": "1f64d-1f3fc-200d-2642-fe0f", "frowning_man_medium_light_skin_tone": "1f64d-1f3fc-200d-2642-fe0f", "man-frowning_medium_skin_tone": "1f64d-1f3fd-200d-2642-fe0f", "frowning_man_medium_skin_tone": "1f64d-1f3fd-200d-2642-fe0f", "man-frowning_medium_dark_skin_tone": "1f64d-1f3fe-200d-2642-fe0f", "frowning_man_medium_dark_skin_tone": "1f64d-1f3fe-200d-2642-fe0f", "man-frowning_dark_skin_tone": "1f64d-1f3ff-200d-2642-fe0f", "frowning_man_dark_skin_tone": "1f64d-1f3ff-200d-2642-fe0f", "person_frowning_light_skin_tone": "1f64d-1f3fb", "person_frowning_medium_light_skin_tone": "1f64d-1f3fc", "person_frowning_medium_skin_tone": "1f64d-1f3fd", "person_frowning_medium_dark_skin_tone": "1f64d-1f3fe", "person_frowning_dark_skin_tone": "1f64d-1f3ff", "woman-pouting_light_skin_tone": "1f64e-1f3fb-200d-2640-fe0f", "pouting_woman_light_skin_tone": "1f64e-1f3fb-200d-2640-fe0f", "woman-pouting_medium_light_skin_tone": "1f64e-1f3fc-200d-2640-fe0f", "pouting_woman_medium_light_skin_tone": "1f64e-1f3fc-200d-2640-fe0f", "woman-pouting_medium_skin_tone": "1f64e-1f3fd-200d-2640-fe0f", "pouting_woman_medium_skin_tone": "1f64e-1f3fd-200d-2640-fe0f", "woman-pouting_medium_dark_skin_tone": "1f64e-1f3fe-200d-2640-fe0f", "pouting_woman_medium_dark_skin_tone": "1f64e-1f3fe-200d-2640-fe0f", "woman-pouting_dark_skin_tone": "1f64e-1f3ff-200d-2640-fe0f", "pouting_woman_dark_skin_tone": "1f64e-1f3ff-200d-2640-fe0f", "man-pouting_light_skin_tone": "1f64e-1f3fb-200d-2642-fe0f", "pouting_man_light_skin_tone": "1f64e-1f3fb-200d-2642-fe0f", "man-pouting_medium_light_skin_tone": "1f64e-1f3fc-200d-2642-fe0f", "pouting_man_medium_light_skin_tone": "1f64e-1f3fc-200d-2642-fe0f", "man-pouting_medium_skin_tone": "1f64e-1f3fd-200d-2642-fe0f", "pouting_man_medium_skin_tone": "1f64e-1f3fd-200d-2642-fe0f", "man-pouting_medium_dark_skin_tone": "1f64e-1f3fe-200d-2642-fe0f", "pouting_man_medium_dark_skin_tone": "1f64e-1f3fe-200d-2642-fe0f", "man-pouting_dark_skin_tone": "1f64e-1f3ff-200d-2642-fe0f", "pouting_man_dark_skin_tone": "1f64e-1f3ff-200d-2642-fe0f", "person_with_pouting_face_light_skin_tone": "1f64e-1f3fb", "person_with_pouting_face_medium_light_skin_tone": "1f64e-1f3fc", "person_with_pouting_face_medium_skin_tone": "1f64e-1f3fd", "person_with_pouting_face_medium_dark_skin_tone": "1f64e-1f3fe", "person_with_pouting_face_dark_skin_tone": "1f64e-1f3ff", "pray_light_skin_tone": "1f64f-1f3fb", "pray_medium_light_skin_tone": "1f64f-1f3fc", "pray_medium_skin_tone": "1f64f-1f3fd", "pray_medium_dark_skin_tone": "1f64f-1f3fe", "pray_dark_skin_tone": "1f64f-1f3ff", "woman-rowing-boat_light_skin_tone": "1f6a3-1f3fb-200d-2640-fe0f", "rowing_woman_light_skin_tone": "1f6a3-1f3fb-200d-2640-fe0f", "woman-rowing-boat_medium_light_skin_tone": "1f6a3-1f3fc-200d-2640-fe0f", "rowing_woman_medium_light_skin_tone": "1f6a3-1f3fc-200d-2640-fe0f", "woman-rowing-boat_medium_skin_tone": "1f6a3-1f3fd-200d-2640-fe0f", "rowing_woman_medium_skin_tone": "1f6a3-1f3fd-200d-2640-fe0f", "woman-rowing-boat_medium_dark_skin_tone": "1f6a3-1f3fe-200d-2640-fe0f", "rowing_woman_medium_dark_skin_tone": "1f6a3-1f3fe-200d-2640-fe0f", "woman-rowing-boat_dark_skin_tone": "1f6a3-1f3ff-200d-2640-fe0f", "rowing_woman_dark_skin_tone": "1f6a3-1f3ff-200d-2640-fe0f", "man-rowing-boat_light_skin_tone": "1f6a3-1f3fb-200d-2642-fe0f", "rowing_man_light_skin_tone": "1f6a3-1f3fb-200d-2642-fe0f", "man-rowing-boat_medium_light_skin_tone": "1f6a3-1f3fc-200d-2642-fe0f", "rowing_man_medium_light_skin_tone": "1f6a3-1f3fc-200d-2642-fe0f", "man-rowing-boat_medium_skin_tone": "1f6a3-1f3fd-200d-2642-fe0f", "rowing_man_medium_skin_tone": "1f6a3-1f3fd-200d-2642-fe0f", "man-rowing-boat_medium_dark_skin_tone": "1f6a3-1f3fe-200d-2642-fe0f", "rowing_man_medium_dark_skin_tone": "1f6a3-1f3fe-200d-2642-fe0f", "man-rowing-boat_dark_skin_tone": "1f6a3-1f3ff-200d-2642-fe0f", "rowing_man_dark_skin_tone": "1f6a3-1f3ff-200d-2642-fe0f", "rowboat_light_skin_tone": "1f6a3-1f3fb", "rowboat_medium_light_skin_tone": "1f6a3-1f3fc", "rowboat_medium_skin_tone": "1f6a3-1f3fd", "rowboat_medium_dark_skin_tone": "1f6a3-1f3fe", "rowboat_dark_skin_tone": "1f6a3-1f3ff", "woman-biking_light_skin_tone": "1f6b4-1f3fb-200d-2640-fe0f", "biking_woman_light_skin_tone": "1f6b4-1f3fb-200d-2640-fe0f", "woman-biking_medium_light_skin_tone": "1f6b4-1f3fc-200d-2640-fe0f", "biking_woman_medium_light_skin_tone": "1f6b4-1f3fc-200d-2640-fe0f", "woman-biking_medium_skin_tone": "1f6b4-1f3fd-200d-2640-fe0f", "biking_woman_medium_skin_tone": "1f6b4-1f3fd-200d-2640-fe0f", "woman-biking_medium_dark_skin_tone": "1f6b4-1f3fe-200d-2640-fe0f", "biking_woman_medium_dark_skin_tone": "1f6b4-1f3fe-200d-2640-fe0f", "woman-biking_dark_skin_tone": "1f6b4-1f3ff-200d-2640-fe0f", "biking_woman_dark_skin_tone": "1f6b4-1f3ff-200d-2640-fe0f", "man-biking_light_skin_tone": "1f6b4-1f3fb-200d-2642-fe0f", "biking_man_light_skin_tone": "1f6b4-1f3fb-200d-2642-fe0f", "man-biking_medium_light_skin_tone": "1f6b4-1f3fc-200d-2642-fe0f", "biking_man_medium_light_skin_tone": "1f6b4-1f3fc-200d-2642-fe0f", "man-biking_medium_skin_tone": "1f6b4-1f3fd-200d-2642-fe0f", "biking_man_medium_skin_tone": "1f6b4-1f3fd-200d-2642-fe0f", "man-biking_medium_dark_skin_tone": "1f6b4-1f3fe-200d-2642-fe0f", "biking_man_medium_dark_skin_tone": "1f6b4-1f3fe-200d-2642-fe0f", "man-biking_dark_skin_tone": "1f6b4-1f3ff-200d-2642-fe0f", "biking_man_dark_skin_tone": "1f6b4-1f3ff-200d-2642-fe0f", "bicyclist_light_skin_tone": "1f6b4-1f3fb", "bicyclist_medium_light_skin_tone": "1f6b4-1f3fc", "bicyclist_medium_skin_tone": "1f6b4-1f3fd", "bicyclist_medium_dark_skin_tone": "1f6b4-1f3fe", "bicyclist_dark_skin_tone": "1f6b4-1f3ff", "woman-mountain-biking_light_skin_tone": "1f6b5-1f3fb-200d-2640-fe0f", "mountain_biking_woman_light_skin_tone": "1f6b5-1f3fb-200d-2640-fe0f", "woman-mountain-biking_medium_light_skin_tone": "1f6b5-1f3fc-200d-2640-fe0f", "mountain_biking_woman_medium_light_skin_tone": "1f6b5-1f3fc-200d-2640-fe0f", "woman-mountain-biking_medium_skin_tone": "1f6b5-1f3fd-200d-2640-fe0f", "mountain_biking_woman_medium_skin_tone": "1f6b5-1f3fd-200d-2640-fe0f", "woman-mountain-biking_medium_dark_skin_tone": "1f6b5-1f3fe-200d-2640-fe0f", "mountain_biking_woman_medium_dark_skin_tone": "1f6b5-1f3fe-200d-2640-fe0f", "woman-mountain-biking_dark_skin_tone": "1f6b5-1f3ff-200d-2640-fe0f", "mountain_biking_woman_dark_skin_tone": "1f6b5-1f3ff-200d-2640-fe0f", "man-mountain-biking_light_skin_tone": "1f6b5-1f3fb-200d-2642-fe0f", "mountain_biking_man_light_skin_tone": "1f6b5-1f3fb-200d-2642-fe0f", "man-mountain-biking_medium_light_skin_tone": "1f6b5-1f3fc-200d-2642-fe0f", "mountain_biking_man_medium_light_skin_tone": "1f6b5-1f3fc-200d-2642-fe0f", "man-mountain-biking_medium_skin_tone": "1f6b5-1f3fd-200d-2642-fe0f", "mountain_biking_man_medium_skin_tone": "1f6b5-1f3fd-200d-2642-fe0f", "man-mountain-biking_medium_dark_skin_tone": "1f6b5-1f3fe-200d-2642-fe0f", "mountain_biking_man_medium_dark_skin_tone": "1f6b5-1f3fe-200d-2642-fe0f", "man-mountain-biking_dark_skin_tone": "1f6b5-1f3ff-200d-2642-fe0f", "mountain_biking_man_dark_skin_tone": "1f6b5-1f3ff-200d-2642-fe0f", "mountain_bicyclist_light_skin_tone": "1f6b5-1f3fb", "mountain_bicyclist_medium_light_skin_tone": "1f6b5-1f3fc", "mountain_bicyclist_medium_skin_tone": "1f6b5-1f3fd", "mountain_bicyclist_medium_dark_skin_tone": "1f6b5-1f3fe", "mountain_bicyclist_dark_skin_tone": "1f6b5-1f3ff", "woman-walking_light_skin_tone": "1f6b6-1f3fb-200d-2640-fe0f", "walking_woman_light_skin_tone": "1f6b6-1f3fb-200d-2640-fe0f", "woman-walking_medium_light_skin_tone": "1f6b6-1f3fc-200d-2640-fe0f", "walking_woman_medium_light_skin_tone": "1f6b6-1f3fc-200d-2640-fe0f", "woman-walking_medium_skin_tone": "1f6b6-1f3fd-200d-2640-fe0f", "walking_woman_medium_skin_tone": "1f6b6-1f3fd-200d-2640-fe0f", "woman-walking_medium_dark_skin_tone": "1f6b6-1f3fe-200d-2640-fe0f", "walking_woman_medium_dark_skin_tone": "1f6b6-1f3fe-200d-2640-fe0f", "woman-walking_dark_skin_tone": "1f6b6-1f3ff-200d-2640-fe0f", "walking_woman_dark_skin_tone": "1f6b6-1f3ff-200d-2640-fe0f", "man-walking_light_skin_tone": "1f6b6-1f3fb-200d-2642-fe0f", "walking_man_light_skin_tone": "1f6b6-1f3fb-200d-2642-fe0f", "man-walking_medium_light_skin_tone": "1f6b6-1f3fc-200d-2642-fe0f", "walking_man_medium_light_skin_tone": "1f6b6-1f3fc-200d-2642-fe0f", "man-walking_medium_skin_tone": "1f6b6-1f3fd-200d-2642-fe0f", "walking_man_medium_skin_tone": "1f6b6-1f3fd-200d-2642-fe0f", "man-walking_medium_dark_skin_tone": "1f6b6-1f3fe-200d-2642-fe0f", "walking_man_medium_dark_skin_tone": "1f6b6-1f3fe-200d-2642-fe0f", "man-walking_dark_skin_tone": "1f6b6-1f3ff-200d-2642-fe0f", "walking_man_dark_skin_tone": "1f6b6-1f3ff-200d-2642-fe0f", "walking_light_skin_tone": "1f6b6-1f3fb", "walking_medium_light_skin_tone": "1f6b6-1f3fc", "walking_medium_skin_tone": "1f6b6-1f3fd", "walking_medium_dark_skin_tone": "1f6b6-1f3fe", "walking_dark_skin_tone": "1f6b6-1f3ff", "bath_light_skin_tone": "1f6c0-1f3fb", "bath_medium_light_skin_tone": "1f6c0-1f3fc", "bath_medium_skin_tone": "1f6c0-1f3fd", "bath_medium_dark_skin_tone": "1f6c0-1f3fe", "bath_dark_skin_tone": "1f6c0-1f3ff", "sleeping_accommodation_light_skin_tone": "1f6cc-1f3fb", "sleeping_accommodation_medium_light_skin_tone": "1f6cc-1f3fc", "sleeping_accommodation_medium_skin_tone": "1f6cc-1f3fd", "sleeping_accommodation_medium_dark_skin_tone": "1f6cc-1f3fe", "sleeping_accommodation_dark_skin_tone": "1f6cc-1f3ff", "pinched_fingers_light_skin_tone": "1f90c-1f3fb", "pinched_fingers_medium_light_skin_tone": "1f90c-1f3fc", "pinched_fingers_medium_skin_tone": "1f90c-1f3fd", "pinched_fingers_medium_dark_skin_tone": "1f90c-1f3fe", "pinched_fingers_dark_skin_tone": "1f90c-1f3ff", "pinching_hand_light_skin_tone": "1f90f-1f3fb", "pinching_hand_medium_light_skin_tone": "1f90f-1f3fc", "pinching_hand_medium_skin_tone": "1f90f-1f3fd", "pinching_hand_medium_dark_skin_tone": "1f90f-1f3fe", "pinching_hand_dark_skin_tone": "1f90f-1f3ff", "the_horns_light_skin_tone": "1f918-1f3fb", "sign_of_the_horns_light_skin_tone": "1f918-1f3fb", "metal_light_skin_tone": "1f918-1f3fb", "the_horns_medium_light_skin_tone": "1f918-1f3fc", "sign_of_the_horns_medium_light_skin_tone": "1f918-1f3fc", "metal_medium_light_skin_tone": "1f918-1f3fc", "the_horns_medium_skin_tone": "1f918-1f3fd", "sign_of_the_horns_medium_skin_tone": "1f918-1f3fd", "metal_medium_skin_tone": "1f918-1f3fd", "the_horns_medium_dark_skin_tone": "1f918-1f3fe", "sign_of_the_horns_medium_dark_skin_tone": "1f918-1f3fe", "metal_medium_dark_skin_tone": "1f918-1f3fe", "the_horns_dark_skin_tone": "1f918-1f3ff", "sign_of_the_horns_dark_skin_tone": "1f918-1f3ff", "metal_dark_skin_tone": "1f918-1f3ff", "call_me_hand_light_skin_tone": "1f919-1f3fb", "call_me_hand_medium_light_skin_tone": "1f919-1f3fc", "call_me_hand_medium_skin_tone": "1f919-1f3fd", "call_me_hand_medium_dark_skin_tone": "1f919-1f3fe", "call_me_hand_dark_skin_tone": "1f919-1f3ff", "raised_back_of_hand_light_skin_tone": "1f91a-1f3fb", "raised_back_of_hand_medium_light_skin_tone": "1f91a-1f3fc", "raised_back_of_hand_medium_skin_tone": "1f91a-1f3fd", "raised_back_of_hand_medium_dark_skin_tone": "1f91a-1f3fe", "raised_back_of_hand_dark_skin_tone": "1f91a-1f3ff", "left-facing_fist_light_skin_tone": "1f91b-1f3fb", "fist_left_light_skin_tone": "1f91b-1f3fb", "left-facing_fist_medium_light_skin_tone": "1f91b-1f3fc", "fist_left_medium_light_skin_tone": "1f91b-1f3fc", "left-facing_fist_medium_skin_tone": "1f91b-1f3fd", "fist_left_medium_skin_tone": "1f91b-1f3fd", "left-facing_fist_medium_dark_skin_tone": "1f91b-1f3fe", "fist_left_medium_dark_skin_tone": "1f91b-1f3fe", "left-facing_fist_dark_skin_tone": "1f91b-1f3ff", "fist_left_dark_skin_tone": "1f91b-1f3ff", "right-facing_fist_light_skin_tone": "1f91c-1f3fb", "fist_right_light_skin_tone": "1f91c-1f3fb", "right-facing_fist_medium_light_skin_tone": "1f91c-1f3fc", "fist_right_medium_light_skin_tone": "1f91c-1f3fc", "right-facing_fist_medium_skin_tone": "1f91c-1f3fd", "fist_right_medium_skin_tone": "1f91c-1f3fd", "right-facing_fist_medium_dark_skin_tone": "1f91c-1f3fe", "fist_right_medium_dark_skin_tone": "1f91c-1f3fe", "right-facing_fist_dark_skin_tone": "1f91c-1f3ff", "fist_right_dark_skin_tone": "1f91c-1f3ff", "crossed_fingers_light_skin_tone": "1f91e-1f3fb", "hand_with_index_and_middle_fingers_crossed_light_skin_tone": "1f91e-1f3fb", "crossed_fingers_medium_light_skin_tone": "1f91e-1f3fc", "hand_with_index_and_middle_fingers_crossed_medium_light_skin_tone": "1f91e-1f3fc", "crossed_fingers_medium_skin_tone": "1f91e-1f3fd", "hand_with_index_and_middle_fingers_crossed_medium_skin_tone": "1f91e-1f3fd", "crossed_fingers_medium_dark_skin_tone": "1f91e-1f3fe", "hand_with_index_and_middle_fingers_crossed_medium_dark_skin_tone": "1f91e-1f3fe", "crossed_fingers_dark_skin_tone": "1f91e-1f3ff", "hand_with_index_and_middle_fingers_crossed_dark_skin_tone": "1f91e-1f3ff", "i_love_you_hand_sign_light_skin_tone": "1f91f-1f3fb", "i_love_you_hand_sign_medium_light_skin_tone": "1f91f-1f3fc", "i_love_you_hand_sign_medium_skin_tone": "1f91f-1f3fd", "i_love_you_hand_sign_medium_dark_skin_tone": "1f91f-1f3fe", "i_love_you_hand_sign_dark_skin_tone": "1f91f-1f3ff", "woman-facepalming_light_skin_tone": "1f926-1f3fb-200d-2640-fe0f", "woman_facepalming_light_skin_tone": "1f926-1f3fb-200d-2640-fe0f", "woman-facepalming_medium_light_skin_tone": "1f926-1f3fc-200d-2640-fe0f", "woman_facepalming_medium_light_skin_tone": "1f926-1f3fc-200d-2640-fe0f", "woman-facepalming_medium_skin_tone": "1f926-1f3fd-200d-2640-fe0f", "woman_facepalming_medium_skin_tone": "1f926-1f3fd-200d-2640-fe0f", "woman-facepalming_medium_dark_skin_tone": "1f926-1f3fe-200d-2640-fe0f", "woman_facepalming_medium_dark_skin_tone": "1f926-1f3fe-200d-2640-fe0f", "woman-facepalming_dark_skin_tone": "1f926-1f3ff-200d-2640-fe0f", "woman_facepalming_dark_skin_tone": "1f926-1f3ff-200d-2640-fe0f", "man-facepalming_light_skin_tone": "1f926-1f3fb-200d-2642-fe0f", "man_facepalming_light_skin_tone": "1f926-1f3fb-200d-2642-fe0f", "man-facepalming_medium_light_skin_tone": "1f926-1f3fc-200d-2642-fe0f", "man_facepalming_medium_light_skin_tone": "1f926-1f3fc-200d-2642-fe0f", "man-facepalming_medium_skin_tone": "1f926-1f3fd-200d-2642-fe0f", "man_facepalming_medium_skin_tone": "1f926-1f3fd-200d-2642-fe0f", "man-facepalming_medium_dark_skin_tone": "1f926-1f3fe-200d-2642-fe0f", "man_facepalming_medium_dark_skin_tone": "1f926-1f3fe-200d-2642-fe0f", "man-facepalming_dark_skin_tone": "1f926-1f3ff-200d-2642-fe0f", "man_facepalming_dark_skin_tone": "1f926-1f3ff-200d-2642-fe0f", "face_palm_light_skin_tone": "1f926-1f3fb", "face_palm_medium_light_skin_tone": "1f926-1f3fc", "face_palm_medium_skin_tone": "1f926-1f3fd", "face_palm_medium_dark_skin_tone": "1f926-1f3fe", "face_palm_dark_skin_tone": "1f926-1f3ff", "pregnant_woman_light_skin_tone": "1f930-1f3fb", "pregnant_woman_medium_light_skin_tone": "1f930-1f3fc", "pregnant_woman_medium_skin_tone": "1f930-1f3fd", "pregnant_woman_medium_dark_skin_tone": "1f930-1f3fe", "pregnant_woman_dark_skin_tone": "1f930-1f3ff", "breast-feeding_light_skin_tone": "1f931-1f3fb", "breast-feeding_medium_light_skin_tone": "1f931-1f3fc", "breast-feeding_medium_skin_tone": "1f931-1f3fd", "breast-feeding_medium_dark_skin_tone": "1f931-1f3fe", "breast-feeding_dark_skin_tone": "1f931-1f3ff", "palms_up_together_light_skin_tone": "1f932-1f3fb", "palms_up_together_medium_light_skin_tone": "1f932-1f3fc", "palms_up_together_medium_skin_tone": "1f932-1f3fd", "palms_up_together_medium_dark_skin_tone": "1f932-1f3fe", "palms_up_together_dark_skin_tone": "1f932-1f3ff", "selfie_light_skin_tone": "1f933-1f3fb", "selfie_medium_light_skin_tone": "1f933-1f3fc", "selfie_medium_skin_tone": "1f933-1f3fd", "selfie_medium_dark_skin_tone": "1f933-1f3fe", "selfie_dark_skin_tone": "1f933-1f3ff", "prince_light_skin_tone": "1f934-1f3fb", "prince_medium_light_skin_tone": "1f934-1f3fc", "prince_medium_skin_tone": "1f934-1f3fd", "prince_medium_dark_skin_tone": "1f934-1f3fe", "prince_dark_skin_tone": "1f934-1f3ff", "woman_in_tuxedo_light_skin_tone": "1f935-1f3fb-200d-2640-fe0f", "woman_in_tuxedo_medium_light_skin_tone": "1f935-1f3fc-200d-2640-fe0f", "woman_in_tuxedo_medium_skin_tone": "1f935-1f3fd-200d-2640-fe0f", "woman_in_tuxedo_medium_dark_skin_tone": "1f935-1f3fe-200d-2640-fe0f", "woman_in_tuxedo_dark_skin_tone": "1f935-1f3ff-200d-2640-fe0f", "man_in_tuxedo_light_skin_tone": "1f935-1f3fb-200d-2642-fe0f", "man_in_tuxedo_medium_light_skin_tone": "1f935-1f3fc-200d-2642-fe0f", "man_in_tuxedo_medium_skin_tone": "1f935-1f3fd-200d-2642-fe0f", "man_in_tuxedo_medium_dark_skin_tone": "1f935-1f3fe-200d-2642-fe0f", "man_in_tuxedo_dark_skin_tone": "1f935-1f3ff-200d-2642-fe0f", "person_in_tuxedo_light_skin_tone": "1f935-1f3fb", "person_in_tuxedo_medium_light_skin_tone": "1f935-1f3fc", "person_in_tuxedo_medium_skin_tone": "1f935-1f3fd", "person_in_tuxedo_medium_dark_skin_tone": "1f935-1f3fe", "person_in_tuxedo_dark_skin_tone": "1f935-1f3ff", "mrs_claus_light_skin_tone": "1f936-1f3fb", "mother_christmas_light_skin_tone": "1f936-1f3fb", "mrs_claus_medium_light_skin_tone": "1f936-1f3fc", "mother_christmas_medium_light_skin_tone": "1f936-1f3fc", "mrs_claus_medium_skin_tone": "1f936-1f3fd", "mother_christmas_medium_skin_tone": "1f936-1f3fd", "mrs_claus_medium_dark_skin_tone": "1f936-1f3fe", "mother_christmas_medium_dark_skin_tone": "1f936-1f3fe", "mrs_claus_dark_skin_tone": "1f936-1f3ff", "mother_christmas_dark_skin_tone": "1f936-1f3ff", "woman-shrugging_light_skin_tone": "1f937-1f3fb-200d-2640-fe0f", "woman_shrugging_light_skin_tone": "1f937-1f3fb-200d-2640-fe0f", "woman-shrugging_medium_light_skin_tone": "1f937-1f3fc-200d-2640-fe0f", "woman_shrugging_medium_light_skin_tone": "1f937-1f3fc-200d-2640-fe0f", "woman-shrugging_medium_skin_tone": "1f937-1f3fd-200d-2640-fe0f", "woman_shrugging_medium_skin_tone": "1f937-1f3fd-200d-2640-fe0f", "woman-shrugging_medium_dark_skin_tone": "1f937-1f3fe-200d-2640-fe0f", "woman_shrugging_medium_dark_skin_tone": "1f937-1f3fe-200d-2640-fe0f", "woman-shrugging_dark_skin_tone": "1f937-1f3ff-200d-2640-fe0f", "woman_shrugging_dark_skin_tone": "1f937-1f3ff-200d-2640-fe0f", "man-shrugging_light_skin_tone": "1f937-1f3fb-200d-2642-fe0f", "man_shrugging_light_skin_tone": "1f937-1f3fb-200d-2642-fe0f", "man-shrugging_medium_light_skin_tone": "1f937-1f3fc-200d-2642-fe0f", "man_shrugging_medium_light_skin_tone": "1f937-1f3fc-200d-2642-fe0f", "man-shrugging_medium_skin_tone": "1f937-1f3fd-200d-2642-fe0f", "man_shrugging_medium_skin_tone": "1f937-1f3fd-200d-2642-fe0f", "man-shrugging_medium_dark_skin_tone": "1f937-1f3fe-200d-2642-fe0f", "man_shrugging_medium_dark_skin_tone": "1f937-1f3fe-200d-2642-fe0f", "man-shrugging_dark_skin_tone": "1f937-1f3ff-200d-2642-fe0f", "man_shrugging_dark_skin_tone": "1f937-1f3ff-200d-2642-fe0f", "shrug_light_skin_tone": "1f937-1f3fb", "shrug_medium_light_skin_tone": "1f937-1f3fc", "shrug_medium_skin_tone": "1f937-1f3fd", "shrug_medium_dark_skin_tone": "1f937-1f3fe", "shrug_dark_skin_tone": "1f937-1f3ff", "woman-cartwheeling_light_skin_tone": "1f938-1f3fb-200d-2640-fe0f", "woman_cartwheeling_light_skin_tone": "1f938-1f3fb-200d-2640-fe0f", "woman-cartwheeling_medium_light_skin_tone": "1f938-1f3fc-200d-2640-fe0f", "woman_cartwheeling_medium_light_skin_tone": "1f938-1f3fc-200d-2640-fe0f", "woman-cartwheeling_medium_skin_tone": "1f938-1f3fd-200d-2640-fe0f", "woman_cartwheeling_medium_skin_tone": "1f938-1f3fd-200d-2640-fe0f", "woman-cartwheeling_medium_dark_skin_tone": "1f938-1f3fe-200d-2640-fe0f", "woman_cartwheeling_medium_dark_skin_tone": "1f938-1f3fe-200d-2640-fe0f", "woman-cartwheeling_dark_skin_tone": "1f938-1f3ff-200d-2640-fe0f", "woman_cartwheeling_dark_skin_tone": "1f938-1f3ff-200d-2640-fe0f", "man-cartwheeling_light_skin_tone": "1f938-1f3fb-200d-2642-fe0f", "man_cartwheeling_light_skin_tone": "1f938-1f3fb-200d-2642-fe0f", "man-cartwheeling_medium_light_skin_tone": "1f938-1f3fc-200d-2642-fe0f", "man_cartwheeling_medium_light_skin_tone": "1f938-1f3fc-200d-2642-fe0f", "man-cartwheeling_medium_skin_tone": "1f938-1f3fd-200d-2642-fe0f", "man_cartwheeling_medium_skin_tone": "1f938-1f3fd-200d-2642-fe0f", "man-cartwheeling_medium_dark_skin_tone": "1f938-1f3fe-200d-2642-fe0f", "man_cartwheeling_medium_dark_skin_tone": "1f938-1f3fe-200d-2642-fe0f", "man-cartwheeling_dark_skin_tone": "1f938-1f3ff-200d-2642-fe0f", "man_cartwheeling_dark_skin_tone": "1f938-1f3ff-200d-2642-fe0f", "person_doing_cartwheel_light_skin_tone": "1f938-1f3fb", "person_doing_cartwheel_medium_light_skin_tone": "1f938-1f3fc", "person_doing_cartwheel_medium_skin_tone": "1f938-1f3fd", "person_doing_cartwheel_medium_dark_skin_tone": "1f938-1f3fe", "person_doing_cartwheel_dark_skin_tone": "1f938-1f3ff", "woman-juggling_light_skin_tone": "1f939-1f3fb-200d-2640-fe0f", "woman_juggling_light_skin_tone": "1f939-1f3fb-200d-2640-fe0f", "woman-juggling_medium_light_skin_tone": "1f939-1f3fc-200d-2640-fe0f", "woman_juggling_medium_light_skin_tone": "1f939-1f3fc-200d-2640-fe0f", "woman-juggling_medium_skin_tone": "1f939-1f3fd-200d-2640-fe0f", "woman_juggling_medium_skin_tone": "1f939-1f3fd-200d-2640-fe0f", "woman-juggling_medium_dark_skin_tone": "1f939-1f3fe-200d-2640-fe0f", "woman_juggling_medium_dark_skin_tone": "1f939-1f3fe-200d-2640-fe0f", "woman-juggling_dark_skin_tone": "1f939-1f3ff-200d-2640-fe0f", "woman_juggling_dark_skin_tone": "1f939-1f3ff-200d-2640-fe0f", "man-juggling_light_skin_tone": "1f939-1f3fb-200d-2642-fe0f", "man_juggling_light_skin_tone": "1f939-1f3fb-200d-2642-fe0f", "man-juggling_medium_light_skin_tone": "1f939-1f3fc-200d-2642-fe0f", "man_juggling_medium_light_skin_tone": "1f939-1f3fc-200d-2642-fe0f", "man-juggling_medium_skin_tone": "1f939-1f3fd-200d-2642-fe0f", "man_juggling_medium_skin_tone": "1f939-1f3fd-200d-2642-fe0f", "man-juggling_medium_dark_skin_tone": "1f939-1f3fe-200d-2642-fe0f", "man_juggling_medium_dark_skin_tone": "1f939-1f3fe-200d-2642-fe0f", "man-juggling_dark_skin_tone": "1f939-1f3ff-200d-2642-fe0f", "man_juggling_dark_skin_tone": "1f939-1f3ff-200d-2642-fe0f", "juggling_light_skin_tone": "1f939-1f3fb", "juggling_medium_light_skin_tone": "1f939-1f3fc", "juggling_medium_skin_tone": "1f939-1f3fd", "juggling_medium_dark_skin_tone": "1f939-1f3fe", "juggling_dark_skin_tone": "1f939-1f3ff", "woman-playing-water-polo_light_skin_tone": "1f93d-1f3fb-200d-2640-fe0f", "woman_playing_water_polo_light_skin_tone": "1f93d-1f3fb-200d-2640-fe0f", "woman-playing-water-polo_medium_light_skin_tone": "1f93d-1f3fc-200d-2640-fe0f", "woman_playing_water_polo_medium_light_skin_tone": "1f93d-1f3fc-200d-2640-fe0f", "woman-playing-water-polo_medium_skin_tone": "1f93d-1f3fd-200d-2640-fe0f", "woman_playing_water_polo_medium_skin_tone": "1f93d-1f3fd-200d-2640-fe0f", "woman-playing-water-polo_medium_dark_skin_tone": "1f93d-1f3fe-200d-2640-fe0f", "woman_playing_water_polo_medium_dark_skin_tone": "1f93d-1f3fe-200d-2640-fe0f", "woman-playing-water-polo_dark_skin_tone": "1f93d-1f3ff-200d-2640-fe0f", "woman_playing_water_polo_dark_skin_tone": "1f93d-1f3ff-200d-2640-fe0f", "man-playing-water-polo_light_skin_tone": "1f93d-1f3fb-200d-2642-fe0f", "man_playing_water_polo_light_skin_tone": "1f93d-1f3fb-200d-2642-fe0f", "man-playing-water-polo_medium_light_skin_tone": "1f93d-1f3fc-200d-2642-fe0f", "man_playing_water_polo_medium_light_skin_tone": "1f93d-1f3fc-200d-2642-fe0f", "man-playing-water-polo_medium_skin_tone": "1f93d-1f3fd-200d-2642-fe0f", "man_playing_water_polo_medium_skin_tone": "1f93d-1f3fd-200d-2642-fe0f", "man-playing-water-polo_medium_dark_skin_tone": "1f93d-1f3fe-200d-2642-fe0f", "man_playing_water_polo_medium_dark_skin_tone": "1f93d-1f3fe-200d-2642-fe0f", "man-playing-water-polo_dark_skin_tone": "1f93d-1f3ff-200d-2642-fe0f", "man_playing_water_polo_dark_skin_tone": "1f93d-1f3ff-200d-2642-fe0f", "water_polo_light_skin_tone": "1f93d-1f3fb", "water_polo_medium_light_skin_tone": "1f93d-1f3fc", "water_polo_medium_skin_tone": "1f93d-1f3fd", "water_polo_medium_dark_skin_tone": "1f93d-1f3fe", "water_polo_dark_skin_tone": "1f93d-1f3ff", "woman-playing-handball_light_skin_tone": "1f93e-1f3fb-200d-2640-fe0f", "woman_playing_handball_light_skin_tone": "1f93e-1f3fb-200d-2640-fe0f", "woman-playing-handball_medium_light_skin_tone": "1f93e-1f3fc-200d-2640-fe0f", "woman_playing_handball_medium_light_skin_tone": "1f93e-1f3fc-200d-2640-fe0f", "woman-playing-handball_medium_skin_tone": "1f93e-1f3fd-200d-2640-fe0f", "woman_playing_handball_medium_skin_tone": "1f93e-1f3fd-200d-2640-fe0f", "woman-playing-handball_medium_dark_skin_tone": "1f93e-1f3fe-200d-2640-fe0f", "woman_playing_handball_medium_dark_skin_tone": "1f93e-1f3fe-200d-2640-fe0f", "woman-playing-handball_dark_skin_tone": "1f93e-1f3ff-200d-2640-fe0f", "woman_playing_handball_dark_skin_tone": "1f93e-1f3ff-200d-2640-fe0f", "man-playing-handball_light_skin_tone": "1f93e-1f3fb-200d-2642-fe0f", "man_playing_handball_light_skin_tone": "1f93e-1f3fb-200d-2642-fe0f", "man-playing-handball_medium_light_skin_tone": "1f93e-1f3fc-200d-2642-fe0f", "man_playing_handball_medium_light_skin_tone": "1f93e-1f3fc-200d-2642-fe0f", "man-playing-handball_medium_skin_tone": "1f93e-1f3fd-200d-2642-fe0f", "man_playing_handball_medium_skin_tone": "1f93e-1f3fd-200d-2642-fe0f", "man-playing-handball_medium_dark_skin_tone": "1f93e-1f3fe-200d-2642-fe0f", "man_playing_handball_medium_dark_skin_tone": "1f93e-1f3fe-200d-2642-fe0f", "man-playing-handball_dark_skin_tone": "1f93e-1f3ff-200d-2642-fe0f", "man_playing_handball_dark_skin_tone": "1f93e-1f3ff-200d-2642-fe0f", "handball_light_skin_tone": "1f93e-1f3fb", "handball_medium_light_skin_tone": "1f93e-1f3fc", "handball_medium_skin_tone": "1f93e-1f3fd", "handball_medium_dark_skin_tone": "1f93e-1f3fe", "handball_dark_skin_tone": "1f93e-1f3ff", "ninja_light_skin_tone": "1f977-1f3fb", "ninja_medium_light_skin_tone": "1f977-1f3fc", "ninja_medium_skin_tone": "1f977-1f3fd", "ninja_medium_dark_skin_tone": "1f977-1f3fe", "ninja_dark_skin_tone": "1f977-1f3ff", "leg_light_skin_tone": "1f9b5-1f3fb", "leg_medium_light_skin_tone": "1f9b5-1f3fc", "leg_medium_skin_tone": "1f9b5-1f3fd", "leg_medium_dark_skin_tone": "1f9b5-1f3fe", "leg_dark_skin_tone": "1f9b5-1f3ff", "foot_light_skin_tone": "1f9b6-1f3fb", "foot_medium_light_skin_tone": "1f9b6-1f3fc", "foot_medium_skin_tone": "1f9b6-1f3fd", "foot_medium_dark_skin_tone": "1f9b6-1f3fe", "foot_dark_skin_tone": "1f9b6-1f3ff", "female_superhero_light_skin_tone": "1f9b8-1f3fb-200d-2640-fe0f", "female_superhero_medium_light_skin_tone": "1f9b8-1f3fc-200d-2640-fe0f", "female_superhero_medium_skin_tone": "1f9b8-1f3fd-200d-2640-fe0f", "female_superhero_medium_dark_skin_tone": "1f9b8-1f3fe-200d-2640-fe0f", "female_superhero_dark_skin_tone": "1f9b8-1f3ff-200d-2640-fe0f", "male_superhero_light_skin_tone": "1f9b8-1f3fb-200d-2642-fe0f", "male_superhero_medium_light_skin_tone": "1f9b8-1f3fc-200d-2642-fe0f", "male_superhero_medium_skin_tone": "1f9b8-1f3fd-200d-2642-fe0f", "male_superhero_medium_dark_skin_tone": "1f9b8-1f3fe-200d-2642-fe0f", "male_superhero_dark_skin_tone": "1f9b8-1f3ff-200d-2642-fe0f", "superhero_light_skin_tone": "1f9b8-1f3fb", "superhero_medium_light_skin_tone": "1f9b8-1f3fc", "superhero_medium_skin_tone": "1f9b8-1f3fd", "superhero_medium_dark_skin_tone": "1f9b8-1f3fe", "superhero_dark_skin_tone": "1f9b8-1f3ff", "female_supervillain_light_skin_tone": "1f9b9-1f3fb-200d-2640-fe0f", "female_supervillain_medium_light_skin_tone": "1f9b9-1f3fc-200d-2640-fe0f", "female_supervillain_medium_skin_tone": "1f9b9-1f3fd-200d-2640-fe0f", "female_supervillain_medium_dark_skin_tone": "1f9b9-1f3fe-200d-2640-fe0f", "female_supervillain_dark_skin_tone": "1f9b9-1f3ff-200d-2640-fe0f", "male_supervillain_light_skin_tone": "1f9b9-1f3fb-200d-2642-fe0f", "male_supervillain_medium_light_skin_tone": "1f9b9-1f3fc-200d-2642-fe0f", "male_supervillain_medium_skin_tone": "1f9b9-1f3fd-200d-2642-fe0f", "male_supervillain_medium_dark_skin_tone": "1f9b9-1f3fe-200d-2642-fe0f", "male_supervillain_dark_skin_tone": "1f9b9-1f3ff-200d-2642-fe0f", "supervillain_light_skin_tone": "1f9b9-1f3fb", "supervillain_medium_light_skin_tone": "1f9b9-1f3fc", "supervillain_medium_skin_tone": "1f9b9-1f3fd", "supervillain_medium_dark_skin_tone": "1f9b9-1f3fe", "supervillain_dark_skin_tone": "1f9b9-1f3ff", "ear_with_hearing_aid_light_skin_tone": "1f9bb-1f3fb", "ear_with_hearing_aid_medium_light_skin_tone": "1f9bb-1f3fc", "ear_with_hearing_aid_medium_skin_tone": "1f9bb-1f3fd", "ear_with_hearing_aid_medium_dark_skin_tone": "1f9bb-1f3fe", "ear_with_hearing_aid_dark_skin_tone": "1f9bb-1f3ff", "woman_standing_light_skin_tone": "1f9cd-1f3fb-200d-2640-fe0f", "woman_standing_medium_light_skin_tone": "1f9cd-1f3fc-200d-2640-fe0f", "woman_standing_medium_skin_tone": "1f9cd-1f3fd-200d-2640-fe0f", "woman_standing_medium_dark_skin_tone": "1f9cd-1f3fe-200d-2640-fe0f", "woman_standing_dark_skin_tone": "1f9cd-1f3ff-200d-2640-fe0f", "man_standing_light_skin_tone": "1f9cd-1f3fb-200d-2642-fe0f", "man_standing_medium_light_skin_tone": "1f9cd-1f3fc-200d-2642-fe0f", "man_standing_medium_skin_tone": "1f9cd-1f3fd-200d-2642-fe0f", "man_standing_medium_dark_skin_tone": "1f9cd-1f3fe-200d-2642-fe0f", "man_standing_dark_skin_tone": "1f9cd-1f3ff-200d-2642-fe0f", "standing_person_light_skin_tone": "1f9cd-1f3fb", "standing_person_medium_light_skin_tone": "1f9cd-1f3fc", "standing_person_medium_skin_tone": "1f9cd-1f3fd", "standing_person_medium_dark_skin_tone": "1f9cd-1f3fe", "standing_person_dark_skin_tone": "1f9cd-1f3ff", "woman_kneeling_light_skin_tone": "1f9ce-1f3fb-200d-2640-fe0f", "woman_kneeling_medium_light_skin_tone": "1f9ce-1f3fc-200d-2640-fe0f", "woman_kneeling_medium_skin_tone": "1f9ce-1f3fd-200d-2640-fe0f", "woman_kneeling_medium_dark_skin_tone": "1f9ce-1f3fe-200d-2640-fe0f", "woman_kneeling_dark_skin_tone": "1f9ce-1f3ff-200d-2640-fe0f", "man_kneeling_light_skin_tone": "1f9ce-1f3fb-200d-2642-fe0f", "man_kneeling_medium_light_skin_tone": "1f9ce-1f3fc-200d-2642-fe0f", "man_kneeling_medium_skin_tone": "1f9ce-1f3fd-200d-2642-fe0f", "man_kneeling_medium_dark_skin_tone": "1f9ce-1f3fe-200d-2642-fe0f", "man_kneeling_dark_skin_tone": "1f9ce-1f3ff-200d-2642-fe0f", "kneeling_person_light_skin_tone": "1f9ce-1f3fb", "kneeling_person_medium_light_skin_tone": "1f9ce-1f3fc", "kneeling_person_medium_skin_tone": "1f9ce-1f3fd", "kneeling_person_medium_dark_skin_tone": "1f9ce-1f3fe", "kneeling_person_dark_skin_tone": "1f9ce-1f3ff", "deaf_woman_light_skin_tone": "1f9cf-1f3fb-200d-2640-fe0f", "deaf_woman_medium_light_skin_tone": "1f9cf-1f3fc-200d-2640-fe0f", "deaf_woman_medium_skin_tone": "1f9cf-1f3fd-200d-2640-fe0f", "deaf_woman_medium_dark_skin_tone": "1f9cf-1f3fe-200d-2640-fe0f", "deaf_woman_dark_skin_tone": "1f9cf-1f3ff-200d-2640-fe0f", "deaf_man_light_skin_tone": "1f9cf-1f3fb-200d-2642-fe0f", "deaf_man_medium_light_skin_tone": "1f9cf-1f3fc-200d-2642-fe0f", "deaf_man_medium_skin_tone": "1f9cf-1f3fd-200d-2642-fe0f", "deaf_man_medium_dark_skin_tone": "1f9cf-1f3fe-200d-2642-fe0f", "deaf_man_dark_skin_tone": "1f9cf-1f3ff-200d-2642-fe0f", "deaf_person_light_skin_tone": "1f9cf-1f3fb", "deaf_person_medium_light_skin_tone": "1f9cf-1f3fc", "deaf_person_medium_skin_tone": "1f9cf-1f3fd", "deaf_person_medium_dark_skin_tone": "1f9cf-1f3fe", "deaf_person_dark_skin_tone": "1f9cf-1f3ff", "farmer_light_skin_tone": "1f9d1-1f3fb-200d-1f33e", "farmer_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f33e", "farmer_medium_skin_tone": "1f9d1-1f3fd-200d-1f33e", "farmer_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f33e", "farmer_dark_skin_tone": "1f9d1-1f3ff-200d-1f33e", "cook_light_skin_tone": "1f9d1-1f3fb-200d-1f373", "cook_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f373", "cook_medium_skin_tone": "1f9d1-1f3fd-200d-1f373", "cook_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f373", "cook_dark_skin_tone": "1f9d1-1f3ff-200d-1f373", "person_feeding_baby_light_skin_tone": "1f9d1-1f3fb-200d-1f37c", "person_feeding_baby_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f37c", "person_feeding_baby_medium_skin_tone": "1f9d1-1f3fd-200d-1f37c", "person_feeding_baby_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f37c", "person_feeding_baby_dark_skin_tone": "1f9d1-1f3ff-200d-1f37c", "mx_claus_light_skin_tone": "1f9d1-1f3fb-200d-1f384", "mx_claus_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f384", "mx_claus_medium_skin_tone": "1f9d1-1f3fd-200d-1f384", "mx_claus_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f384", "mx_claus_dark_skin_tone": "1f9d1-1f3ff-200d-1f384", "student_light_skin_tone": "1f9d1-1f3fb-200d-1f393", "student_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f393", "student_medium_skin_tone": "1f9d1-1f3fd-200d-1f393", "student_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f393", "student_dark_skin_tone": "1f9d1-1f3ff-200d-1f393", "singer_light_skin_tone": "1f9d1-1f3fb-200d-1f3a4", "singer_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f3a4", "singer_medium_skin_tone": "1f9d1-1f3fd-200d-1f3a4", "singer_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f3a4", "singer_dark_skin_tone": "1f9d1-1f3ff-200d-1f3a4", "artist_light_skin_tone": "1f9d1-1f3fb-200d-1f3a8", "artist_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f3a8", "artist_medium_skin_tone": "1f9d1-1f3fd-200d-1f3a8", "artist_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f3a8", "artist_dark_skin_tone": "1f9d1-1f3ff-200d-1f3a8", "teacher_light_skin_tone": "1f9d1-1f3fb-200d-1f3eb", "teacher_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f3eb", "teacher_medium_skin_tone": "1f9d1-1f3fd-200d-1f3eb", "teacher_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f3eb", "teacher_dark_skin_tone": "1f9d1-1f3ff-200d-1f3eb", "factory_worker_light_skin_tone": "1f9d1-1f3fb-200d-1f3ed", "factory_worker_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f3ed", "factory_worker_medium_skin_tone": "1f9d1-1f3fd-200d-1f3ed", "factory_worker_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f3ed", "factory_worker_dark_skin_tone": "1f9d1-1f3ff-200d-1f3ed", "technologist_light_skin_tone": "1f9d1-1f3fb-200d-1f4bb", "technologist_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f4bb", "technologist_medium_skin_tone": "1f9d1-1f3fd-200d-1f4bb", "technologist_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f4bb", "technologist_dark_skin_tone": "1f9d1-1f3ff-200d-1f4bb", "office_worker_light_skin_tone": "1f9d1-1f3fb-200d-1f4bc", "office_worker_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f4bc", "office_worker_medium_skin_tone": "1f9d1-1f3fd-200d-1f4bc", "office_worker_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f4bc", "office_worker_dark_skin_tone": "1f9d1-1f3ff-200d-1f4bc", "mechanic_light_skin_tone": "1f9d1-1f3fb-200d-1f527", "mechanic_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f527", "mechanic_medium_skin_tone": "1f9d1-1f3fd-200d-1f527", "mechanic_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f527", "mechanic_dark_skin_tone": "1f9d1-1f3ff-200d-1f527", "scientist_light_skin_tone": "1f9d1-1f3fb-200d-1f52c", "scientist_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f52c", "scientist_medium_skin_tone": "1f9d1-1f3fd-200d-1f52c", "scientist_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f52c", "scientist_dark_skin_tone": "1f9d1-1f3ff-200d-1f52c", "astronaut_light_skin_tone": "1f9d1-1f3fb-200d-1f680", "astronaut_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f680", "astronaut_medium_skin_tone": "1f9d1-1f3fd-200d-1f680", "astronaut_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f680", "astronaut_dark_skin_tone": "1f9d1-1f3ff-200d-1f680", "firefighter_light_skin_tone": "1f9d1-1f3fb-200d-1f692", "firefighter_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f692", "firefighter_medium_skin_tone": "1f9d1-1f3fd-200d-1f692", "firefighter_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f692", "firefighter_dark_skin_tone": "1f9d1-1f3ff-200d-1f692", "people_holding_hands_light_skin_tone_light_skin_tone": "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb", "people_holding_hands_light_skin_tone_medium_light_skin_tone": "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc", "people_holding_hands_light_skin_tone_medium_skin_tone": "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd", "people_holding_hands_light_skin_tone_medium_dark_skin_tone": "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe", "people_holding_hands_light_skin_tone_dark_skin_tone": "1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff", "people_holding_hands_medium_light_skin_tone_light_skin_tone": "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb", "people_holding_hands_medium_light_skin_tone_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc", "people_holding_hands_medium_light_skin_tone_medium_skin_tone": "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd", "people_holding_hands_medium_light_skin_tone_medium_dark_skin_tone": "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe", "people_holding_hands_medium_light_skin_tone_dark_skin_tone": "1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff", "people_holding_hands_medium_skin_tone_light_skin_tone": "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb", "people_holding_hands_medium_skin_tone_medium_light_skin_tone": "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc", "people_holding_hands_medium_skin_tone_medium_skin_tone": "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd", "people_holding_hands_medium_skin_tone_medium_dark_skin_tone": "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe", "people_holding_hands_medium_skin_tone_dark_skin_tone": "1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff", "people_holding_hands_medium_dark_skin_tone_light_skin_tone": "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb", "people_holding_hands_medium_dark_skin_tone_medium_light_skin_tone": "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc", "people_holding_hands_medium_dark_skin_tone_medium_skin_tone": "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd", "people_holding_hands_medium_dark_skin_tone_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe", "people_holding_hands_medium_dark_skin_tone_dark_skin_tone": "1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff", "people_holding_hands_dark_skin_tone_light_skin_tone": "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb", "people_holding_hands_dark_skin_tone_medium_light_skin_tone": "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc", "people_holding_hands_dark_skin_tone_medium_skin_tone": "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd", "people_holding_hands_dark_skin_tone_medium_dark_skin_tone": "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe", "people_holding_hands_dark_skin_tone_dark_skin_tone": "1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff", "person_with_probing_cane_light_skin_tone": "1f9d1-1f3fb-200d-1f9af", "person_with_probing_cane_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9af", "person_with_probing_cane_medium_skin_tone": "1f9d1-1f3fd-200d-1f9af", "person_with_probing_cane_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9af", "person_with_probing_cane_dark_skin_tone": "1f9d1-1f3ff-200d-1f9af", "red_haired_person_light_skin_tone": "1f9d1-1f3fb-200d-1f9b0", "red_haired_person_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9b0", "red_haired_person_medium_skin_tone": "1f9d1-1f3fd-200d-1f9b0", "red_haired_person_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9b0", "red_haired_person_dark_skin_tone": "1f9d1-1f3ff-200d-1f9b0", "curly_haired_person_light_skin_tone": "1f9d1-1f3fb-200d-1f9b1", "curly_haired_person_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9b1", "curly_haired_person_medium_skin_tone": "1f9d1-1f3fd-200d-1f9b1", "curly_haired_person_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9b1", "curly_haired_person_dark_skin_tone": "1f9d1-1f3ff-200d-1f9b1", "bald_person_light_skin_tone": "1f9d1-1f3fb-200d-1f9b2", "bald_person_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9b2", "bald_person_medium_skin_tone": "1f9d1-1f3fd-200d-1f9b2", "bald_person_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9b2", "bald_person_dark_skin_tone": "1f9d1-1f3ff-200d-1f9b2", "white_haired_person_light_skin_tone": "1f9d1-1f3fb-200d-1f9b3", "white_haired_person_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9b3", "white_haired_person_medium_skin_tone": "1f9d1-1f3fd-200d-1f9b3", "white_haired_person_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9b3", "white_haired_person_dark_skin_tone": "1f9d1-1f3ff-200d-1f9b3", "person_in_motorized_wheelchair_light_skin_tone": "1f9d1-1f3fb-200d-1f9bc", "person_in_motorized_wheelchair_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9bc", "person_in_motorized_wheelchair_medium_skin_tone": "1f9d1-1f3fd-200d-1f9bc", "person_in_motorized_wheelchair_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9bc", "person_in_motorized_wheelchair_dark_skin_tone": "1f9d1-1f3ff-200d-1f9bc", "person_in_manual_wheelchair_light_skin_tone": "1f9d1-1f3fb-200d-1f9bd", "person_in_manual_wheelchair_medium_light_skin_tone": "1f9d1-1f3fc-200d-1f9bd", "person_in_manual_wheelchair_medium_skin_tone": "1f9d1-1f3fd-200d-1f9bd", "person_in_manual_wheelchair_medium_dark_skin_tone": "1f9d1-1f3fe-200d-1f9bd", "person_in_manual_wheelchair_dark_skin_tone": "1f9d1-1f3ff-200d-1f9bd", "health_worker_light_skin_tone": "1f9d1-1f3fb-200d-2695-fe0f", "health_worker_medium_light_skin_tone": "1f9d1-1f3fc-200d-2695-fe0f", "health_worker_medium_skin_tone": "1f9d1-1f3fd-200d-2695-fe0f", "health_worker_medium_dark_skin_tone": "1f9d1-1f3fe-200d-2695-fe0f", "health_worker_dark_skin_tone": "1f9d1-1f3ff-200d-2695-fe0f", "judge_light_skin_tone": "1f9d1-1f3fb-200d-2696-fe0f", "judge_medium_light_skin_tone": "1f9d1-1f3fc-200d-2696-fe0f", "judge_medium_skin_tone": "1f9d1-1f3fd-200d-2696-fe0f", "judge_medium_dark_skin_tone": "1f9d1-1f3fe-200d-2696-fe0f", "judge_dark_skin_tone": "1f9d1-1f3ff-200d-2696-fe0f", "pilot_light_skin_tone": "1f9d1-1f3fb-200d-2708-fe0f", "pilot_medium_light_skin_tone": "1f9d1-1f3fc-200d-2708-fe0f", "pilot_medium_skin_tone": "1f9d1-1f3fd-200d-2708-fe0f", "pilot_medium_dark_skin_tone": "1f9d1-1f3fe-200d-2708-fe0f", "pilot_dark_skin_tone": "1f9d1-1f3ff-200d-2708-fe0f", "adult_light_skin_tone": "1f9d1-1f3fb", "adult_medium_light_skin_tone": "1f9d1-1f3fc", "adult_medium_skin_tone": "1f9d1-1f3fd", "adult_medium_dark_skin_tone": "1f9d1-1f3fe", "adult_dark_skin_tone": "1f9d1-1f3ff", "child_light_skin_tone": "1f9d2-1f3fb", "child_medium_light_skin_tone": "1f9d2-1f3fc", "child_medium_skin_tone": "1f9d2-1f3fd", "child_medium_dark_skin_tone": "1f9d2-1f3fe", "child_dark_skin_tone": "1f9d2-1f3ff", "older_adult_light_skin_tone": "1f9d3-1f3fb", "older_adult_medium_light_skin_tone": "1f9d3-1f3fc", "older_adult_medium_skin_tone": "1f9d3-1f3fd", "older_adult_medium_dark_skin_tone": "1f9d3-1f3fe", "older_adult_dark_skin_tone": "1f9d3-1f3ff", "bearded_person_light_skin_tone": "1f9d4-1f3fb", "bearded_person_medium_light_skin_tone": "1f9d4-1f3fc", "bearded_person_medium_skin_tone": "1f9d4-1f3fd", "bearded_person_medium_dark_skin_tone": "1f9d4-1f3fe", "bearded_person_dark_skin_tone": "1f9d4-1f3ff", "person_with_headscarf_light_skin_tone": "1f9d5-1f3fb", "person_with_headscarf_medium_light_skin_tone": "1f9d5-1f3fc", "person_with_headscarf_medium_skin_tone": "1f9d5-1f3fd", "person_with_headscarf_medium_dark_skin_tone": "1f9d5-1f3fe", "person_with_headscarf_dark_skin_tone": "1f9d5-1f3ff", "woman_in_steamy_room_light_skin_tone": "1f9d6-1f3fb-200d-2640-fe0f", "woman_in_steamy_room_medium_light_skin_tone": "1f9d6-1f3fc-200d-2640-fe0f", "woman_in_steamy_room_medium_skin_tone": "1f9d6-1f3fd-200d-2640-fe0f", "woman_in_steamy_room_medium_dark_skin_tone": "1f9d6-1f3fe-200d-2640-fe0f", "woman_in_steamy_room_dark_skin_tone": "1f9d6-1f3ff-200d-2640-fe0f", "man_in_steamy_room_light_skin_tone": "1f9d6-1f3fb-200d-2642-fe0f", "man_in_steamy_room_medium_light_skin_tone": "1f9d6-1f3fc-200d-2642-fe0f", "man_in_steamy_room_medium_skin_tone": "1f9d6-1f3fd-200d-2642-fe0f", "man_in_steamy_room_medium_dark_skin_tone": "1f9d6-1f3fe-200d-2642-fe0f", "man_in_steamy_room_dark_skin_tone": "1f9d6-1f3ff-200d-2642-fe0f", "person_in_steamy_room_light_skin_tone": "1f9d6-1f3fb", "person_in_steamy_room_medium_light_skin_tone": "1f9d6-1f3fc", "person_in_steamy_room_medium_skin_tone": "1f9d6-1f3fd", "person_in_steamy_room_medium_dark_skin_tone": "1f9d6-1f3fe", "person_in_steamy_room_dark_skin_tone": "1f9d6-1f3ff", "woman_climbing_light_skin_tone": "1f9d7-1f3fb-200d-2640-fe0f", "woman_climbing_medium_light_skin_tone": "1f9d7-1f3fc-200d-2640-fe0f", "woman_climbing_medium_skin_tone": "1f9d7-1f3fd-200d-2640-fe0f", "woman_climbing_medium_dark_skin_tone": "1f9d7-1f3fe-200d-2640-fe0f", "woman_climbing_dark_skin_tone": "1f9d7-1f3ff-200d-2640-fe0f", "man_climbing_light_skin_tone": "1f9d7-1f3fb-200d-2642-fe0f", "man_climbing_medium_light_skin_tone": "1f9d7-1f3fc-200d-2642-fe0f", "man_climbing_medium_skin_tone": "1f9d7-1f3fd-200d-2642-fe0f", "man_climbing_medium_dark_skin_tone": "1f9d7-1f3fe-200d-2642-fe0f", "man_climbing_dark_skin_tone": "1f9d7-1f3ff-200d-2642-fe0f", "person_climbing_light_skin_tone": "1f9d7-1f3fb", "person_climbing_medium_light_skin_tone": "1f9d7-1f3fc", "person_climbing_medium_skin_tone": "1f9d7-1f3fd", "person_climbing_medium_dark_skin_tone": "1f9d7-1f3fe", "person_climbing_dark_skin_tone": "1f9d7-1f3ff", "woman_in_lotus_position_light_skin_tone": "1f9d8-1f3fb-200d-2640-fe0f", "woman_in_lotus_position_medium_light_skin_tone": "1f9d8-1f3fc-200d-2640-fe0f", "woman_in_lotus_position_medium_skin_tone": "1f9d8-1f3fd-200d-2640-fe0f", "woman_in_lotus_position_medium_dark_skin_tone": "1f9d8-1f3fe-200d-2640-fe0f", "woman_in_lotus_position_dark_skin_tone": "1f9d8-1f3ff-200d-2640-fe0f", "man_in_lotus_position_light_skin_tone": "1f9d8-1f3fb-200d-2642-fe0f", "man_in_lotus_position_medium_light_skin_tone": "1f9d8-1f3fc-200d-2642-fe0f", "man_in_lotus_position_medium_skin_tone": "1f9d8-1f3fd-200d-2642-fe0f", "man_in_lotus_position_medium_dark_skin_tone": "1f9d8-1f3fe-200d-2642-fe0f", "man_in_lotus_position_dark_skin_tone": "1f9d8-1f3ff-200d-2642-fe0f", "person_in_lotus_position_light_skin_tone": "1f9d8-1f3fb", "person_in_lotus_position_medium_light_skin_tone": "1f9d8-1f3fc", "person_in_lotus_position_medium_skin_tone": "1f9d8-1f3fd", "person_in_lotus_position_medium_dark_skin_tone": "1f9d8-1f3fe", "person_in_lotus_position_dark_skin_tone": "1f9d8-1f3ff", "female_mage_light_skin_tone": "1f9d9-1f3fb-200d-2640-fe0f", "female_mage_medium_light_skin_tone": "1f9d9-1f3fc-200d-2640-fe0f", "female_mage_medium_skin_tone": "1f9d9-1f3fd-200d-2640-fe0f", "female_mage_medium_dark_skin_tone": "1f9d9-1f3fe-200d-2640-fe0f", "female_mage_dark_skin_tone": "1f9d9-1f3ff-200d-2640-fe0f", "male_mage_light_skin_tone": "1f9d9-1f3fb-200d-2642-fe0f", "male_mage_medium_light_skin_tone": "1f9d9-1f3fc-200d-2642-fe0f", "male_mage_medium_skin_tone": "1f9d9-1f3fd-200d-2642-fe0f", "male_mage_medium_dark_skin_tone": "1f9d9-1f3fe-200d-2642-fe0f", "male_mage_dark_skin_tone": "1f9d9-1f3ff-200d-2642-fe0f", "mage_light_skin_tone": "1f9d9-1f3fb", "mage_medium_light_skin_tone": "1f9d9-1f3fc", "mage_medium_skin_tone": "1f9d9-1f3fd", "mage_medium_dark_skin_tone": "1f9d9-1f3fe", "mage_dark_skin_tone": "1f9d9-1f3ff", "female_fairy_light_skin_tone": "1f9da-1f3fb-200d-2640-fe0f", "female_fairy_medium_light_skin_tone": "1f9da-1f3fc-200d-2640-fe0f", "female_fairy_medium_skin_tone": "1f9da-1f3fd-200d-2640-fe0f", "female_fairy_medium_dark_skin_tone": "1f9da-1f3fe-200d-2640-fe0f", "female_fairy_dark_skin_tone": "1f9da-1f3ff-200d-2640-fe0f", "male_fairy_light_skin_tone": "1f9da-1f3fb-200d-2642-fe0f", "male_fairy_medium_light_skin_tone": "1f9da-1f3fc-200d-2642-fe0f", "male_fairy_medium_skin_tone": "1f9da-1f3fd-200d-2642-fe0f", "male_fairy_medium_dark_skin_tone": "1f9da-1f3fe-200d-2642-fe0f", "male_fairy_dark_skin_tone": "1f9da-1f3ff-200d-2642-fe0f", "fairy_light_skin_tone": "1f9da-1f3fb", "fairy_medium_light_skin_tone": "1f9da-1f3fc", "fairy_medium_skin_tone": "1f9da-1f3fd", "fairy_medium_dark_skin_tone": "1f9da-1f3fe", "fairy_dark_skin_tone": "1f9da-1f3ff", "female_vampire_light_skin_tone": "1f9db-1f3fb-200d-2640-fe0f", "female_vampire_medium_light_skin_tone": "1f9db-1f3fc-200d-2640-fe0f", "female_vampire_medium_skin_tone": "1f9db-1f3fd-200d-2640-fe0f", "female_vampire_medium_dark_skin_tone": "1f9db-1f3fe-200d-2640-fe0f", "female_vampire_dark_skin_tone": "1f9db-1f3ff-200d-2640-fe0f", "male_vampire_light_skin_tone": "1f9db-1f3fb-200d-2642-fe0f", "male_vampire_medium_light_skin_tone": "1f9db-1f3fc-200d-2642-fe0f", "male_vampire_medium_skin_tone": "1f9db-1f3fd-200d-2642-fe0f", "male_vampire_medium_dark_skin_tone": "1f9db-1f3fe-200d-2642-fe0f", "male_vampire_dark_skin_tone": "1f9db-1f3ff-200d-2642-fe0f", "vampire_light_skin_tone": "1f9db-1f3fb", "vampire_medium_light_skin_tone": "1f9db-1f3fc", "vampire_medium_skin_tone": "1f9db-1f3fd", "vampire_medium_dark_skin_tone": "1f9db-1f3fe", "vampire_dark_skin_tone": "1f9db-1f3ff", "mermaid_light_skin_tone": "1f9dc-1f3fb-200d-2640-fe0f", "mermaid_medium_light_skin_tone": "1f9dc-1f3fc-200d-2640-fe0f", "mermaid_medium_skin_tone": "1f9dc-1f3fd-200d-2640-fe0f", "mermaid_medium_dark_skin_tone": "1f9dc-1f3fe-200d-2640-fe0f", "mermaid_dark_skin_tone": "1f9dc-1f3ff-200d-2640-fe0f", "merman_light_skin_tone": "1f9dc-1f3fb-200d-2642-fe0f", "merman_medium_light_skin_tone": "1f9dc-1f3fc-200d-2642-fe0f", "merman_medium_skin_tone": "1f9dc-1f3fd-200d-2642-fe0f", "merman_medium_dark_skin_tone": "1f9dc-1f3fe-200d-2642-fe0f", "merman_dark_skin_tone": "1f9dc-1f3ff-200d-2642-fe0f", "merperson_light_skin_tone": "1f9dc-1f3fb", "merperson_medium_light_skin_tone": "1f9dc-1f3fc", "merperson_medium_skin_tone": "1f9dc-1f3fd", "merperson_medium_dark_skin_tone": "1f9dc-1f3fe", "merperson_dark_skin_tone": "1f9dc-1f3ff", "female_elf_light_skin_tone": "1f9dd-1f3fb-200d-2640-fe0f", "female_elf_medium_light_skin_tone": "1f9dd-1f3fc-200d-2640-fe0f", "female_elf_medium_skin_tone": "1f9dd-1f3fd-200d-2640-fe0f", "female_elf_medium_dark_skin_tone": "1f9dd-1f3fe-200d-2640-fe0f", "female_elf_dark_skin_tone": "1f9dd-1f3ff-200d-2640-fe0f", "male_elf_light_skin_tone": "1f9dd-1f3fb-200d-2642-fe0f", "male_elf_medium_light_skin_tone": "1f9dd-1f3fc-200d-2642-fe0f", "male_elf_medium_skin_tone": "1f9dd-1f3fd-200d-2642-fe0f", "male_elf_medium_dark_skin_tone": "1f9dd-1f3fe-200d-2642-fe0f", "male_elf_dark_skin_tone": "1f9dd-1f3ff-200d-2642-fe0f", "elf_light_skin_tone": "1f9dd-1f3fb", "elf_medium_light_skin_tone": "1f9dd-1f3fc", "elf_medium_skin_tone": "1f9dd-1f3fd", "elf_medium_dark_skin_tone": "1f9dd-1f3fe", "elf_dark_skin_tone": "1f9dd-1f3ff", "point_up_light_skin_tone": "261d-1f3fb", "point_up_medium_light_skin_tone": "261d-1f3fc", "point_up_medium_skin_tone": "261d-1f3fd", "point_up_medium_dark_skin_tone": "261d-1f3fe", "point_up_dark_skin_tone": "261d-1f3ff", "woman-bouncing-ball_light_skin_tone": "26f9-1f3fb-200d-2640-fe0f", "basketball_woman_light_skin_tone": "26f9-1f3fb-200d-2640-fe0f", "woman-bouncing-ball_medium_light_skin_tone": "26f9-1f3fc-200d-2640-fe0f", "basketball_woman_medium_light_skin_tone": "26f9-1f3fc-200d-2640-fe0f", "woman-bouncing-ball_medium_skin_tone": "26f9-1f3fd-200d-2640-fe0f", "basketball_woman_medium_skin_tone": "26f9-1f3fd-200d-2640-fe0f", "woman-bouncing-ball_medium_dark_skin_tone": "26f9-1f3fe-200d-2640-fe0f", "basketball_woman_medium_dark_skin_tone": "26f9-1f3fe-200d-2640-fe0f", "woman-bouncing-ball_dark_skin_tone": "26f9-1f3ff-200d-2640-fe0f", "basketball_woman_dark_skin_tone": "26f9-1f3ff-200d-2640-fe0f", "man-bouncing-ball_light_skin_tone": "26f9-1f3fb-200d-2642-fe0f", "basketball_man_light_skin_tone": "26f9-1f3fb-200d-2642-fe0f", "man-bouncing-ball_medium_light_skin_tone": "26f9-1f3fc-200d-2642-fe0f", "basketball_man_medium_light_skin_tone": "26f9-1f3fc-200d-2642-fe0f", "man-bouncing-ball_medium_skin_tone": "26f9-1f3fd-200d-2642-fe0f", "basketball_man_medium_skin_tone": "26f9-1f3fd-200d-2642-fe0f", "man-bouncing-ball_medium_dark_skin_tone": "26f9-1f3fe-200d-2642-fe0f", "basketball_man_medium_dark_skin_tone": "26f9-1f3fe-200d-2642-fe0f", "man-bouncing-ball_dark_skin_tone": "26f9-1f3ff-200d-2642-fe0f", "basketball_man_dark_skin_tone": "26f9-1f3ff-200d-2642-fe0f", "person_with_ball_light_skin_tone": "26f9-1f3fb", "person_with_ball_medium_light_skin_tone": "26f9-1f3fc", "person_with_ball_medium_skin_tone": "26f9-1f3fd", "person_with_ball_medium_dark_skin_tone": "26f9-1f3fe", "person_with_ball_dark_skin_tone": "26f9-1f3ff", "fist_light_skin_tone": "270a-1f3fb", "fist_raised_light_skin_tone": "270a-1f3fb", "fist_medium_light_skin_tone": "270a-1f3fc", "fist_raised_medium_light_skin_tone": "270a-1f3fc", "fist_medium_skin_tone": "270a-1f3fd", "fist_raised_medium_skin_tone": "270a-1f3fd", "fist_medium_dark_skin_tone": "270a-1f3fe", "fist_raised_medium_dark_skin_tone": "270a-1f3fe", "fist_dark_skin_tone": "270a-1f3ff", "fist_raised_dark_skin_tone": "270a-1f3ff", "hand_light_skin_tone": "270b-1f3fb", "raised_hand_light_skin_tone": "270b-1f3fb", "hand_medium_light_skin_tone": "270b-1f3fc", "raised_hand_medium_light_skin_tone": "270b-1f3fc", "hand_medium_skin_tone": "270b-1f3fd", "raised_hand_medium_skin_tone": "270b-1f3fd", "hand_medium_dark_skin_tone": "270b-1f3fe", "raised_hand_medium_dark_skin_tone": "270b-1f3fe", "hand_dark_skin_tone": "270b-1f3ff", "raised_hand_dark_skin_tone": "270b-1f3ff", "v_light_skin_tone": "270c-1f3fb", "v_medium_light_skin_tone": "270c-1f3fc", "v_medium_skin_tone": "270c-1f3fd", "v_medium_dark_skin_tone": "270c-1f3fe", "v_dark_skin_tone": "270c-1f3ff", "writing_hand_light_skin_tone": "270d-1f3fb", "writing_hand_medium_light_skin_tone": "270d-1f3fc", "writing_hand_medium_skin_tone": "270d-1f3fd", "writing_hand_medium_dark_skin_tone": "270d-1f3fe", "writing_hand_dark_skin_tone": "270d-1f3ff", "mattermost": "mattermost"}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/emoji_search.go b/vendor/github.com/mattermost/mattermost-server/v6/model/emoji_search.go
new file mode 100644
index 00000000..4d947a11
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/emoji_search.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type EmojiSearch struct {
+ Term string `json:"term"`
+ PrefixOnly bool `json:"prefix_only"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/feature_flags.go b/vendor/github.com/mattermost/mattermost-server/v6/model/feature_flags.go
new file mode 100644
index 00000000..a341c254
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/feature_flags.go
@@ -0,0 +1,104 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "reflect"
+ "strconv"
+)
+
+type FeatureFlags struct {
+ // Exists only for unit and manual testing.
+ // When set to a value, will be returned by the ping endpoint.
+ TestFeature string
+ // Exists only for testing bool functionality. Boolean feature flags interpret "on" or "true" as true and
+ // all other values as false.
+ TestBoolFeature bool
+
+ // Toggle on and off scheduled jobs for cloud user limit emails see MM-29999
+ CloudDelinquentEmailJobsEnabled bool
+
+ // Toggle on and off support for Collapsed Threads
+ CollapsedThreads bool
+
+ // Enable the remote cluster service for shared channels.
+ EnableRemoteClusterService bool
+
+ // AppsEnabled toggle the Apps framework functionalities both in server and client side
+ AppsEnabled bool
+
+ // Feature flags to control plugin versions
+ PluginPlaybooks string `plugin_id:"playbooks"`
+ PluginApps string `plugin_id:"com.mattermost.apps"`
+ PluginFocalboard string `plugin_id:"focalboard"`
+
+ // Enable timed dnd support for user status
+ TimedDND bool
+
+ PermalinkPreviews bool
+
+ // Enable the Global Header
+ GlobalHeader bool
+
+ // Enable different team menu button treatments, possible values = ("none", "by_team_name", "inverted_sidebar_bg_color")
+ AddChannelButton string
+}
+
+func (f *FeatureFlags) SetDefaults() {
+ f.TestFeature = "off"
+ f.TestBoolFeature = false
+ f.CloudDelinquentEmailJobsEnabled = false
+ f.CollapsedThreads = true
+ f.EnableRemoteClusterService = false
+ f.AppsEnabled = false
+ f.PluginApps = ""
+ f.PluginFocalboard = ""
+ f.TimedDND = false
+ f.PermalinkPreviews = true
+ f.GlobalHeader = true
+ f.AddChannelButton = "by_team_name"
+}
+
+func (f *FeatureFlags) Plugins() map[string]string {
+ rFFVal := reflect.ValueOf(f).Elem()
+ rFFType := reflect.TypeOf(f).Elem()
+
+ pluginVersions := make(map[string]string)
+ for i := 0; i < rFFVal.NumField(); i++ {
+ rFieldVal := rFFVal.Field(i)
+ rFieldType := rFFType.Field(i)
+
+ pluginId, hasPluginId := rFieldType.Tag.Lookup("plugin_id")
+ if !hasPluginId {
+ continue
+ }
+
+ pluginVersions[pluginId] = rFieldVal.String()
+ }
+
+ return pluginVersions
+}
+
+// ToMap returns the feature flags as a map[string]string
+// Supports boolean and string feature flags.
+func (f *FeatureFlags) ToMap() map[string]string {
+ refStructVal := reflect.ValueOf(*f)
+ refStructType := reflect.TypeOf(*f)
+ ret := make(map[string]string)
+ for i := 0; i < refStructVal.NumField(); i++ {
+ refFieldVal := refStructVal.Field(i)
+ if !refFieldVal.IsValid() {
+ continue
+ }
+ refFieldType := refStructType.Field(i)
+ switch refFieldType.Type.Kind() {
+ case reflect.Bool:
+ ret[refFieldType.Name] = strconv.FormatBool(refFieldVal.Bool())
+ default:
+ ret[refFieldType.Name] = refFieldVal.String()
+ }
+ }
+
+ return ret
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/file.go b/vendor/github.com/mattermost/mattermost-server/v6/model/file.go
new file mode 100644
index 00000000..63d1ad33
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/file.go
@@ -0,0 +1,13 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ MaxImageSize = int64(6048 * 4032) // 24 megapixels, roughly 36MB as a raw image
+)
+
+type FileUploadResponse struct {
+ FileInfos []*FileInfo `json:"file_infos"`
+ ClientIds []string `json:"client_ids"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/file_info.go b/vendor/github.com/mattermost/mattermost-server/v6/model/file_info.go
new file mode 100644
index 00000000..9519ef45
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/file_info.go
@@ -0,0 +1,184 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "image"
+ "image/gif"
+ "io"
+ "mime"
+ "net/http"
+ "path/filepath"
+ "strings"
+)
+
+const (
+ FileinfoSortByCreated = "CreateAt"
+ FileinfoSortBySize = "Size"
+)
+
+// GetFileInfosOptions contains options for getting FileInfos
+type GetFileInfosOptions struct {
+ // UserIds optionally limits the FileInfos to those created by the given users.
+ UserIds []string `json:"user_ids"`
+ // ChannelIds optionally limits the FileInfos to those created in the given channels.
+ ChannelIds []string `json:"channel_ids"`
+ // Since optionally limits FileInfos to those created at or after the given time, specified as Unix time in milliseconds.
+ Since int64 `json:"since"`
+ // IncludeDeleted if set includes deleted FileInfos.
+ IncludeDeleted bool `json:"include_deleted"`
+ // SortBy sorts the FileInfos by this field. The default is to sort by date created.
+ SortBy string `json:"sort_by"`
+ // SortDescending changes the sort direction to descending order when true.
+ SortDescending bool `json:"sort_descending"`
+}
+
+type FileInfo struct {
+ Id string `json:"id"`
+ CreatorId string `json:"user_id"`
+ PostId string `json:"post_id,omitempty"`
+ ChannelId string `db:"-" json:"channel_id"`
+ 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"`
+ MiniPreview *[]byte `json:"mini_preview"` // declared as *[]byte to avoid postgres/mysql differences in deserialization
+ Content string `json:"-"`
+ RemoteId *string `json:"remote_id"`
+}
+
+func (fi *FileInfo) PreSave() {
+ if fi.Id == "" {
+ fi.Id = NewId()
+ }
+
+ if fi.CreateAt == 0 {
+ fi.CreateAt = GetMillis()
+ }
+
+ if fi.UpdateAt < fi.CreateAt {
+ fi.UpdateAt = fi.CreateAt
+ }
+
+ if fi.RemoteId == nil {
+ fi.RemoteId = NewString("")
+ }
+}
+
+func (fi *FileInfo) IsValid() *AppError {
+ if !IsValidId(fi.Id) {
+ return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(fi.CreatorId) && fi.CreatorId != "nouser" {
+ return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
+ }
+
+ if fi.PostId != "" && !IsValidId(fi.PostId) {
+ return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
+ }
+
+ if fi.CreateAt == 0 {
+ return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
+ }
+
+ if fi.UpdateAt == 0 {
+ return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
+ }
+
+ if fi.Path == "" {
+ return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+fi.Id, http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (fi *FileInfo) IsImage() bool {
+ return strings.HasPrefix(fi.MimeType, "image")
+}
+
+func NewInfo(name string) *FileInfo {
+ info := &FileInfo{
+ Name: name,
+ }
+
+ 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
+ }
+
+ return info
+}
+
+func GetInfoForBytes(name string, data io.ReadSeeker, size int) (*FileInfo, *AppError) {
+ info := &FileInfo{
+ Name: name,
+ Size: int64(size),
+ }
+ 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(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
+ data.Seek(0, io.SeekStart)
+ gifConfig, err := gif.DecodeAll(data)
+ if err != nil {
+ // Still return the rest of the info even though it doesn't appear to be an actual gif
+ info.HasPreviewImage = true
+ return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ 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/v6/model/file_info_list.go b/vendor/github.com/mattermost/mattermost-server/v6/model/file_info_list.go
new file mode 100644
index 00000000..219b24f1
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/file_info_list.go
@@ -0,0 +1,111 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "sort"
+)
+
+type FileInfoList struct {
+ Order []string `json:"order"`
+ FileInfos map[string]*FileInfo `json:"file_infos"`
+ NextFileInfoId string `json:"next_file_info_id"`
+ PrevFileInfoId string `json:"prev_file_info_id"`
+}
+
+func NewFileInfoList() *FileInfoList {
+ return &FileInfoList{
+ Order: make([]string, 0),
+ FileInfos: make(map[string]*FileInfo),
+ NextFileInfoId: "",
+ PrevFileInfoId: "",
+ }
+}
+
+func (o *FileInfoList) ToSlice() []*FileInfo {
+ var fileInfos []*FileInfo
+ for _, id := range o.Order {
+ fileInfos = append(fileInfos, o.FileInfos[id])
+ }
+ return fileInfos
+}
+
+func (o *FileInfoList) MakeNonNil() {
+ if o.Order == nil {
+ o.Order = make([]string, 0)
+ }
+
+ if o.FileInfos == nil {
+ o.FileInfos = make(map[string]*FileInfo)
+ }
+}
+
+func (o *FileInfoList) AddOrder(id string) {
+ if o.Order == nil {
+ o.Order = make([]string, 0, 128)
+ }
+
+ o.Order = append(o.Order, id)
+}
+
+func (o *FileInfoList) AddFileInfo(fileInfo *FileInfo) {
+ if o.FileInfos == nil {
+ o.FileInfos = make(map[string]*FileInfo)
+ }
+
+ o.FileInfos[fileInfo.Id] = fileInfo
+}
+
+func (o *FileInfoList) UniqueOrder() {
+ keys := make(map[string]bool)
+ order := []string{}
+ for _, fileInfoId := range o.Order {
+ if _, value := keys[fileInfoId]; !value {
+ keys[fileInfoId] = true
+ order = append(order, fileInfoId)
+ }
+ }
+
+ o.Order = order
+}
+
+func (o *FileInfoList) Extend(other *FileInfoList) {
+ for fileInfoId := range other.FileInfos {
+ o.AddFileInfo(other.FileInfos[fileInfoId])
+ }
+
+ for _, fileInfoId := range other.Order {
+ o.AddOrder(fileInfoId)
+ }
+
+ o.UniqueOrder()
+}
+
+func (o *FileInfoList) SortByCreateAt() {
+ sort.Slice(o.Order, func(i, j int) bool {
+ return o.FileInfos[o.Order[i]].CreateAt > o.FileInfos[o.Order[j]].CreateAt
+ })
+}
+
+func (o *FileInfoList) Etag() string {
+ id := "0"
+ var t int64 = 0
+
+ for _, v := range o.FileInfos {
+ 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)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/file_info_search_results.go b/vendor/github.com/mattermost/mattermost-server/v6/model/file_info_search_results.go
new file mode 100644
index 00000000..fddbffd4
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/file_info_search_results.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type FileInfoSearchMatches map[string][]string
+
+type FileInfoSearchResults struct {
+ *FileInfoList
+ Matches FileInfoSearchMatches `json:"matches"`
+}
+
+func MakeFileInfoSearchResults(fileInfos *FileInfoList, matches FileInfoSearchMatches) *FileInfoSearchResults {
+ return &FileInfoSearchResults{
+ fileInfos,
+ matches,
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/gitlab.go b/vendor/github.com/mattermost/mattermost-server/v6/model/gitlab.go
new file mode 100644
index 00000000..c6233f13
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/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 (
+ UserAuthServiceGitlab = "gitlab"
+)
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/group.go b/vendor/github.com/mattermost/mattermost-server/v6/model/group.go
new file mode 100644
index 00000000..566b2361
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/group.go
@@ -0,0 +1,190 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "regexp"
+)
+
+const (
+ GroupSourceLdap GroupSource = "ldap"
+
+ GroupNameMaxLength = 64
+ GroupSourceMaxLength = 64
+ GroupDisplayNameMaxLength = 128
+ GroupDescriptionMaxLength = 1024
+ GroupRemoteIDMaxLength = 48
+)
+
+type GroupSource string
+
+var allGroupSources = []GroupSource{
+ GroupSourceLdap,
+}
+
+var groupSourcesRequiringRemoteID = []GroupSource{
+ GroupSourceLdap,
+}
+
+type Group struct {
+ Id string `json:"id"`
+ Name *string `json:"name,omitempty"`
+ DisplayName string `json:"display_name"`
+ Description string `json:"description"`
+ Source GroupSource `json:"source"`
+ RemoteId string `json:"remote_id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ HasSyncables bool `db:"-" json:"has_syncables"`
+ MemberCount *int `db:"-" json:"member_count,omitempty"`
+ AllowReference bool `json:"allow_reference"`
+}
+
+type GroupWithSchemeAdmin struct {
+ Group
+ SchemeAdmin *bool `db:"SyncableSchemeAdmin" json:"scheme_admin,omitempty"`
+}
+
+type GroupsAssociatedToChannelWithSchemeAdmin struct {
+ ChannelId string `json:"channel_id"`
+ Group
+ SchemeAdmin *bool `db:"SyncableSchemeAdmin" json:"scheme_admin,omitempty"`
+}
+type GroupsAssociatedToChannel struct {
+ ChannelId string `json:"channel_id"`
+ Groups []*GroupWithSchemeAdmin `json:"groups"`
+}
+
+type GroupPatch struct {
+ Name *string `json:"name"`
+ DisplayName *string `json:"display_name"`
+ Description *string `json:"description"`
+ AllowReference *bool `json:"allow_reference"`
+}
+
+type LdapGroupSearchOpts struct {
+ Q string
+ IsLinked *bool
+ IsConfigured *bool
+}
+
+type GroupSearchOpts struct {
+ Q string
+ NotAssociatedToTeam string
+ NotAssociatedToChannel string
+ IncludeMemberCount bool
+ FilterAllowReference bool
+ PageOpts *PageOpts
+ Since int64
+
+ // FilterParentTeamPermitted filters the groups to the intersect of the
+ // set associated to the parent team and those returned by the query.
+ // If the parent team is not group-constrained or if NotAssociatedToChannel
+ // is not set then this option is ignored.
+ FilterParentTeamPermitted bool
+}
+
+type PageOpts struct {
+ Page int
+ PerPage int
+}
+
+type GroupStats struct {
+ GroupID string `json:"group_id"`
+ TotalMemberCount int64 `json:"total_member_count"`
+}
+
+func (group *Group) Patch(patch *GroupPatch) {
+ if patch.Name != nil {
+ group.Name = patch.Name
+ }
+ if patch.DisplayName != nil {
+ group.DisplayName = *patch.DisplayName
+ }
+ if patch.Description != nil {
+ group.Description = *patch.Description
+ }
+ if patch.AllowReference != nil {
+ group.AllowReference = *patch.AllowReference
+ }
+}
+
+func (group *Group) IsValidForCreate() *AppError {
+ err := group.IsValidName()
+ if err != nil {
+ return err
+ }
+
+ if l := len(group.DisplayName); l == 0 || l > GroupDisplayNameMaxLength {
+ return NewAppError("Group.IsValidForCreate", "model.group.display_name.app_error", map[string]interface{}{"GroupDisplayNameMaxLength": GroupDisplayNameMaxLength}, "", http.StatusBadRequest)
+ }
+
+ if len(group.Description) > GroupDescriptionMaxLength {
+ return NewAppError("Group.IsValidForCreate", "model.group.description.app_error", map[string]interface{}{"GroupDescriptionMaxLength": GroupDescriptionMaxLength}, "", http.StatusBadRequest)
+ }
+
+ isValidSource := false
+ for _, groupSource := range allGroupSources {
+ if group.Source == groupSource {
+ isValidSource = true
+ break
+ }
+ }
+ if !isValidSource {
+ return NewAppError("Group.IsValidForCreate", "model.group.source.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if len(group.RemoteId) > GroupRemoteIDMaxLength || (group.RemoteId == "" && group.requiresRemoteId()) {
+ return NewAppError("Group.IsValidForCreate", "model.group.remote_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (group *Group) requiresRemoteId() bool {
+ for _, groupSource := range groupSourcesRequiringRemoteID {
+ if groupSource == group.Source {
+ return true
+ }
+ }
+ return false
+}
+
+func (group *Group) IsValidForUpdate() *AppError {
+ if !IsValidId(group.Id) {
+ return NewAppError("Group.IsValidForUpdate", "app.group.id.app_error", nil, "", http.StatusBadRequest)
+ }
+ if group.CreateAt == 0 {
+ return NewAppError("Group.IsValidForUpdate", "model.group.create_at.app_error", nil, "", http.StatusBadRequest)
+ }
+ if group.UpdateAt == 0 {
+ return NewAppError("Group.IsValidForUpdate", "model.group.update_at.app_error", nil, "", http.StatusBadRequest)
+ }
+ if err := group.IsValidForCreate(); err != nil {
+ return err
+ }
+ return nil
+}
+
+var validGroupnameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
+
+func (group *Group) IsValidName() *AppError {
+
+ if group.Name == nil {
+ if group.AllowReference {
+ return NewAppError("Group.IsValidName", "model.group.name.app_error", map[string]interface{}{"GroupNameMaxLength": GroupNameMaxLength}, "", http.StatusBadRequest)
+ }
+ } else {
+ if l := len(*group.Name); l == 0 || l > GroupNameMaxLength {
+ return NewAppError("Group.IsValidName", "model.group.name.invalid_length.app_error", map[string]interface{}{"GroupNameMaxLength": GroupNameMaxLength}, "", http.StatusBadRequest)
+ }
+
+ if !validGroupnameChars.MatchString(*group.Name) {
+ return NewAppError("Group.IsValidName", "model.group.name.invalid_chars.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/group_member.go b/vendor/github.com/mattermost/mattermost-server/v6/model/group_member.go
new file mode 100644
index 00000000..d18d7849
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/group_member.go
@@ -0,0 +1,23 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import "net/http"
+
+type GroupMember struct {
+ GroupId string `json:"group_id"`
+ UserId string `json:"user_id"`
+ CreateAt int64 `json:"create_at"`
+ DeleteAt int64 `json:"delete_at"`
+}
+
+func (gm *GroupMember) IsValid() *AppError {
+ if !IsValidId(gm.GroupId) {
+ return NewAppError("GroupMember.IsValid", "model.group_member.group_id.app_error", nil, "", http.StatusBadRequest)
+ }
+ if !IsValidId(gm.UserId) {
+ return NewAppError("GroupMember.IsValid", "model.group_member.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/group_syncable.go b/vendor/github.com/mattermost/mattermost-server/v6/model/group_syncable.go
new file mode 100644
index 00000000..1f1bf60f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/group_syncable.go
@@ -0,0 +1,175 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+)
+
+type GroupSyncableType string
+
+const (
+ GroupSyncableTypeTeam GroupSyncableType = "Team"
+ GroupSyncableTypeChannel GroupSyncableType = "Channel"
+)
+
+func (gst GroupSyncableType) String() string {
+ return string(gst)
+}
+
+type GroupSyncable struct {
+ GroupId string `json:"group_id"`
+
+ // SyncableId represents the Id of the model that is being synced with the group, for example a ChannelId or
+ // TeamId.
+ SyncableId string `db:"-" json:"-"`
+
+ AutoAdd bool `json:"auto_add"`
+ SchemeAdmin bool `json:"scheme_admin"`
+ CreateAt int64 `json:"create_at"`
+ DeleteAt int64 `json:"delete_at"`
+ UpdateAt int64 `json:"update_at"`
+ Type GroupSyncableType `db:"-" json:"-"`
+
+ // Values joined in from the associated team and/or channel
+ ChannelDisplayName string `db:"-" json:"-"`
+ TeamDisplayName string `db:"-" json:"-"`
+ TeamType string `db:"-" json:"-"`
+ ChannelType string `db:"-" json:"-"`
+ TeamID string `db:"-" json:"-"`
+}
+
+func (syncable *GroupSyncable) IsValid() *AppError {
+ if !IsValidId(syncable.GroupId) {
+ return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.group_id.app_error", nil, "", http.StatusBadRequest)
+ }
+ if !IsValidId(syncable.SyncableId) {
+ return NewAppError("GroupSyncable.SyncableIsValid", "model.group_syncable.syncable_id.app_error", nil, "", http.StatusBadRequest)
+ }
+ return nil
+}
+
+func (syncable *GroupSyncable) UnmarshalJSON(b []byte) error {
+ var kvp map[string]interface{}
+ err := json.Unmarshal(b, &kvp)
+ if err != nil {
+ return err
+ }
+ var channelId string
+ var teamId string
+ for key, value := range kvp {
+ switch key {
+ case "team_id":
+ teamId = value.(string)
+ case "channel_id":
+ channelId = value.(string)
+ case "group_id":
+ syncable.GroupId = value.(string)
+ case "auto_add":
+ syncable.AutoAdd = value.(bool)
+ default:
+ }
+ }
+ if channelId != "" {
+ syncable.TeamID = teamId
+ syncable.SyncableId = channelId
+ syncable.Type = GroupSyncableTypeChannel
+ } else {
+ syncable.SyncableId = teamId
+ syncable.Type = GroupSyncableTypeTeam
+ }
+ return nil
+}
+
+func (syncable *GroupSyncable) MarshalJSON() ([]byte, error) {
+ type Alias GroupSyncable
+ switch syncable.Type {
+ case GroupSyncableTypeTeam:
+ return json.Marshal(&struct {
+ TeamID string `json:"team_id"`
+ TeamDisplayName string `json:"team_display_name,omitempty"`
+ TeamType string `json:"team_type,omitempty"`
+ Type GroupSyncableType `json:"type,omitempty"`
+ *Alias
+ }{
+ TeamDisplayName: syncable.TeamDisplayName,
+ TeamType: syncable.TeamType,
+ TeamID: syncable.SyncableId,
+ Type: syncable.Type,
+ Alias: (*Alias)(syncable),
+ })
+ case GroupSyncableTypeChannel:
+ return json.Marshal(&struct {
+ ChannelID string `json:"channel_id"`
+ ChannelDisplayName string `json:"channel_display_name,omitempty"`
+ ChannelType string `json:"channel_type,omitempty"`
+ Type GroupSyncableType `json:"type,omitempty"`
+
+ TeamID string `json:"team_id,omitempty"`
+ TeamDisplayName string `json:"team_display_name,omitempty"`
+ TeamType string `json:"team_type,omitempty"`
+
+ *Alias
+ }{
+ ChannelID: syncable.SyncableId,
+ ChannelDisplayName: syncable.ChannelDisplayName,
+ ChannelType: syncable.ChannelType,
+ Type: syncable.Type,
+
+ TeamID: syncable.TeamID,
+ TeamDisplayName: syncable.TeamDisplayName,
+ TeamType: syncable.TeamType,
+
+ Alias: (*Alias)(syncable),
+ })
+ default:
+ return nil, &json.MarshalerError{
+ Err: fmt.Errorf("unknown syncable type: %s", syncable.Type),
+ }
+ }
+}
+
+type GroupSyncablePatch struct {
+ AutoAdd *bool `json:"auto_add"`
+ SchemeAdmin *bool `json:"scheme_admin"`
+}
+
+func (syncable *GroupSyncable) Patch(patch *GroupSyncablePatch) {
+ if patch.AutoAdd != nil {
+ syncable.AutoAdd = *patch.AutoAdd
+ }
+ if patch.SchemeAdmin != nil {
+ syncable.SchemeAdmin = *patch.SchemeAdmin
+ }
+}
+
+type UserTeamIDPair struct {
+ UserID string
+ TeamID string
+}
+
+type UserChannelIDPair struct {
+ UserID string
+ ChannelID string
+}
+
+func NewGroupTeam(groupID, teamID string, autoAdd bool) *GroupSyncable {
+ return &GroupSyncable{
+ GroupId: groupID,
+ SyncableId: teamID,
+ Type: GroupSyncableTypeTeam,
+ AutoAdd: autoAdd,
+ }
+}
+
+func NewGroupChannel(groupID, channelID string, autoAdd bool) *GroupSyncable {
+ return &GroupSyncable{
+ GroupId: groupID,
+ SyncableId: channelID,
+ Type: GroupSyncableTypeChannel,
+ AutoAdd: autoAdd,
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/guest_invite.go b/vendor/github.com/mattermost/mattermost-server/v6/model/guest_invite.go
new file mode 100644
index 00000000..5103ccd1
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/guest_invite.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+)
+
+type GuestsInvite struct {
+ Emails []string `json:"emails"`
+ Channels []string `json:"channels"`
+ Message string `json:"message"`
+}
+
+// IsValid validates the user and returns an error if it isn't configured
+// correctly.
+func (i *GuestsInvite) IsValid() *AppError {
+ if len(i.Emails) == 0 {
+ return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.emails.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ for _, email := range i.Emails {
+ if len(email) > UserEmailMaxLength || email == "" || !IsValidEmail(email) {
+ return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.email.app_error", nil, "email="+email, http.StatusBadRequest)
+ }
+ }
+
+ if len(i.Channels) == 0 {
+ return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.channels.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ for _, channel := range i.Channels {
+ if len(channel) != 26 {
+ return NewAppError("GuestsInvite.IsValid", "model.guest.is_valid.channel.app_error", nil, "channel="+channel, http.StatusBadRequest)
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/incoming_webhook.go b/vendor/github.com/mattermost/mattermost-server/v6/model/incoming_webhook.go
new file mode 100644
index 00000000..ce7828e4
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/incoming_webhook.go
@@ -0,0 +1,184 @@
+// 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 (
+ DefaultWebhookUsername = "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"`
+ IconEmoji string `json:"icon_emoji"`
+}
+
+func (o *IncomingWebhook) IsValid() *AppError {
+ if !IsValidId(o.Id) {
+ 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 !IsValidId(o.UserId) {
+ return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.ChannelId) {
+ return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.TeamId) {
+ 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
+ }
+ 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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/initial_load.go b/vendor/github.com/mattermost/mattermost-server/v6/model/initial_load.go
new file mode 100644
index 00000000..5ecddda2
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/initial_load.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/integration_action.go b/vendor/github.com/mattermost/mattermost-server/v6/model/integration_action.go
new file mode 100644
index 00000000..c61cc6d4
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/integration_action.go
@@ -0,0 +1,470 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/ecdsa"
+ "crypto/rand"
+ "encoding/asn1"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "math/big"
+ "net/http"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+const (
+ PostActionTypeButton = "button"
+ PostActionTypeSelect = "select"
+ InteractiveDialogTriggerTimeoutMilliseconds = 3000
+)
+
+var PostActionRetainPropKeys = []string{"from_webhook", "override_username", "override_icon_url"}
+
+type DoPostActionRequest struct {
+ SelectedOption string `json:"selected_option,omitempty"`
+ Cookie string `json:"cookie,omitempty"`
+}
+
+type PostAction struct {
+ // A unique Action ID. If not set, generated automatically.
+ Id string `json:"id,omitempty"`
+
+ // The type of the interactive element. Currently supported are
+ // "select" and "button".
+ Type string `json:"type,omitempty"`
+
+ // The text on the button, or in the select placeholder.
+ Name string `json:"name,omitempty"`
+
+ // If the action is disabled.
+ Disabled bool `json:"disabled,omitempty"`
+
+ // Style defines a text and border style.
+ // Supported values are "default", "primary", "success", "good", "warning", "danger"
+ // and any hex color.
+ Style string `json:"style,omitempty"`
+
+ // DataSource indicates the data source for the select action. If left
+ // empty, the select is populated from Options. Other supported values
+ // are "users" and "channels".
+ DataSource string `json:"data_source,omitempty"`
+
+ // Options contains the values listed in a select dropdown on the post.
+ Options []*PostActionOptions `json:"options,omitempty"`
+
+ // DefaultOption contains the option, if any, that will appear as the
+ // default selection in a select box. It has no effect when used with
+ // other types of actions.
+ DefaultOption string `json:"default_option,omitempty"`
+
+ // Defines the interaction with the backend upon a user action.
+ // Integration contains Context, which is private plugin data;
+ // Integrations are stripped from Posts when they are sent to the
+ // client, or are encrypted in a Cookie.
+ Integration *PostActionIntegration `json:"integration,omitempty"`
+ Cookie string `json:"cookie,omitempty" db:"-"`
+}
+
+func (p *PostAction) Equals(input *PostAction) bool {
+ if p.Id != input.Id {
+ return false
+ }
+
+ if p.Type != input.Type {
+ return false
+ }
+
+ if p.Name != input.Name {
+ return false
+ }
+
+ if p.DataSource != input.DataSource {
+ return false
+ }
+
+ if p.DefaultOption != input.DefaultOption {
+ return false
+ }
+
+ if p.Cookie != input.Cookie {
+ return false
+ }
+
+ // Compare PostActionOptions
+ if len(p.Options) != len(input.Options) {
+ return false
+ }
+
+ for k := range p.Options {
+ if p.Options[k].Text != input.Options[k].Text {
+ return false
+ }
+
+ if p.Options[k].Value != input.Options[k].Value {
+ return false
+ }
+ }
+
+ // Compare PostActionIntegration
+ if p.Integration.URL != input.Integration.URL {
+ return false
+ }
+
+ if len(p.Integration.Context) != len(input.Integration.Context) {
+ return false
+ }
+
+ for key, value := range p.Integration.Context {
+ inputValue, ok := input.Integration.Context[key]
+ if !ok {
+ return false
+ }
+
+ switch inputValue.(type) {
+ case string, bool, int, float64:
+ if value != inputValue {
+ return false
+ }
+ default:
+ if !reflect.DeepEqual(value, inputValue) {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+// PostActionCookie is set by the server, serialized and encrypted into
+// PostAction.Cookie. The clients should hold on to it, and include it with
+// subsequent DoPostAction requests. This allows the server to access the
+// action metadata even when it's not available in the database, for ephemeral
+// posts.
+type PostActionCookie struct {
+ Type string `json:"type,omitempty"`
+ PostId string `json:"post_id,omitempty"`
+ RootPostId string `json:"root_post_id,omitempty"`
+ ChannelId string `json:"channel_id,omitempty"`
+ DataSource string `json:"data_source,omitempty"`
+ Integration *PostActionIntegration `json:"integration,omitempty"`
+ RetainProps map[string]interface{} `json:"retain_props,omitempty"`
+ RemoveProps []string `json:"remove_props,omitempty"`
+}
+
+type PostActionOptions struct {
+ Text string `json:"text"`
+ Value string `json:"value"`
+}
+
+type PostActionIntegration struct {
+ URL string `json:"url,omitempty"`
+ Context map[string]interface{} `json:"context,omitempty"`
+}
+
+type PostActionIntegrationRequest struct {
+ UserId string `json:"user_id"`
+ UserName string `json:"user_name"`
+ ChannelId string `json:"channel_id"`
+ ChannelName string `json:"channel_name"`
+ TeamId string `json:"team_id"`
+ TeamName string `json:"team_domain"`
+ PostId string `json:"post_id"`
+ TriggerId string `json:"trigger_id"`
+ Type string `json:"type"`
+ DataSource string `json:"data_source"`
+ Context map[string]interface{} `json:"context,omitempty"`
+}
+
+type PostActionIntegrationResponse struct {
+ Update *Post `json:"update"`
+ EphemeralText string `json:"ephemeral_text"`
+ SkipSlackParsing bool `json:"skip_slack_parsing"` // Set to `true` to skip the Slack-compatibility handling of Text.
+}
+
+type PostActionAPIResponse struct {
+ Status string `json:"status"` // needed to maintain backwards compatibility
+ TriggerId string `json:"trigger_id"`
+}
+
+type Dialog struct {
+ CallbackId string `json:"callback_id"`
+ Title string `json:"title"`
+ IntroductionText string `json:"introduction_text"`
+ IconURL string `json:"icon_url"`
+ Elements []DialogElement `json:"elements"`
+ SubmitLabel string `json:"submit_label"`
+ NotifyOnCancel bool `json:"notify_on_cancel"`
+ State string `json:"state"`
+}
+
+type DialogElement struct {
+ DisplayName string `json:"display_name"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ SubType string `json:"subtype"`
+ Default string `json:"default"`
+ Placeholder string `json:"placeholder"`
+ HelpText string `json:"help_text"`
+ Optional bool `json:"optional"`
+ MinLength int `json:"min_length"`
+ MaxLength int `json:"max_length"`
+ DataSource string `json:"data_source"`
+ Options []*PostActionOptions `json:"options"`
+}
+
+type OpenDialogRequest struct {
+ TriggerId string `json:"trigger_id"`
+ URL string `json:"url"`
+ Dialog Dialog `json:"dialog"`
+}
+
+type SubmitDialogRequest struct {
+ Type string `json:"type"`
+ URL string `json:"url,omitempty"`
+ CallbackId string `json:"callback_id"`
+ State string `json:"state"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ TeamId string `json:"team_id"`
+ Submission map[string]interface{} `json:"submission"`
+ Cancelled bool `json:"cancelled"`
+}
+
+type SubmitDialogResponse struct {
+ Error string `json:"error,omitempty"`
+ Errors map[string]string `json:"errors,omitempty"`
+}
+
+func GenerateTriggerId(userId string, s crypto.Signer) (string, string, *AppError) {
+ clientTriggerId := NewId()
+ triggerData := strings.Join([]string{clientTriggerId, userId, strconv.FormatInt(GetMillis(), 10)}, ":") + ":"
+
+ h := crypto.SHA256
+ sum := h.New()
+ sum.Write([]byte(triggerData))
+ signature, err := s.Sign(rand.Reader, sum.Sum(nil), h)
+ if err != nil {
+ return "", "", NewAppError("GenerateTriggerId", "interactive_message.generate_trigger_id.signing_failed", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ base64Sig := base64.StdEncoding.EncodeToString(signature)
+
+ triggerId := base64.StdEncoding.EncodeToString([]byte(triggerData + base64Sig))
+ return clientTriggerId, triggerId, nil
+}
+
+func (r *PostActionIntegrationRequest) GenerateTriggerId(s crypto.Signer) (string, string, *AppError) {
+ clientTriggerId, triggerId, err := GenerateTriggerId(r.UserId, s)
+ if err != nil {
+ return "", "", err
+ }
+
+ r.TriggerId = triggerId
+ return clientTriggerId, triggerId, nil
+}
+
+func DecodeAndVerifyTriggerId(triggerId string, s *ecdsa.PrivateKey) (string, string, *AppError) {
+ triggerIdBytes, err := base64.StdEncoding.DecodeString(triggerId)
+ if err != nil {
+ return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ split := strings.Split(string(triggerIdBytes), ":")
+ if len(split) != 4 {
+ return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.missing_data", nil, "", http.StatusBadRequest)
+ }
+
+ clientTriggerId := split[0]
+ userId := split[1]
+ timestampStr := split[2]
+ timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
+
+ now := GetMillis()
+ if now-timestamp > InteractiveDialogTriggerTimeoutMilliseconds {
+ return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.expired", map[string]interface{}{"Seconds": InteractiveDialogTriggerTimeoutMilliseconds / 1000}, "", http.StatusBadRequest)
+ }
+
+ signature, err := base64.StdEncoding.DecodeString(split[3])
+ if err != nil {
+ return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.base64_decode_failed_signature", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ var esig struct {
+ R, S *big.Int
+ }
+
+ if _, err := asn1.Unmarshal(signature, &esig); err != nil {
+ return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.signature_decode_failed", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ triggerData := strings.Join([]string{clientTriggerId, userId, timestampStr}, ":") + ":"
+
+ h := crypto.SHA256
+ sum := h.New()
+ sum.Write([]byte(triggerData))
+
+ if !ecdsa.Verify(&s.PublicKey, sum.Sum(nil), esig.R, esig.S) {
+ return "", "", NewAppError("DecodeAndVerifyTriggerId", "interactive_message.decode_trigger_id.verify_signature_failed", nil, "", http.StatusBadRequest)
+ }
+
+ return clientTriggerId, userId, nil
+}
+
+func (r *OpenDialogRequest) DecodeAndVerifyTriggerId(s *ecdsa.PrivateKey) (string, string, *AppError) {
+ return DecodeAndVerifyTriggerId(r.TriggerId, s)
+}
+
+func (o *Post) StripActionIntegrations() {
+ attachments := o.Attachments()
+ if o.GetProp("attachments") != nil {
+ o.AddProp("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 != nil && action.Id == id {
+ return action
+ }
+ }
+ }
+ return nil
+}
+
+func (o *Post) GenerateActionIds() {
+ if o.GetProp("attachments") != nil {
+ o.AddProp("attachments", o.Attachments())
+ }
+ if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
+ for _, attachment := range attachments {
+ for _, action := range attachment.Actions {
+ if action != nil && action.Id == "" {
+ action.Id = NewId()
+ }
+ }
+ }
+ }
+}
+
+func AddPostActionCookies(o *Post, secret []byte) *Post {
+ p := o.Clone()
+
+ // retainedProps carry over their value from the old post, including no value
+ retainProps := map[string]interface{}{}
+ removeProps := []string{}
+ for _, key := range PostActionRetainPropKeys {
+ value, ok := p.GetProps()[key]
+ if ok {
+ retainProps[key] = value
+ } else {
+ removeProps = append(removeProps, key)
+ }
+ }
+
+ attachments := p.Attachments()
+ for _, attachment := range attachments {
+ for _, action := range attachment.Actions {
+ c := &PostActionCookie{
+ Type: action.Type,
+ ChannelId: p.ChannelId,
+ DataSource: action.DataSource,
+ Integration: action.Integration,
+ RetainProps: retainProps,
+ RemoveProps: removeProps,
+ }
+
+ c.PostId = p.Id
+ if p.RootId == "" {
+ c.RootPostId = p.Id
+ } else {
+ c.RootPostId = p.RootId
+ }
+
+ b, _ := json.Marshal(c)
+ action.Cookie, _ = encryptPostActionCookie(string(b), secret)
+ }
+ }
+
+ return p
+}
+
+func encryptPostActionCookie(plain string, secret []byte) (string, error) {
+ if len(secret) == 0 {
+ return plain, nil
+ }
+
+ block, err := aes.NewCipher(secret)
+ if err != nil {
+ return "", err
+ }
+
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return "", err
+ }
+
+ nonce := make([]byte, aesgcm.NonceSize())
+ _, err = io.ReadFull(rand.Reader, nonce)
+ if err != nil {
+ return "", err
+ }
+
+ sealed := aesgcm.Seal(nil, nonce, []byte(plain), nil)
+
+ combined := append(nonce, sealed...)
+ encoded := make([]byte, base64.StdEncoding.EncodedLen(len(combined)))
+ base64.StdEncoding.Encode(encoded, combined)
+
+ return string(encoded), nil
+}
+
+func DecryptPostActionCookie(encoded string, secret []byte) (string, error) {
+ if len(secret) == 0 {
+ return encoded, nil
+ }
+
+ block, err := aes.NewCipher(secret)
+ if err != nil {
+ return "", err
+ }
+
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return "", err
+ }
+
+ decoded := make([]byte, base64.StdEncoding.DecodedLen(len(encoded)))
+ n, err := base64.StdEncoding.Decode(decoded, []byte(encoded))
+ if err != nil {
+ return "", err
+ }
+ decoded = decoded[:n]
+
+ nonceSize := aesgcm.NonceSize()
+ if len(decoded) < nonceSize {
+ return "", fmt.Errorf("cookie too short")
+ }
+
+ nonce, decoded := decoded[:nonceSize], decoded[nonceSize:]
+ plain, err := aesgcm.Open(nil, nonce, decoded, nil)
+ if err != nil {
+ return "", err
+ }
+
+ return string(plain), nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/integrity.go b/vendor/github.com/mattermost/mattermost-server/v6/model/integrity.go
new file mode 100644
index 00000000..744ad07c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/integrity.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "errors"
+)
+
+type OrphanedRecord struct {
+ ParentId *string `json:"parent_id"`
+ ChildId *string `json:"child_id"`
+}
+
+type RelationalIntegrityCheckData struct {
+ ParentName string `json:"parent_name"`
+ ChildName string `json:"child_name"`
+ ParentIdAttr string `json:"parent_id_attr"`
+ ChildIdAttr string `json:"child_id_attr"`
+ Records []OrphanedRecord `json:"records"`
+}
+
+type IntegrityCheckResult struct {
+ Data interface{} `json:"data"`
+ Err error `json:"err"`
+}
+
+func (r *IntegrityCheckResult) UnmarshalJSON(b []byte) error {
+ var data map[string]interface{}
+ if err := json.Unmarshal(b, &data); err != nil {
+ return err
+ }
+ if d, ok := data["data"]; ok && d != nil {
+ var rdata RelationalIntegrityCheckData
+ m := d.(map[string]interface{})
+ rdata.ParentName = m["parent_name"].(string)
+ rdata.ChildName = m["child_name"].(string)
+ rdata.ParentIdAttr = m["parent_id_attr"].(string)
+ rdata.ChildIdAttr = m["child_id_attr"].(string)
+ for _, recData := range m["records"].([]interface{}) {
+ var record OrphanedRecord
+ m := recData.(map[string]interface{})
+ if val := m["parent_id"]; val != nil {
+ record.ParentId = NewString(val.(string))
+ }
+ if val := m["child_id"]; val != nil {
+ record.ChildId = NewString(val.(string))
+ }
+ rdata.Records = append(rdata.Records, record)
+ }
+ r.Data = rdata
+ }
+ if err, ok := data["err"]; ok && err != nil {
+ r.Err = errors.New(data["err"].(string))
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/job.go b/vendor/github.com/mattermost/mattermost-server/v6/model/job.go
new file mode 100644
index 00000000..13c6154f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/job.go
@@ -0,0 +1,130 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "time"
+)
+
+const (
+ JobTypeDataRetention = "data_retention"
+ JobTypeMessageExport = "message_export"
+ JobTypeElasticsearchPostIndexing = "elasticsearch_post_indexing"
+ JobTypeElasticsearchPostAggregation = "elasticsearch_post_aggregation"
+ JobTypeBlevePostIndexing = "bleve_post_indexing"
+ JobTypeLdapSync = "ldap_sync"
+ JobTypeMigrations = "migrations"
+ JobTypePlugins = "plugins"
+ JobTypeExpiryNotify = "expiry_notify"
+ JobTypeProductNotices = "product_notices"
+ JobTypeActiveUsers = "active_users"
+ JobTypeImportProcess = "import_process"
+ JobTypeImportDelete = "import_delete"
+ JobTypeExportProcess = "export_process"
+ JobTypeExportDelete = "export_delete"
+ JobTypeCloud = "cloud"
+ JobTypeResendInvitationEmail = "resend_invitation_email"
+ JobTypeExtractContent = "extract_content"
+
+ JobStatusPending = "pending"
+ JobStatusInProgress = "in_progress"
+ JobStatusSuccess = "success"
+ JobStatusError = "error"
+ JobStatusCancelRequested = "cancel_requested"
+ JobStatusCanceled = "canceled"
+ JobStatusWarning = "warning"
+)
+
+var AllJobTypes = [...]string{
+ JobTypeDataRetention,
+ JobTypeMessageExport,
+ JobTypeElasticsearchPostIndexing,
+ JobTypeElasticsearchPostAggregation,
+ JobTypeBlevePostIndexing,
+ JobTypeLdapSync,
+ JobTypeMigrations,
+ JobTypePlugins,
+ JobTypeExpiryNotify,
+ JobTypeProductNotices,
+ JobTypeActiveUsers,
+ JobTypeImportProcess,
+ JobTypeImportDelete,
+ JobTypeExportProcess,
+ JobTypeExportDelete,
+ JobTypeCloud,
+ JobTypeExtractContent,
+}
+
+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 !IsValidId(j.Id) {
+ 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 JobTypeDataRetention:
+ case JobTypeElasticsearchPostIndexing:
+ case JobTypeElasticsearchPostAggregation:
+ case JobTypeBlevePostIndexing:
+ case JobTypeLdapSync:
+ case JobTypeMessageExport:
+ case JobTypeMigrations:
+ case JobTypePlugins:
+ case JobTypeProductNotices:
+ case JobTypeExpiryNotify:
+ case JobTypeActiveUsers:
+ case JobTypeImportProcess:
+ case JobTypeImportDelete:
+ case JobTypeExportProcess:
+ case JobTypeExportDelete:
+ case JobTypeCloud:
+ case JobTypeResendInvitationEmail:
+ case JobTypeExtractContent:
+ default:
+ return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest)
+ }
+
+ switch j.Status {
+ case JobStatusPending:
+ case JobStatusInProgress:
+ case JobStatusSuccess:
+ case JobStatusError:
+ case JobStatusCancelRequested:
+ case JobStatusCanceled:
+ default:
+ return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+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/v6/model/ldap.go b/vendor/github.com/mattermost/mattermost-server/v6/model/ldap.go
new file mode 100644
index 00000000..314e7222
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/ldap.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ UserAuthServiceLdap = "ldap"
+ LdapPublicCertificateName = "ldap-public.crt"
+ LdapPrivateKeyName = "ldap-private.key"
+)
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/license.go b/vendor/github.com/mattermost/mattermost-server/v6/model/license.go
new file mode 100644
index 00000000..d83a61f9
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/license.go
@@ -0,0 +1,343 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+)
+
+const (
+ ExpiredLicenseError = "api.license.add_license.expired.app_error"
+ InvalidLicenseError = "api.license.add_license.invalid.app_error"
+ LicenseGracePeriod = 1000 * 60 * 60 * 24 * 10 //10 days
+ LicenseRenewalLink = "https://mattermost.com/renew/"
+
+ LicenseShortSkuE10 = "E10"
+ LicenseShortSkuE20 = "E20"
+ LicenseShortSkuProfessional = "professional"
+ LicenseShortSkuEnterprise = "enterprise"
+)
+
+const (
+ LicenseUpForRenewalEmailSent = "LicenseUpForRenewalEmailSent"
+)
+
+var (
+ trialDuration = 30*(time.Hour*24) + (time.Hour * 8) // 720 hours (30 days) + 8 hours is trial license duration
+ adminTrialDuration = 30*(time.Hour*24) + (time.Hour * 23) + (time.Minute * 59) + (time.Second * 59) // 720 hours (30 days) + 23 hours, 59 mins and 59 seconds
+
+ // a sanctioned trial's duration is either more than the upper bound,
+ // or less than the lower bound
+ sanctionedTrialDurationLowerBound = 31*(time.Hour*24) + (time.Hour * 23) + (time.Minute * 59) + (time.Second * 59) // 744 hours (31 days) + 23 hours, 59 mins and 59 seconds
+ sanctionedTrialDurationUpperBound = 29*(time.Hour*24) + (time.Hour * 23) + (time.Minute * 59) + (time.Second * 59) // 696 hours (29 days) + 23 hours, 59 mins and 59 seconds
+)
+
+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"`
+ SkuName string `json:"sku_name"`
+ SkuShortName string `json:"sku_short_name"`
+ IsTrial bool `json:"is_trial"`
+}
+
+type Customer struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Company string `json:"company"`
+}
+
+type TrialLicenseRequest struct {
+ ServerID string `json:"server_id"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ SiteURL string `json:"site_url"`
+ SiteName string `json:"site_name"`
+ Users int `json:"users"`
+ TermsAccepted bool `json:"terms_accepted"`
+ ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
+}
+
+type Features struct {
+ Users *int `json:"users"`
+ LDAP *bool `json:"ldap"`
+ LDAPGroups *bool `json:"ldap_groups"`
+ MFA *bool `json:"mfa"`
+ GoogleOAuth *bool `json:"google_oauth"`
+ Office365OAuth *bool `json:"office365_oauth"`
+ OpenId *bool `json:"openid"`
+ 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"`
+ GuestAccounts *bool `json:"guest_accounts"`
+ GuestAccountsPermissions *bool `json:"guest_accounts_permissions"`
+ IDLoadedPushNotifications *bool `json:"id_loaded"`
+ LockTeammateNameDisplay *bool `json:"lock_teammate_name_display"`
+ EnterprisePlugins *bool `json:"enterprise_plugins"`
+ AdvancedLogging *bool `json:"advanced_logging"`
+ Cloud *bool `json:"cloud"`
+ SharedChannels *bool `json:"shared_channels"`
+ RemoteClusterService *bool `json:"remote_cluster_service"`
+
+ // after we enabled more features 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,
+ "ldap_groups": *f.LDAPGroups,
+ "mfa": *f.MFA,
+ "google": *f.GoogleOAuth,
+ "office365": *f.Office365OAuth,
+ "openid": *f.OpenId,
+ "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,
+ "guest_accounts": *f.GuestAccounts,
+ "guest_accounts_permissions": *f.GuestAccountsPermissions,
+ "id_loaded": *f.IDLoadedPushNotifications,
+ "lock_teammate_name_display": *f.LockTeammateNameDisplay,
+ "enterprise_plugins": *f.EnterprisePlugins,
+ "advanced_logging": *f.AdvancedLogging,
+ "cloud": *f.Cloud,
+ "shared_channels": *f.SharedChannels,
+ "remote_cluster_service": *f.RemoteClusterService,
+ "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.LDAPGroups == nil {
+ f.LDAPGroups = 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.OpenId == nil {
+ f.OpenId = 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.GuestAccounts == nil {
+ f.GuestAccounts = NewBool(*f.FutureFeatures)
+ }
+
+ if f.GuestAccountsPermissions == nil {
+ f.GuestAccountsPermissions = NewBool(*f.FutureFeatures)
+ }
+
+ if f.CustomTermsOfService == nil {
+ f.CustomTermsOfService = NewBool(*f.FutureFeatures)
+ }
+
+ if f.IDLoadedPushNotifications == nil {
+ f.IDLoadedPushNotifications = NewBool(*f.FutureFeatures)
+ }
+
+ if f.LockTeammateNameDisplay == nil {
+ f.LockTeammateNameDisplay = NewBool(*f.FutureFeatures)
+ }
+
+ if f.EnterprisePlugins == nil {
+ f.EnterprisePlugins = NewBool(*f.FutureFeatures)
+ }
+
+ if f.AdvancedLogging == nil {
+ f.AdvancedLogging = NewBool(*f.FutureFeatures)
+ }
+
+ if f.Cloud == nil {
+ f.Cloud = NewBool(false)
+ }
+
+ if f.SharedChannels == nil {
+ f.SharedChannels = NewBool(*f.FutureFeatures)
+ }
+
+ if f.RemoteClusterService == nil {
+ f.RemoteClusterService = NewBool(*f.FutureFeatures)
+ }
+}
+
+func (l *License) IsExpired() bool {
+ return l.ExpiresAt < GetMillis()
+}
+
+func (l *License) IsPastGracePeriod() bool {
+ timeDiff := GetMillis() - l.ExpiresAt
+ return timeDiff > LicenseGracePeriod
+}
+
+func (l *License) IsWithinExpirationPeriod() bool {
+ days := l.DaysToExpiration()
+ return days <= 60 && days >= 58
+}
+
+func (l *License) DaysToExpiration() int {
+ dif := l.ExpiresAt - GetMillis()
+ d, _ := time.ParseDuration(fmt.Sprint(dif) + "ms")
+ days := d.Hours() / 24
+ return int(days)
+}
+
+func (l *License) IsStarted() bool {
+ return l.StartsAt < GetMillis()
+}
+
+func (l *License) IsTrialLicense() bool {
+ return l.IsTrial || (l.ExpiresAt-l.StartsAt) == trialDuration.Milliseconds() || (l.ExpiresAt-l.StartsAt) == adminTrialDuration.Milliseconds()
+}
+
+func (l *License) IsSanctionedTrial() bool {
+ duration := l.ExpiresAt - l.StartsAt
+
+ return l.IsTrialLicense() &&
+ (duration >= sanctionedTrialDurationLowerBound.Milliseconds() || duration <= sanctionedTrialDurationUpperBound.Milliseconds())
+}
+
+func (l *License) HasEnterpriseMarketplacePlugins() bool {
+ return *l.Features.EnterprisePlugins ||
+ l.SkuShortName == LicenseShortSkuE20 ||
+ l.SkuShortName == LicenseShortSkuProfessional ||
+ l.SkuShortName == LicenseShortSkuEnterprise
+}
+
+// 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 (lr *LicenseRecord) IsValid() *AppError {
+ if !IsValidId(lr.Id) {
+ 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 lr.Bytes == "" || 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/v6/model/link_metadata.go b/vendor/github.com/mattermost/mattermost-server/v6/model/link_metadata.go
new file mode 100644
index 00000000..66d10739
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/link_metadata.go
@@ -0,0 +1,193 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "hash/fnv"
+ "net/http"
+ "time"
+ "unicode/utf8"
+
+ "github.com/dyatlov/go-opengraph/opengraph"
+)
+
+const (
+ LinkMetadataTypeImage LinkMetadataType = "image"
+ LinkMetadataTypeNone LinkMetadataType = "none"
+ LinkMetadataTypeOpengraph LinkMetadataType = "opengraph"
+ LinkMetadataMaxImages int = 5
+)
+
+type LinkMetadataType string
+
+// LinkMetadata stores arbitrary data about a link posted in a message. This includes dimensions of linked images
+// and OpenGraph metadata.
+type LinkMetadata struct {
+ // Hash is a value computed from the URL and Timestamp for use as a primary key in the database.
+ Hash int64
+
+ URL string
+ Timestamp int64
+ Type LinkMetadataType
+
+ // Data is the actual metadata for the link. It should contain data of one of the following types:
+ // - *model.PostImage if the linked content is an image
+ // - *opengraph.OpenGraph if the linked content is an HTML document
+ // - nil if the linked content has no metadata
+ Data interface{}
+}
+
+// truncateText ensure string is 300 chars, truncate and add ellipsis
+// if it was bigger.
+func truncateText(original string) string {
+ if utf8.RuneCountInString(original) > 300 {
+ return fmt.Sprintf("%.300s[...]", original)
+ }
+ return original
+}
+
+func firstNImages(images []*opengraph.Image, maxImages int) []*opengraph.Image {
+ if maxImages < 0 { // don't break stuff, if it's weird, go for sane defaults
+ maxImages = LinkMetadataMaxImages
+ }
+ numImages := len(images)
+ if numImages > maxImages {
+ return images[0:maxImages]
+ }
+ return images
+}
+
+// TruncateOpenGraph ensure OpenGraph metadata doesn't grow too big by
+// shortening strings, trimming fields and reducing the number of
+// images.
+func TruncateOpenGraph(ogdata *opengraph.OpenGraph) *opengraph.OpenGraph {
+ if ogdata != nil {
+ empty := &opengraph.OpenGraph{}
+ ogdata.Title = truncateText(ogdata.Title)
+ ogdata.Description = truncateText(ogdata.Description)
+ ogdata.SiteName = truncateText(ogdata.SiteName)
+ ogdata.Article = empty.Article
+ ogdata.Book = empty.Book
+ ogdata.Profile = empty.Profile
+ ogdata.Determiner = empty.Determiner
+ ogdata.Locale = empty.Locale
+ ogdata.LocalesAlternate = empty.LocalesAlternate
+ ogdata.Images = firstNImages(ogdata.Images, LinkMetadataMaxImages)
+ ogdata.Audios = empty.Audios
+ ogdata.Videos = empty.Videos
+ }
+ return ogdata
+}
+
+func (o *LinkMetadata) PreSave() {
+ o.Hash = GenerateLinkMetadataHash(o.URL, o.Timestamp)
+}
+
+func (o *LinkMetadata) IsValid() *AppError {
+ if o.URL == "" {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.url.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.Timestamp == 0 || !isRoundedToNearestHour(o.Timestamp) {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.timestamp.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ switch o.Type {
+ case LinkMetadataTypeImage:
+ if o.Data == nil {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if _, ok := o.Data.(*PostImage); !ok {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data_type.app_error", nil, "", http.StatusBadRequest)
+ }
+ case LinkMetadataTypeNone:
+ if o.Data != nil {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data_type.app_error", nil, "", http.StatusBadRequest)
+ }
+ case LinkMetadataTypeOpengraph:
+ if o.Data == nil {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if _, ok := o.Data.(*opengraph.OpenGraph); !ok {
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.data_type.app_error", nil, "", http.StatusBadRequest)
+ }
+ default:
+ return NewAppError("LinkMetadata.IsValid", "model.link_metadata.is_valid.type.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+// DeserializeDataToConcreteType converts o.Data from JSON into properly structured data. This is intended to be used
+// after getting a LinkMetadata object that has been stored in the database.
+func (o *LinkMetadata) DeserializeDataToConcreteType() error {
+ var b []byte
+ switch t := o.Data.(type) {
+ case []byte:
+ // MySQL uses a byte slice for JSON
+ b = t
+ case string:
+ // Postgres uses a string for JSON
+ b = []byte(t)
+ }
+
+ if b == nil {
+ // Data doesn't need to be fixed
+ return nil
+ }
+
+ var data interface{}
+ var err error
+
+ switch o.Type {
+ case LinkMetadataTypeImage:
+ image := &PostImage{}
+
+ err = json.Unmarshal(b, &image)
+
+ data = image
+ case LinkMetadataTypeOpengraph:
+ og := &opengraph.OpenGraph{}
+
+ json.Unmarshal(b, &og)
+
+ data = og
+ }
+
+ if err != nil {
+ return err
+ }
+
+ o.Data = data
+
+ return nil
+}
+
+// FloorToNearestHour takes a timestamp (in milliseconds) and returns it rounded to the previous hour in UTC.
+func FloorToNearestHour(ms int64) int64 {
+ t := time.Unix(0, ms*int64(1000*1000)).UTC()
+
+ return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond)
+}
+
+// isRoundedToNearestHour returns true if the given timestamp (in milliseconds) has been rounded to the nearest hour in UTC.
+func isRoundedToNearestHour(ms int64) bool {
+ return FloorToNearestHour(ms) == ms
+}
+
+// GenerateLinkMetadataHash generates a unique hash for a given URL and timestamp for use as a database key.
+func GenerateLinkMetadataHash(url string, timestamp int64) int64 {
+ hash := fnv.New32()
+
+ // Note that we ignore write errors here because the Hash interface says that its Write will never return an error
+ binary.Write(hash, binary.LittleEndian, timestamp)
+ hash.Write([]byte(url))
+
+ return int64(hash.Sum32())
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/manifest.go b/vendor/github.com/mattermost/mattermost-server/v6/model/manifest.go
new file mode 100644
index 00000000..67b0d120
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/manifest.go
@@ -0,0 +1,455 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/blang/semver"
+ "github.com/pkg/errors"
+ "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 PluginSettingType int
+
+const (
+ Bool PluginSettingType = iota
+ Dropdown
+ Generated
+ Radio
+ Text
+ LongText
+ Number
+ Username
+ Custom
+)
+
+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.
+ //
+ // "longtext" will result in a multi line string that can be typed in manually.
+ //
+ // "number" will result in in integer setting that can be typed in manually.
+ //
+ // "username" will result in a text setting that will autocomplete to a username.
+ //
+ // "custom" will result in a custom defined setting and will load the custom component registered for the Web App System Console.
+ Type string `json:"type" yaml:"type"`
+
+ // The help text to display to the user. Supports Markdown formatting.
+ 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 "generated", "text", "longtext", "number" 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. Supports Markdown formatting.
+ Header string `json:"header" yaml:"header"`
+
+ // Optional text to display below the settings. Supports Markdown formatting.
+ 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.json:
+//
+//
+// {
+// "id": "com.mycompany.myplugin",
+// "name": "My Plugin",
+// "description": "This is my plugin",
+// "homepage_url": "https://example.com",
+// "support_url": "https://example.com/support",
+// "release_notes_url": "https://example.com/releases/v0.0.1",
+// "icon_path": "assets/logo.svg",
+// "version": "0.1.0",
+// "min_server_version": "5.6.0",
+// "server": {
+// "executables": {
+// "linux-amd64": "server/dist/plugin-linux-amd64",
+// "darwin-amd64": "server/dist/plugin-darwin-amd64",
+// "windows-amd64": "server/dist/plugin-windows-amd64.exe"
+// }
+// },
+// "webapp": {
+// "bundle_path": "webapp/dist/main.js"
+// },
+// "settings_schema": {
+// "header": "Some header text",
+// "footer": "Some footer text",
+// "settings": [{
+// "key": "someKey",
+// "display_name": "Enable Extra Feature",
+// "type": "bool",
+// "help_text": "When true, an extra feature will be enabled!",
+// "default": "false"
+// }]
+// },
+// "props": {
+// "someKey": "someData"
+// }
+// }
+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" yaml:"name"`
+
+ // A description of what your plugin is and does.
+ Description string `json:"description,omitempty" yaml:"description,omitempty"`
+
+ // HomepageURL is an optional link to learn more about the plugin.
+ HomepageURL string `json:"homepage_url,omitempty" yaml:"homepage_url,omitempty"`
+
+ // SupportURL is an optional URL where plugin issues can be reported.
+ SupportURL string `json:"support_url,omitempty" yaml:"support_url,omitempty"`
+
+ // ReleaseNotesURL is an optional URL where a changelog for the release can be found.
+ ReleaseNotesURL string `json:"release_notes_url,omitempty" yaml:"release_notes_url,omitempty"`
+
+ // A relative file path in the bundle that points to the plugins svg icon for use with the Plugin Marketplace.
+ // This should be relative to the root of your bundle and the location of the manifest file. Bitmap image formats are not supported.
+ IconPath string `json:"icon_path,omitempty" yaml:"icon_path,omitempty"`
+
+ // A version number for your plugin. Semantic versioning is recommended: http://semver.org
+ Version string `json:"version" yaml:"version"`
+
+ // The minimum Mattermost server version required for your plugin.
+ //
+ // Minimum server version: 5.6
+ MinServerVersion string `json:"min_server_version,omitempty" yaml:"min_server_version,omitempty"`
+
+ // Server defines the server-side portion of your plugin.
+ Server *ManifestServer `json:"server,omitempty" yaml:"server,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"`
+
+ // Plugins can store any kind of data in Props to allow other plugins to use it.
+ Props map[string]interface{} `json:"props,omitempty" yaml:"props,omitempty"`
+
+ // RequiredConfig defines any required server configuration fields for the plugin to function properly.
+ //
+ // Use the pluginapi.Configuration.CheckRequiredServerConfiguration method to enforce this.
+ RequiredConfig *Config `json:"required_configuration,omitempty" yaml:"required_configuration,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 map[string]string `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"`
+}
+
+// ManifestExecutables is a legacy structure capturing a subet of the known platform executables.
+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) 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
+
+ if server == nil {
+ return ""
+ }
+
+ var executable string
+ if len(server.Executables) > 0 {
+ osArch := fmt.Sprintf("%s-%s", goOs, goArch)
+ executable = server.Executables[osArch]
+ }
+
+ if executable == "" {
+ executable = server.Executable
+ }
+
+ return executable
+}
+
+func (m *Manifest) HasServer() bool {
+ return m.Server != nil
+}
+
+func (m *Manifest) HasWebapp() bool {
+ return m.Webapp != nil
+}
+
+func (m *Manifest) MeetMinServerVersion(serverVersion string) (bool, error) {
+ minServerVersion, err := semver.Parse(m.MinServerVersion)
+ if err != nil {
+ return false, errors.New("failed to parse MinServerVersion")
+ }
+ sv := semver.MustParse(serverVersion)
+ if sv.LT(minServerVersion) {
+ return false, nil
+ }
+ return true, nil
+}
+
+func (m *Manifest) IsValid() error {
+ if !IsValidPluginId(m.Id) {
+ return errors.New("invalid plugin ID")
+ }
+
+ if strings.TrimSpace(m.Name) == "" {
+ return errors.New("a plugin name is needed")
+ }
+
+ if m.HomepageURL != "" && !IsValidHTTPURL(m.HomepageURL) {
+ return errors.New("invalid HomepageURL")
+ }
+
+ if m.SupportURL != "" && !IsValidHTTPURL(m.SupportURL) {
+ return errors.New("invalid SupportURL")
+ }
+
+ if m.ReleaseNotesURL != "" && !IsValidHTTPURL(m.ReleaseNotesURL) {
+ return errors.New("invalid ReleaseNotesURL")
+ }
+
+ if m.Version != "" {
+ _, err := semver.Parse(m.Version)
+ if err != nil {
+ return errors.Wrap(err, "failed to parse Version")
+ }
+ }
+
+ if m.MinServerVersion != "" {
+ _, err := semver.Parse(m.MinServerVersion)
+ if err != nil {
+ return errors.Wrap(err, "failed to parse MinServerVersion")
+ }
+ }
+
+ if m.SettingsSchema != nil {
+ err := m.SettingsSchema.isValid()
+ if err != nil {
+ return errors.Wrap(err, "invalid settings schema")
+ }
+ }
+
+ return nil
+}
+
+func (s *PluginSettingsSchema) isValid() error {
+ for _, setting := range s.Settings {
+ err := setting.isValid()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (s *PluginSetting) isValid() error {
+ pluginSettingType, err := convertTypeToPluginSettingType(s.Type)
+ if err != nil {
+ return err
+ }
+
+ if s.RegenerateHelpText != "" && pluginSettingType != Generated {
+ return errors.New("should not set RegenerateHelpText for setting type that is not generated")
+ }
+
+ if s.Placeholder != "" && !(pluginSettingType == Generated ||
+ pluginSettingType == Text ||
+ pluginSettingType == LongText ||
+ pluginSettingType == Number ||
+ pluginSettingType == Username) {
+ return errors.New("should not set Placeholder for setting type not in text, generated or username")
+ }
+
+ if s.Options != nil {
+ if pluginSettingType != Radio && pluginSettingType != Dropdown {
+ return errors.New("should not set Options for setting type not in radio or dropdown")
+ }
+
+ for _, option := range s.Options {
+ if option.DisplayName == "" || option.Value == "" {
+ return errors.New("should not have empty Displayname or Value for any option")
+ }
+ }
+ }
+
+ return nil
+}
+
+func convertTypeToPluginSettingType(t string) (PluginSettingType, error) {
+ var settingType PluginSettingType
+ switch t {
+ case "bool":
+ return Bool, nil
+ case "dropdown":
+ return Dropdown, nil
+ case "generated":
+ return Generated, nil
+ case "radio":
+ return Radio, nil
+ case "text":
+ return Text, nil
+ case "number":
+ return Number, nil
+ case "longtext":
+ return LongText, nil
+ case "username":
+ return Username, nil
+ case "custom":
+ return Custom, nil
+ default:
+ return settingType, errors.New("invalid setting type: " + t)
+ }
+}
+
+// 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) {
+ return nil, "", ferr
+ }
+ continue
+ }
+ b, ioerr := ioutil.ReadAll(f)
+ f.Close()
+ if ioerr != nil {
+ return nil, path, ioerr
+ }
+ var parsed Manifest
+ err = yaml.Unmarshal(b, &parsed)
+ if err != nil {
+ return nil, path, err
+ }
+ manifest = &parsed
+ manifest.Id = strings.ToLower(manifest.Id)
+ return manifest, path, nil
+ }
+
+ path = filepath.Join(dir, "plugin.json")
+ f, ferr := os.Open(path)
+ if ferr != nil {
+ if os.IsNotExist(ferr) {
+ path = ""
+ }
+ return nil, path, ferr
+ }
+ defer f.Close()
+ var parsed Manifest
+ err = json.NewDecoder(f).Decode(&parsed)
+ if err != nil {
+ return nil, path, err
+ }
+ manifest = &parsed
+ manifest.Id = strings.ToLower(manifest.Id)
+ return manifest, path, nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/marketplace_plugin.go b/vendor/github.com/mattermost/mattermost-server/v6/model/marketplace_plugin.go
new file mode 100644
index 00000000..8f0371b1
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/marketplace_plugin.go
@@ -0,0 +1,127 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "io"
+ "net/url"
+ "strconv"
+
+ "github.com/pkg/errors"
+)
+
+// BaseMarketplacePlugin is a Mattermost plugin received from the Marketplace server.
+type BaseMarketplacePlugin struct {
+ HomepageURL string `json:"homepage_url"`
+ IconData string `json:"icon_data"`
+ DownloadURL string `json:"download_url"`
+ ReleaseNotesURL string `json:"release_notes_url"`
+ Labels []MarketplaceLabel `json:"labels,omitempty"`
+ Hosting string `json:"hosting"` // Indicated if the plugin is limited to a certain hosting type
+ AuthorType string `json:"author_type"` // The maintainer of the plugin
+ ReleaseStage string `json:"release_stage"` // The stage in the software release cycle that the plugin is in
+ Enterprise bool `json:"enterprise"` // Indicated if the plugin is an enterprise plugin
+ Signature string `json:"signature"` // Signature represents a signature of a plugin saved in base64 encoding.
+ Manifest *Manifest `json:"manifest"`
+}
+
+// MarketplaceLabel represents a label shown in the Marketplace UI.
+type MarketplaceLabel struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ URL string `json:"url"`
+ Color string `json:"color"`
+}
+
+// MarketplacePlugin is a state aware Marketplace plugin.
+type MarketplacePlugin struct {
+ *BaseMarketplacePlugin
+ InstalledVersion string `json:"installed_version"`
+}
+
+// BaseMarketplacePluginsFromReader decodes a json-encoded list of plugins from the given io.Reader.
+func BaseMarketplacePluginsFromReader(reader io.Reader) ([]*BaseMarketplacePlugin, error) {
+ plugins := []*BaseMarketplacePlugin{}
+ decoder := json.NewDecoder(reader)
+
+ if err := decoder.Decode(&plugins); err != nil && err != io.EOF {
+ return nil, err
+ }
+
+ return plugins, nil
+}
+
+// MarketplacePluginsFromReader decodes a json-encoded list of plugins from the given io.Reader.
+func MarketplacePluginsFromReader(reader io.Reader) ([]*MarketplacePlugin, error) {
+ plugins := []*MarketplacePlugin{}
+ decoder := json.NewDecoder(reader)
+
+ if err := decoder.Decode(&plugins); err != nil && err != io.EOF {
+ return nil, err
+ }
+
+ return plugins, nil
+}
+
+// DecodeSignature Decodes signature and returns ReadSeeker.
+func (plugin *BaseMarketplacePlugin) DecodeSignature() (io.ReadSeeker, error) {
+ signatureBytes, err := base64.StdEncoding.DecodeString(plugin.Signature)
+ if err != nil {
+ return nil, errors.Wrap(err, "Unable to decode base64 signature.")
+ }
+ return bytes.NewReader(signatureBytes), nil
+}
+
+// MarketplacePluginFilter describes the parameters to request a list of plugins.
+type MarketplacePluginFilter struct {
+ Page int
+ PerPage int
+ Filter string
+ ServerVersion string
+ BuildEnterpriseReady bool
+ EnterprisePlugins bool
+ Cloud bool
+ LocalOnly bool
+ Platform string
+ PluginId string
+ ReturnAllVersions bool
+}
+
+// ApplyToURL modifies the given url to include query string parameters for the request.
+func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
+ q := u.Query()
+ q.Add("page", strconv.Itoa(filter.Page))
+ if filter.PerPage > 0 {
+ q.Add("per_page", strconv.Itoa(filter.PerPage))
+ }
+ q.Add("filter", filter.Filter)
+ q.Add("server_version", filter.ServerVersion)
+ q.Add("build_enterprise_ready", strconv.FormatBool(filter.BuildEnterpriseReady))
+ q.Add("enterprise_plugins", strconv.FormatBool(filter.EnterprisePlugins))
+ q.Add("cloud", strconv.FormatBool(filter.Cloud))
+ q.Add("local_only", strconv.FormatBool(filter.LocalOnly))
+ q.Add("platform", filter.Platform)
+ q.Add("plugin_id", filter.PluginId)
+ q.Add("return_all_versions", strconv.FormatBool(filter.ReturnAllVersions))
+ u.RawQuery = q.Encode()
+}
+
+// InstallMarketplacePluginRequest struct describes parameters of the requested plugin.
+type InstallMarketplacePluginRequest struct {
+ Id string `json:"id"`
+ Version string `json:"version"`
+}
+
+// PluginRequestFromReader decodes a json-encoded plugin request from the given io.Reader.
+func PluginRequestFromReader(reader io.Reader) (*InstallMarketplacePluginRequest, error) {
+ var r *InstallMarketplacePluginRequest
+ err := json.NewDecoder(reader).Decode(&r)
+ if err != nil {
+ return nil, err
+ }
+ return r, nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/mention_map.go b/vendor/github.com/mattermost/mattermost-server/v6/model/mention_map.go
new file mode 100644
index 00000000..2f3444dd
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/mention_map.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/url"
+)
+
+type UserMentionMap map[string]string
+type ChannelMentionMap map[string]string
+
+const (
+ userMentionsKey = "user_mentions"
+ userMentionsIdsKey = "user_mentions_ids"
+ channelMentionsKey = "channel_mentions"
+ channelMentionsIdsKey = "channel_mentions_ids"
+)
+
+func UserMentionMapFromURLValues(values url.Values) (UserMentionMap, error) {
+ return mentionsFromURLValues(values, userMentionsKey, userMentionsIdsKey)
+}
+
+func (m UserMentionMap) ToURLValues() url.Values {
+ return mentionsToURLValues(m, userMentionsKey, userMentionsIdsKey)
+}
+
+func ChannelMentionMapFromURLValues(values url.Values) (ChannelMentionMap, error) {
+ return mentionsFromURLValues(values, channelMentionsKey, channelMentionsIdsKey)
+}
+
+func (m ChannelMentionMap) ToURLValues() url.Values {
+ return mentionsToURLValues(m, channelMentionsKey, channelMentionsIdsKey)
+}
+
+func mentionsFromURLValues(values url.Values, mentionKey, idKey string) (map[string]string, error) {
+ mentions, mentionsOk := values[mentionKey]
+ ids, idsOk := values[idKey]
+
+ if !mentionsOk && !idsOk {
+ return map[string]string{}, nil
+ }
+
+ if !mentionsOk {
+ return nil, fmt.Errorf("%s key not found", mentionKey)
+ }
+
+ if !idsOk {
+ return nil, fmt.Errorf("%s key not found", idKey)
+ }
+
+ if len(mentions) != len(ids) {
+ return nil, fmt.Errorf("keys %s and %s have different length", mentionKey, idKey)
+ }
+
+ mentionsMap := make(map[string]string)
+ for i, mention := range mentions {
+ id := ids[i]
+
+ if oldId, ok := mentionsMap[mention]; ok && oldId != id {
+ return nil, fmt.Errorf("key %s has two different values: %s and %s", mention, oldId, id)
+ }
+
+ mentionsMap[mention] = id
+ }
+
+ return mentionsMap, nil
+}
+
+func mentionsToURLValues(mentions map[string]string, mentionKey, idKey string) url.Values {
+ values := url.Values{}
+
+ for mention, id := range mentions {
+ values.Add(mentionKey, mention)
+ values.Add(idKey, id)
+ }
+
+ return values
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/message_export.go b/vendor/github.com/mattermost/mattermost-server/v6/model/message_export.go
new file mode 100644
index 00000000..cc8f882b
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/message_export.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import "encoding/json"
+
+type MessageExport struct {
+ TeamId *string
+ TeamName *string
+ TeamDisplayName *string
+
+ ChannelId *string
+ ChannelName *string
+ ChannelDisplayName *string
+ ChannelType *ChannelType
+
+ UserId *string
+ UserEmail *string
+ Username *string
+ IsBot bool
+
+ PostId *string
+ PostCreateAt *int64
+ PostUpdateAt *int64
+ PostDeleteAt *int64
+ PostMessage *string
+ PostType *string
+ PostRootId *string
+ PostProps *string
+ PostOriginalId *string
+ PostFileIds StringArray
+}
+
+type MessageExportCursor struct {
+ LastPostUpdateAt int64
+ LastPostId string
+}
+
+// PreviewID returns the value of the post's previewed_post prop, if present, or an empty string.
+func (m *MessageExport) PreviewID() string {
+ var previewID string
+ props := map[string]interface{}{}
+ if m.PostProps != nil && json.Unmarshal([]byte(*m.PostProps), &props) == nil {
+ if val, ok := props[PostPropsPreviewedPost]; ok {
+ previewID = val.(string)
+ }
+ }
+ return previewID
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/mfa_secret.go b/vendor/github.com/mattermost/mattermost-server/v6/model/mfa_secret.go
new file mode 100644
index 00000000..8cfa675e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/mfa_secret.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type MfaSecret struct {
+ Secret string `json:"secret"`
+ QRCode string `json:"qr_code"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/migration.go b/vendor/github.com/mattermost/mattermost-server/v6/model/migration.go
new file mode 100644
index 00000000..2e0efb46
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/migration.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ AdvancedPermissionsMigrationKey = "AdvancedPermissionsMigrationComplete"
+ MigrationKeyAdvancedPermissionsPhase2 = "migration_advanced_permissions_phase_2"
+
+ MigrationKeyEmojiPermissionsSplit = "emoji_permissions_split"
+ MigrationKeyWebhookPermissionsSplit = "webhook_permissions_split"
+ MigrationKeyListJoinPublicPrivateTeams = "list_join_public_private_teams"
+ MigrationKeyRemovePermanentDeleteUser = "remove_permanent_delete_user"
+ MigrationKeyAddBotPermissions = "add_bot_permissions"
+ MigrationKeyApplyChannelManageDeleteToChannelUser = "apply_channel_manage_delete_to_channel_user"
+ MigrationKeyRemoveChannelManageDeleteFromTeamUser = "remove_channel_manage_delete_from_team_user"
+ MigrationKeyViewMembersNewPermission = "view_members_new_permission"
+ MigrationKeyAddManageGuestsPermissions = "add_manage_guests_permissions"
+ MigrationKeyChannelModerationsPermissions = "channel_moderations_permissions"
+ MigrationKeyAddUseGroupMentionsPermission = "add_use_group_mentions_permission"
+ MigrationKeyAddSystemConsolePermissions = "add_system_console_permissions"
+ MigrationKeySidebarCategoriesPhase2 = "migration_sidebar_categories_phase_2"
+ MigrationKeyAddConvertChannelPermissions = "add_convert_channel_permissions"
+ MigrationKeyAddSystemRolesPermissions = "add_system_roles_permissions"
+ MigrationKeyAddBillingPermissions = "add_billing_permissions"
+ MigrationKeyAddManageSharedChannelPermissions = "manage_shared_channel_permissions"
+ MigrationKeyAddManageSecureConnectionsPermissions = "manage_secure_connections_permissions"
+ MigrationKeyAddDownloadComplianceExportResults = "download_compliance_export_results"
+ MigrationKeyAddComplianceSubsectionPermissions = "compliance_subsection_permissions"
+ MigrationKeyAddExperimentalSubsectionPermissions = "experimental_subsection_permissions"
+ MigrationKeyAddAuthenticationSubsectionPermissions = "authentication_subsection_permissions"
+ MigrationKeyAddSiteSubsectionPermissions = "site_subsection_permissions"
+ MigrationKeyAddEnvironmentSubsectionPermissions = "environment_subsection_permissions"
+ MigrationKeyAddReportingSubsectionPermissions = "reporting_subsection_permissions"
+ MigrationKeyAddTestEmailAncillaryPermission = "test_email_ancillary_permission"
+ MigrationKeyAddAboutSubsectionPermissions = "about_subsection_permissions"
+ MigrationKeyAddIntegrationsSubsectionPermissions = "integrations_subsection_permissions"
+)
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/oauth.go b/vendor/github.com/mattermost/mattermost-server/v6/model/oauth.go
new file mode 100644
index 00000000..a3d7f95f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/oauth.go
@@ -0,0 +1,127 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+ "unicode/utf8"
+)
+
+const (
+ OAuthActionSignup = "signup"
+ OAuthActionLogin = "login"
+ OAuthActionEmailToSSO = "email_to_sso"
+ OAuthActionSSOToEmail = "sso_to_email"
+ OAuthActionMobile = "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 !IsValidId(a.Id) {
+ 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 !IsValidId(a.CreatorId) {
+ return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
+ }
+
+ if a.ClientSecret == "" || len(a.ClientSecret) > 128 {
+ return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
+ }
+
+ if a.Name == "" || 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 a.Homepage == "" || 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 a.IconURL != "" {
+ 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()
+}
+
+// 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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/outgoing_webhook.go b/vendor/github.com/mattermost/mattermost-server/v6/model/outgoing_webhook.go
new file mode 100644
index 00000000..b24aed67
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/outgoing_webhook.go
@@ -0,0 +1,224 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "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 OutgoingHookResponseTypeComment = "comment"
+
+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) IsValid() *AppError {
+
+ if !IsValidId(o.Id) {
+ 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 !IsValidId(o.CreatorId) {
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if o.ChannelId != "" && !IsValidId(o.ChannelId) {
+ return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.TeamId) {
+ 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 triggerWord == "" {
+ 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 word == "" {
+ return false
+ }
+
+ for _, trigger := range o.TriggerWords {
+ if trigger == word {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
+ if word == "" {
+ 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 word == "" {
+ 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/v6/model/permalink.go b/vendor/github.com/mattermost/mattermost-server/v6/model/permalink.go
new file mode 100644
index 00000000..6a19fb75
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/permalink.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type Permalink struct {
+ PreviewPost *PreviewPost `json:"preview_post"`
+}
+
+type PreviewPost struct {
+ PostID string `json:"post_id"`
+ Post *Post `json:"post"`
+ TeamName string `json:"team_name"`
+ ChannelDisplayName string `json:"channel_display_name"`
+}
+
+func NewPreviewPost(post *Post, team *Team, channel *Channel) *PreviewPost {
+ if post == nil {
+ return nil
+ }
+ return &PreviewPost{
+ PostID: post.Id,
+ Post: post,
+ TeamName: team.Name,
+ ChannelDisplayName: channel.DisplayName,
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/permission.go b/vendor/github.com/mattermost/mattermost-server/v6/model/permission.go
new file mode 100644
index 00000000..6c10bab0
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/permission.go
@@ -0,0 +1,2192 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ PermissionScopeSystem = "system_scope"
+ PermissionScopeTeam = "team_scope"
+ PermissionScopeChannel = "channel_scope"
+)
+
+type Permission struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Scope string `json:"scope"`
+}
+
+var PermissionInviteUser *Permission
+var PermissionAddUserToTeam *Permission
+var PermissionUseSlashCommands *Permission
+var PermissionManageSlashCommands *Permission
+var PermissionManageOthersSlashCommands *Permission
+var PermissionCreatePublicChannel *Permission
+var PermissionCreatePrivateChannel *Permission
+var PermissionManagePublicChannelMembers *Permission
+var PermissionManagePrivateChannelMembers *Permission
+var PermissionConvertPublicChannelToPrivate *Permission
+var PermissionConvertPrivateChannelToPublic *Permission
+var PermissionAssignSystemAdminRole *Permission
+var PermissionManageRoles *Permission
+var PermissionManageTeamRoles *Permission
+var PermissionManageChannelRoles *Permission
+var PermissionCreateDirectChannel *Permission
+var PermissionCreateGroupChannel *Permission
+var PermissionManagePublicChannelProperties *Permission
+var PermissionManagePrivateChannelProperties *Permission
+var PermissionListPublicTeams *Permission
+var PermissionJoinPublicTeams *Permission
+var PermissionListPrivateTeams *Permission
+var PermissionJoinPrivateTeams *Permission
+var PermissionListTeamChannels *Permission
+var PermissionJoinPublicChannels *Permission
+var PermissionDeletePublicChannel *Permission
+var PermissionDeletePrivateChannel *Permission
+var PermissionEditOtherUsers *Permission
+var PermissionReadChannel *Permission
+var PermissionReadPublicChannelGroups *Permission
+var PermissionReadPrivateChannelGroups *Permission
+var PermissionReadPublicChannel *Permission
+var PermissionAddReaction *Permission
+var PermissionRemoveReaction *Permission
+var PermissionRemoveOthersReactions *Permission
+var PermissionPermanentDeleteUser *Permission
+var PermissionUploadFile *Permission
+var PermissionGetPublicLink *Permission
+var PermissionManageWebhooks *Permission
+var PermissionManageOthersWebhooks *Permission
+var PermissionManageIncomingWebhooks *Permission
+var PermissionManageOutgoingWebhooks *Permission
+var PermissionManageOthersIncomingWebhooks *Permission
+var PermissionManageOthersOutgoingWebhooks *Permission
+var PermissionManageOAuth *Permission
+var PermissionManageSystemWideOAuth *Permission
+var PermissionManageEmojis *Permission
+var PermissionManageOthersEmojis *Permission
+var PermissionCreateEmojis *Permission
+var PermissionDeleteEmojis *Permission
+var PermissionDeleteOthersEmojis *Permission
+var PermissionCreatePost *Permission
+var PermissionCreatePostPublic *Permission
+var PermissionCreatePostEphemeral *Permission
+var PermissionEditPost *Permission
+var PermissionEditOthersPosts *Permission
+var PermissionDeletePost *Permission
+var PermissionDeleteOthersPosts *Permission
+var PermissionRemoveUserFromTeam *Permission
+var PermissionCreateTeam *Permission
+var PermissionManageTeam *Permission
+var PermissionImportTeam *Permission
+var PermissionViewTeam *Permission
+var PermissionListUsersWithoutTeam *Permission
+var PermissionReadJobs *Permission
+var PermissionManageJobs *Permission
+var PermissionCreateUserAccessToken *Permission
+var PermissionReadUserAccessToken *Permission
+var PermissionRevokeUserAccessToken *Permission
+var PermissionCreateBot *Permission
+var PermissionAssignBot *Permission
+var PermissionReadBots *Permission
+var PermissionReadOthersBots *Permission
+var PermissionManageBots *Permission
+var PermissionManageOthersBots *Permission
+var PermissionViewMembers *Permission
+var PermissionInviteGuest *Permission
+var PermissionPromoteGuest *Permission
+var PermissionDemoteToGuest *Permission
+var PermissionUseChannelMentions *Permission
+var PermissionUseGroupMentions *Permission
+var PermissionReadOtherUsersTeams *Permission
+var PermissionEditBrand *Permission
+var PermissionManageSharedChannels *Permission
+var PermissionManageSecureConnections *Permission
+var PermissionDownloadComplianceExportResult *Permission
+var PermissionCreateDataRetentionJob *Permission
+var PermissionReadDataRetentionJob *Permission
+var PermissionCreateComplianceExportJob *Permission
+var PermissionReadComplianceExportJob *Permission
+var PermissionReadAudits *Permission
+var PermissionTestElasticsearch *Permission
+var PermissionTestSiteURL *Permission
+var PermissionTestS3 *Permission
+var PermissionReloadConfig *Permission
+var PermissionInvalidateCaches *Permission
+var PermissionRecycleDatabaseConnections *Permission
+var PermissionPurgeElasticsearchIndexes *Permission
+var PermissionTestEmail *Permission
+var PermissionCreateElasticsearchPostIndexingJob *Permission
+var PermissionCreateElasticsearchPostAggregationJob *Permission
+var PermissionReadElasticsearchPostIndexingJob *Permission
+var PermissionReadElasticsearchPostAggregationJob *Permission
+var PermissionPurgeBleveIndexes *Permission
+var PermissionCreatePostBleveIndexesJob *Permission
+var PermissionCreateLdapSyncJob *Permission
+var PermissionReadLdapSyncJob *Permission
+var PermissionTestLdap *Permission
+var PermissionInvalidateEmailInvite *Permission
+var PermissionGetSamlMetadataFromIdp *Permission
+var PermissionAddSamlPublicCert *Permission
+var PermissionAddSamlPrivateCert *Permission
+var PermissionAddSamlIdpCert *Permission
+var PermissionRemoveSamlPublicCert *Permission
+var PermissionRemoveSamlPrivateCert *Permission
+var PermissionRemoveSamlIdpCert *Permission
+var PermissionGetSamlCertStatus *Permission
+var PermissionAddLdapPublicCert *Permission
+var PermissionAddLdapPrivateCert *Permission
+var PermissionRemoveLdapPublicCert *Permission
+var PermissionRemoveLdapPrivateCert *Permission
+var PermissionGetLogs *Permission
+var PermissionGetAnalytics *Permission
+var PermissionReadLicenseInformation *Permission
+var PermissionManageLicenseInformation *Permission
+
+var PermissionSysconsoleReadAbout *Permission
+var PermissionSysconsoleWriteAbout *Permission
+
+var PermissionSysconsoleReadAboutEditionAndLicense *Permission
+var PermissionSysconsoleWriteAboutEditionAndLicense *Permission
+
+var PermissionSysconsoleReadBilling *Permission
+var PermissionSysconsoleWriteBilling *Permission
+
+var PermissionSysconsoleReadReporting *Permission
+var PermissionSysconsoleWriteReporting *Permission
+
+var PermissionSysconsoleReadReportingSiteStatistics *Permission
+var PermissionSysconsoleWriteReportingSiteStatistics *Permission
+
+var PermissionSysconsoleReadReportingTeamStatistics *Permission
+var PermissionSysconsoleWriteReportingTeamStatistics *Permission
+
+var PermissionSysconsoleReadReportingServerLogs *Permission
+var PermissionSysconsoleWriteReportingServerLogs *Permission
+
+var PermissionSysconsoleReadUserManagementUsers *Permission
+var PermissionSysconsoleWriteUserManagementUsers *Permission
+
+var PermissionSysconsoleReadUserManagementGroups *Permission
+var PermissionSysconsoleWriteUserManagementGroups *Permission
+
+var PermissionSysconsoleReadUserManagementTeams *Permission
+var PermissionSysconsoleWriteUserManagementTeams *Permission
+
+var PermissionSysconsoleReadUserManagementChannels *Permission
+var PermissionSysconsoleWriteUserManagementChannels *Permission
+
+var PermissionSysconsoleReadUserManagementPermissions *Permission
+var PermissionSysconsoleWriteUserManagementPermissions *Permission
+
+var PermissionSysconsoleReadUserManagementSystemRoles *Permission
+var PermissionSysconsoleWriteUserManagementSystemRoles *Permission
+
+// DEPRECATED
+var PermissionSysconsoleReadEnvironment *Permission
+
+// DEPRECATED
+var PermissionSysconsoleWriteEnvironment *Permission
+
+var PermissionSysconsoleReadEnvironmentWebServer *Permission
+var PermissionSysconsoleWriteEnvironmentWebServer *Permission
+
+var PermissionSysconsoleReadEnvironmentDatabase *Permission
+var PermissionSysconsoleWriteEnvironmentDatabase *Permission
+
+var PermissionSysconsoleReadEnvironmentElasticsearch *Permission
+var PermissionSysconsoleWriteEnvironmentElasticsearch *Permission
+
+var PermissionSysconsoleReadEnvironmentFileStorage *Permission
+var PermissionSysconsoleWriteEnvironmentFileStorage *Permission
+
+var PermissionSysconsoleReadEnvironmentImageProxy *Permission
+var PermissionSysconsoleWriteEnvironmentImageProxy *Permission
+
+var PermissionSysconsoleReadEnvironmentSMTP *Permission
+var PermissionSysconsoleWriteEnvironmentSMTP *Permission
+
+var PermissionSysconsoleReadEnvironmentPushNotificationServer *Permission
+var PermissionSysconsoleWriteEnvironmentPushNotificationServer *Permission
+
+var PermissionSysconsoleReadEnvironmentHighAvailability *Permission
+var PermissionSysconsoleWriteEnvironmentHighAvailability *Permission
+
+var PermissionSysconsoleReadEnvironmentRateLimiting *Permission
+var PermissionSysconsoleWriteEnvironmentRateLimiting *Permission
+
+var PermissionSysconsoleReadEnvironmentLogging *Permission
+var PermissionSysconsoleWriteEnvironmentLogging *Permission
+
+var PermissionSysconsoleReadEnvironmentSessionLengths *Permission
+var PermissionSysconsoleWriteEnvironmentSessionLengths *Permission
+
+var PermissionSysconsoleReadEnvironmentPerformanceMonitoring *Permission
+var PermissionSysconsoleWriteEnvironmentPerformanceMonitoring *Permission
+
+var PermissionSysconsoleReadEnvironmentDeveloper *Permission
+var PermissionSysconsoleWriteEnvironmentDeveloper *Permission
+
+var PermissionSysconsoleReadSite *Permission
+var PermissionSysconsoleWriteSite *Permission
+
+var PermissionSysconsoleReadSiteCustomization *Permission
+var PermissionSysconsoleWriteSiteCustomization *Permission
+
+var PermissionSysconsoleReadSiteLocalization *Permission
+var PermissionSysconsoleWriteSiteLocalization *Permission
+
+var PermissionSysconsoleReadSiteUsersAndTeams *Permission
+var PermissionSysconsoleWriteSiteUsersAndTeams *Permission
+
+var PermissionSysconsoleReadSiteNotifications *Permission
+var PermissionSysconsoleWriteSiteNotifications *Permission
+
+var PermissionSysconsoleReadSiteAnnouncementBanner *Permission
+var PermissionSysconsoleWriteSiteAnnouncementBanner *Permission
+
+var PermissionSysconsoleReadSiteEmoji *Permission
+var PermissionSysconsoleWriteSiteEmoji *Permission
+
+var PermissionSysconsoleReadSitePosts *Permission
+var PermissionSysconsoleWriteSitePosts *Permission
+
+var PermissionSysconsoleReadSiteFileSharingAndDownloads *Permission
+var PermissionSysconsoleWriteSiteFileSharingAndDownloads *Permission
+
+var PermissionSysconsoleReadSitePublicLinks *Permission
+var PermissionSysconsoleWriteSitePublicLinks *Permission
+
+var PermissionSysconsoleReadSiteNotices *Permission
+var PermissionSysconsoleWriteSiteNotices *Permission
+
+var PermissionSysconsoleReadAuthentication *Permission
+var PermissionSysconsoleWriteAuthentication *Permission
+
+var PermissionSysconsoleReadAuthenticationSignup *Permission
+var PermissionSysconsoleWriteAuthenticationSignup *Permission
+
+var PermissionSysconsoleReadAuthenticationEmail *Permission
+var PermissionSysconsoleWriteAuthenticationEmail *Permission
+
+var PermissionSysconsoleReadAuthenticationPassword *Permission
+var PermissionSysconsoleWriteAuthenticationPassword *Permission
+
+var PermissionSysconsoleReadAuthenticationMfa *Permission
+var PermissionSysconsoleWriteAuthenticationMfa *Permission
+
+var PermissionSysconsoleReadAuthenticationLdap *Permission
+var PermissionSysconsoleWriteAuthenticationLdap *Permission
+
+var PermissionSysconsoleReadAuthenticationSaml *Permission
+var PermissionSysconsoleWriteAuthenticationSaml *Permission
+
+var PermissionSysconsoleReadAuthenticationOpenid *Permission
+var PermissionSysconsoleWriteAuthenticationOpenid *Permission
+
+var PermissionSysconsoleReadAuthenticationGuestAccess *Permission
+var PermissionSysconsoleWriteAuthenticationGuestAccess *Permission
+
+var PermissionSysconsoleReadPlugins *Permission
+var PermissionSysconsoleWritePlugins *Permission
+
+var PermissionSysconsoleReadIntegrations *Permission
+var PermissionSysconsoleWriteIntegrations *Permission
+
+var PermissionSysconsoleReadIntegrationsIntegrationManagement *Permission
+var PermissionSysconsoleWriteIntegrationsIntegrationManagement *Permission
+
+var PermissionSysconsoleReadIntegrationsBotAccounts *Permission
+var PermissionSysconsoleWriteIntegrationsBotAccounts *Permission
+
+var PermissionSysconsoleReadIntegrationsGif *Permission
+var PermissionSysconsoleWriteIntegrationsGif *Permission
+
+var PermissionSysconsoleReadIntegrationsCors *Permission
+var PermissionSysconsoleWriteIntegrationsCors *Permission
+
+var PermissionSysconsoleReadCompliance *Permission
+var PermissionSysconsoleWriteCompliance *Permission
+
+var PermissionSysconsoleReadComplianceDataRetentionPolicy *Permission
+var PermissionSysconsoleWriteComplianceDataRetentionPolicy *Permission
+
+var PermissionSysconsoleReadComplianceComplianceExport *Permission
+var PermissionSysconsoleWriteComplianceComplianceExport *Permission
+
+var PermissionSysconsoleReadComplianceComplianceMonitoring *Permission
+var PermissionSysconsoleWriteComplianceComplianceMonitoring *Permission
+
+var PermissionSysconsoleReadComplianceCustomTermsOfService *Permission
+var PermissionSysconsoleWriteComplianceCustomTermsOfService *Permission
+
+var PermissionSysconsoleReadExperimental *Permission
+var PermissionSysconsoleWriteExperimental *Permission
+
+var PermissionSysconsoleReadExperimentalFeatures *Permission
+var PermissionSysconsoleWriteExperimentalFeatures *Permission
+
+var PermissionSysconsoleReadExperimentalFeatureFlags *Permission
+var PermissionSysconsoleWriteExperimentalFeatureFlags *Permission
+
+var PermissionSysconsoleReadExperimentalBleve *Permission
+var PermissionSysconsoleWriteExperimentalBleve *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 PermissionManageSystem *Permission
+
+var AllPermissions []*Permission
+var DeprecatedPermissions []*Permission
+
+var ChannelModeratedPermissions []string
+var ChannelModeratedPermissionsMap map[string]string
+
+var SysconsoleReadPermissions []*Permission
+var SysconsoleWritePermissions []*Permission
+
+func initializePermissions() {
+ PermissionInviteUser = &Permission{
+ "invite_user",
+ "authentication.permissions.team_invite_user.name",
+ "authentication.permissions.team_invite_user.description",
+ PermissionScopeTeam,
+ }
+ PermissionAddUserToTeam = &Permission{
+ "add_user_to_team",
+ "authentication.permissions.add_user_to_team.name",
+ "authentication.permissions.add_user_to_team.description",
+ PermissionScopeTeam,
+ }
+ PermissionUseSlashCommands = &Permission{
+ "use_slash_commands",
+ "authentication.permissions.team_use_slash_commands.name",
+ "authentication.permissions.team_use_slash_commands.description",
+ PermissionScopeChannel,
+ }
+ PermissionManageSlashCommands = &Permission{
+ "manage_slash_commands",
+ "authentication.permissions.manage_slash_commands.name",
+ "authentication.permissions.manage_slash_commands.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageOthersSlashCommands = &Permission{
+ "manage_others_slash_commands",
+ "authentication.permissions.manage_others_slash_commands.name",
+ "authentication.permissions.manage_others_slash_commands.description",
+ PermissionScopeTeam,
+ }
+ PermissionCreatePublicChannel = &Permission{
+ "create_public_channel",
+ "authentication.permissions.create_public_channel.name",
+ "authentication.permissions.create_public_channel.description",
+ PermissionScopeTeam,
+ }
+ PermissionCreatePrivateChannel = &Permission{
+ "create_private_channel",
+ "authentication.permissions.create_private_channel.name",
+ "authentication.permissions.create_private_channel.description",
+ PermissionScopeTeam,
+ }
+ PermissionManagePublicChannelMembers = &Permission{
+ "manage_public_channel_members",
+ "authentication.permissions.manage_public_channel_members.name",
+ "authentication.permissions.manage_public_channel_members.description",
+ PermissionScopeChannel,
+ }
+ PermissionManagePrivateChannelMembers = &Permission{
+ "manage_private_channel_members",
+ "authentication.permissions.manage_private_channel_members.name",
+ "authentication.permissions.manage_private_channel_members.description",
+ PermissionScopeChannel,
+ }
+ PermissionConvertPublicChannelToPrivate = &Permission{
+ "convert_public_channel_to_private",
+ "authentication.permissions.convert_public_channel_to_private.name",
+ "authentication.permissions.convert_public_channel_to_private.description",
+ PermissionScopeChannel,
+ }
+ PermissionConvertPrivateChannelToPublic = &Permission{
+ "convert_private_channel_to_public",
+ "authentication.permissions.convert_private_channel_to_public.name",
+ "authentication.permissions.convert_private_channel_to_public.description",
+ PermissionScopeChannel,
+ }
+ PermissionAssignSystemAdminRole = &Permission{
+ "assign_system_admin_role",
+ "authentication.permissions.assign_system_admin_role.name",
+ "authentication.permissions.assign_system_admin_role.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageRoles = &Permission{
+ "manage_roles",
+ "authentication.permissions.manage_roles.name",
+ "authentication.permissions.manage_roles.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageTeamRoles = &Permission{
+ "manage_team_roles",
+ "authentication.permissions.manage_team_roles.name",
+ "authentication.permissions.manage_team_roles.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageChannelRoles = &Permission{
+ "manage_channel_roles",
+ "authentication.permissions.manage_channel_roles.name",
+ "authentication.permissions.manage_channel_roles.description",
+ PermissionScopeChannel,
+ }
+ PermissionManageSystem = &Permission{
+ "manage_system",
+ "authentication.permissions.manage_system.name",
+ "authentication.permissions.manage_system.description",
+ PermissionScopeSystem,
+ }
+ PermissionCreateDirectChannel = &Permission{
+ "create_direct_channel",
+ "authentication.permissions.create_direct_channel.name",
+ "authentication.permissions.create_direct_channel.description",
+ PermissionScopeSystem,
+ }
+ PermissionCreateGroupChannel = &Permission{
+ "create_group_channel",
+ "authentication.permissions.create_group_channel.name",
+ "authentication.permissions.create_group_channel.description",
+ PermissionScopeSystem,
+ }
+ PermissionManagePublicChannelProperties = &Permission{
+ "manage_public_channel_properties",
+ "authentication.permissions.manage_public_channel_properties.name",
+ "authentication.permissions.manage_public_channel_properties.description",
+ PermissionScopeChannel,
+ }
+ PermissionManagePrivateChannelProperties = &Permission{
+ "manage_private_channel_properties",
+ "authentication.permissions.manage_private_channel_properties.name",
+ "authentication.permissions.manage_private_channel_properties.description",
+ PermissionScopeChannel,
+ }
+ PermissionListPublicTeams = &Permission{
+ "list_public_teams",
+ "authentication.permissions.list_public_teams.name",
+ "authentication.permissions.list_public_teams.description",
+ PermissionScopeSystem,
+ }
+ PermissionJoinPublicTeams = &Permission{
+ "join_public_teams",
+ "authentication.permissions.join_public_teams.name",
+ "authentication.permissions.join_public_teams.description",
+ PermissionScopeSystem,
+ }
+ PermissionListPrivateTeams = &Permission{
+ "list_private_teams",
+ "authentication.permissions.list_private_teams.name",
+ "authentication.permissions.list_private_teams.description",
+ PermissionScopeSystem,
+ }
+ PermissionJoinPrivateTeams = &Permission{
+ "join_private_teams",
+ "authentication.permissions.join_private_teams.name",
+ "authentication.permissions.join_private_teams.description",
+ PermissionScopeSystem,
+ }
+ PermissionListTeamChannels = &Permission{
+ "list_team_channels",
+ "authentication.permissions.list_team_channels.name",
+ "authentication.permissions.list_team_channels.description",
+ PermissionScopeTeam,
+ }
+ PermissionJoinPublicChannels = &Permission{
+ "join_public_channels",
+ "authentication.permissions.join_public_channels.name",
+ "authentication.permissions.join_public_channels.description",
+ PermissionScopeTeam,
+ }
+ PermissionDeletePublicChannel = &Permission{
+ "delete_public_channel",
+ "authentication.permissions.delete_public_channel.name",
+ "authentication.permissions.delete_public_channel.description",
+ PermissionScopeChannel,
+ }
+ PermissionDeletePrivateChannel = &Permission{
+ "delete_private_channel",
+ "authentication.permissions.delete_private_channel.name",
+ "authentication.permissions.delete_private_channel.description",
+ PermissionScopeChannel,
+ }
+ PermissionEditOtherUsers = &Permission{
+ "edit_other_users",
+ "authentication.permissions.edit_other_users.name",
+ "authentication.permissions.edit_other_users.description",
+ PermissionScopeSystem,
+ }
+ PermissionReadChannel = &Permission{
+ "read_channel",
+ "authentication.permissions.read_channel.name",
+ "authentication.permissions.read_channel.description",
+ PermissionScopeChannel,
+ }
+ PermissionReadPublicChannelGroups = &Permission{
+ "read_public_channel_groups",
+ "authentication.permissions.read_public_channel_groups.name",
+ "authentication.permissions.read_public_channel_groups.description",
+ PermissionScopeChannel,
+ }
+ PermissionReadPrivateChannelGroups = &Permission{
+ "read_private_channel_groups",
+ "authentication.permissions.read_private_channel_groups.name",
+ "authentication.permissions.read_private_channel_groups.description",
+ PermissionScopeChannel,
+ }
+ PermissionReadPublicChannel = &Permission{
+ "read_public_channel",
+ "authentication.permissions.read_public_channel.name",
+ "authentication.permissions.read_public_channel.description",
+ PermissionScopeTeam,
+ }
+ PermissionAddReaction = &Permission{
+ "add_reaction",
+ "authentication.permissions.add_reaction.name",
+ "authentication.permissions.add_reaction.description",
+ PermissionScopeChannel,
+ }
+ PermissionRemoveReaction = &Permission{
+ "remove_reaction",
+ "authentication.permissions.remove_reaction.name",
+ "authentication.permissions.remove_reaction.description",
+ PermissionScopeChannel,
+ }
+ PermissionRemoveOthersReactions = &Permission{
+ "remove_others_reactions",
+ "authentication.permissions.remove_others_reactions.name",
+ "authentication.permissions.remove_others_reactions.description",
+ PermissionScopeChannel,
+ }
+ // DEPRECATED
+ PermissionPermanentDeleteUser = &Permission{
+ "permanent_delete_user",
+ "authentication.permissions.permanent_delete_user.name",
+ "authentication.permissions.permanent_delete_user.description",
+ PermissionScopeSystem,
+ }
+ PermissionUploadFile = &Permission{
+ "upload_file",
+ "authentication.permissions.upload_file.name",
+ "authentication.permissions.upload_file.description",
+ PermissionScopeChannel,
+ }
+ PermissionGetPublicLink = &Permission{
+ "get_public_link",
+ "authentication.permissions.get_public_link.name",
+ "authentication.permissions.get_public_link.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionManageWebhooks = &Permission{
+ "manage_webhooks",
+ "authentication.permissions.manage_webhooks.name",
+ "authentication.permissions.manage_webhooks.description",
+ PermissionScopeTeam,
+ }
+ // DEPRECATED
+ PermissionManageOthersWebhooks = &Permission{
+ "manage_others_webhooks",
+ "authentication.permissions.manage_others_webhooks.name",
+ "authentication.permissions.manage_others_webhooks.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageIncomingWebhooks = &Permission{
+ "manage_incoming_webhooks",
+ "authentication.permissions.manage_incoming_webhooks.name",
+ "authentication.permissions.manage_incoming_webhooks.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageOutgoingWebhooks = &Permission{
+ "manage_outgoing_webhooks",
+ "authentication.permissions.manage_outgoing_webhooks.name",
+ "authentication.permissions.manage_outgoing_webhooks.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageOthersIncomingWebhooks = &Permission{
+ "manage_others_incoming_webhooks",
+ "authentication.permissions.manage_others_incoming_webhooks.name",
+ "authentication.permissions.manage_others_incoming_webhooks.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageOthersOutgoingWebhooks = &Permission{
+ "manage_others_outgoing_webhooks",
+ "authentication.permissions.manage_others_outgoing_webhooks.name",
+ "authentication.permissions.manage_others_outgoing_webhooks.description",
+ PermissionScopeTeam,
+ }
+ PermissionManageOAuth = &Permission{
+ "manage_oauth",
+ "authentication.permissions.manage_oauth.name",
+ "authentication.permissions.manage_oauth.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageSystemWideOAuth = &Permission{
+ "manage_system_wide_oauth",
+ "authentication.permissions.manage_system_wide_oauth.name",
+ "authentication.permissions.manage_system_wide_oauth.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionManageEmojis = &Permission{
+ "manage_emojis",
+ "authentication.permissions.manage_emojis.name",
+ "authentication.permissions.manage_emojis.description",
+ PermissionScopeTeam,
+ }
+ // DEPRECATED
+ PermissionManageOthersEmojis = &Permission{
+ "manage_others_emojis",
+ "authentication.permissions.manage_others_emojis.name",
+ "authentication.permissions.manage_others_emojis.description",
+ PermissionScopeTeam,
+ }
+ PermissionCreateEmojis = &Permission{
+ "create_emojis",
+ "authentication.permissions.create_emojis.name",
+ "authentication.permissions.create_emojis.description",
+ PermissionScopeTeam,
+ }
+ PermissionDeleteEmojis = &Permission{
+ "delete_emojis",
+ "authentication.permissions.delete_emojis.name",
+ "authentication.permissions.delete_emojis.description",
+ PermissionScopeTeam,
+ }
+ PermissionDeleteOthersEmojis = &Permission{
+ "delete_others_emojis",
+ "authentication.permissions.delete_others_emojis.name",
+ "authentication.permissions.delete_others_emojis.description",
+ PermissionScopeTeam,
+ }
+ PermissionCreatePost = &Permission{
+ "create_post",
+ "authentication.permissions.create_post.name",
+ "authentication.permissions.create_post.description",
+ PermissionScopeChannel,
+ }
+ PermissionCreatePostPublic = &Permission{
+ "create_post_public",
+ "authentication.permissions.create_post_public.name",
+ "authentication.permissions.create_post_public.description",
+ PermissionScopeChannel,
+ }
+ PermissionCreatePostEphemeral = &Permission{
+ "create_post_ephemeral",
+ "authentication.permissions.create_post_ephemeral.name",
+ "authentication.permissions.create_post_ephemeral.description",
+ PermissionScopeChannel,
+ }
+ PermissionEditPost = &Permission{
+ "edit_post",
+ "authentication.permissions.edit_post.name",
+ "authentication.permissions.edit_post.description",
+ PermissionScopeChannel,
+ }
+ PermissionEditOthersPosts = &Permission{
+ "edit_others_posts",
+ "authentication.permissions.edit_others_posts.name",
+ "authentication.permissions.edit_others_posts.description",
+ PermissionScopeChannel,
+ }
+ PermissionDeletePost = &Permission{
+ "delete_post",
+ "authentication.permissions.delete_post.name",
+ "authentication.permissions.delete_post.description",
+ PermissionScopeChannel,
+ }
+ PermissionDeleteOthersPosts = &Permission{
+ "delete_others_posts",
+ "authentication.permissions.delete_others_posts.name",
+ "authentication.permissions.delete_others_posts.description",
+ PermissionScopeChannel,
+ }
+ PermissionManageSharedChannels = &Permission{
+ "manage_shared_channels",
+ "authentication.permissions.manage_shared_channels.name",
+ "authentication.permissions.manage_shared_channels.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageSecureConnections = &Permission{
+ "manage_secure_connections",
+ "authentication.permissions.manage_secure_connections.name",
+ "authentication.permissions.manage_secure_connections.description",
+ PermissionScopeSystem,
+ }
+
+ PermissionCreateDataRetentionJob = &Permission{
+ "create_data_retention_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionReadDataRetentionJob = &Permission{
+ "read_data_retention_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionCreateComplianceExportJob = &Permission{
+ "create_compliance_export_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionReadComplianceExportJob = &Permission{
+ "read_compliance_export_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionReadAudits = &Permission{
+ "read_audits",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionPurgeBleveIndexes = &Permission{
+ "purge_bleve_indexes",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionCreatePostBleveIndexesJob = &Permission{
+ "create_post_bleve_indexes_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionCreateLdapSyncJob = &Permission{
+ "create_ldap_sync_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionReadLdapSyncJob = &Permission{
+ "read_ldap_sync_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionTestLdap = &Permission{
+ "test_ldap",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionInvalidateEmailInvite = &Permission{
+ "invalidate_email_invite",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionGetSamlMetadataFromIdp = &Permission{
+ "get_saml_metadata_from_idp",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionAddSamlPublicCert = &Permission{
+ "add_saml_public_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionAddSamlPrivateCert = &Permission{
+ "add_saml_private_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionAddSamlIdpCert = &Permission{
+ "add_saml_idp_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionRemoveSamlPublicCert = &Permission{
+ "remove_saml_public_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionRemoveSamlPrivateCert = &Permission{
+ "remove_saml_private_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionRemoveSamlIdpCert = &Permission{
+ "remove_saml_idp_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionGetSamlCertStatus = &Permission{
+ "get_saml_cert_status",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionAddLdapPublicCert = &Permission{
+ "add_ldap_public_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionAddLdapPrivateCert = &Permission{
+ "add_ldap_private_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionRemoveLdapPublicCert = &Permission{
+ "remove_ldap_public_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionRemoveLdapPrivateCert = &Permission{
+ "remove_ldap_private_cert",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionGetLogs = &Permission{
+ "get_logs",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionReadLicenseInformation = &Permission{
+ "read_license_information",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionGetAnalytics = &Permission{
+ "get_analytics",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionManageLicenseInformation = &Permission{
+ "manage_license_information",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionDownloadComplianceExportResult = &Permission{
+ "download_compliance_export_result",
+ "authentication.permissions.download_compliance_export_result.name",
+ "authentication.permissions.download_compliance_export_result.description",
+ PermissionScopeSystem,
+ }
+
+ PermissionTestSiteURL = &Permission{
+ "test_site_url",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionTestElasticsearch = &Permission{
+ "test_elasticsearch",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionTestS3 = &Permission{
+ "test_s3",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionReloadConfig = &Permission{
+ "reload_config",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionInvalidateCaches = &Permission{
+ "invalidate_caches",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionRecycleDatabaseConnections = &Permission{
+ "recycle_database_connections",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionPurgeElasticsearchIndexes = &Permission{
+ "purge_elasticsearch_indexes",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionTestEmail = &Permission{
+ "test_email",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionCreateElasticsearchPostIndexingJob = &Permission{
+ "create_elasticsearch_post_indexing_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionCreateElasticsearchPostAggregationJob = &Permission{
+ "create_elasticsearch_post_aggregation_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionReadElasticsearchPostIndexingJob = &Permission{
+ "read_elasticsearch_post_indexing_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionReadElasticsearchPostAggregationJob = &Permission{
+ "read_elasticsearch_post_aggregation_job",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ PermissionRemoveUserFromTeam = &Permission{
+ "remove_user_from_team",
+ "authentication.permissions.remove_user_from_team.name",
+ "authentication.permissions.remove_user_from_team.description",
+ PermissionScopeTeam,
+ }
+ PermissionCreateTeam = &Permission{
+ "create_team",
+ "authentication.permissions.create_team.name",
+ "authentication.permissions.create_team.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageTeam = &Permission{
+ "manage_team",
+ "authentication.permissions.manage_team.name",
+ "authentication.permissions.manage_team.description",
+ PermissionScopeTeam,
+ }
+ PermissionImportTeam = &Permission{
+ "import_team",
+ "authentication.permissions.import_team.name",
+ "authentication.permissions.import_team.description",
+ PermissionScopeTeam,
+ }
+ PermissionViewTeam = &Permission{
+ "view_team",
+ "authentication.permissions.view_team.name",
+ "authentication.permissions.view_team.description",
+ PermissionScopeTeam,
+ }
+ PermissionListUsersWithoutTeam = &Permission{
+ "list_users_without_team",
+ "authentication.permissions.list_users_without_team.name",
+ "authentication.permissions.list_users_without_team.description",
+ PermissionScopeSystem,
+ }
+ PermissionCreateUserAccessToken = &Permission{
+ "create_user_access_token",
+ "authentication.permissions.create_user_access_token.name",
+ "authentication.permissions.create_user_access_token.description",
+ PermissionScopeSystem,
+ }
+ PermissionReadUserAccessToken = &Permission{
+ "read_user_access_token",
+ "authentication.permissions.read_user_access_token.name",
+ "authentication.permissions.read_user_access_token.description",
+ PermissionScopeSystem,
+ }
+ PermissionRevokeUserAccessToken = &Permission{
+ "revoke_user_access_token",
+ "authentication.permissions.revoke_user_access_token.name",
+ "authentication.permissions.revoke_user_access_token.description",
+ PermissionScopeSystem,
+ }
+ PermissionCreateBot = &Permission{
+ "create_bot",
+ "authentication.permissions.create_bot.name",
+ "authentication.permissions.create_bot.description",
+ PermissionScopeSystem,
+ }
+ PermissionAssignBot = &Permission{
+ "assign_bot",
+ "authentication.permissions.assign_bot.name",
+ "authentication.permissions.assign_bot.description",
+ PermissionScopeSystem,
+ }
+ PermissionReadBots = &Permission{
+ "read_bots",
+ "authentication.permissions.read_bots.name",
+ "authentication.permissions.read_bots.description",
+ PermissionScopeSystem,
+ }
+ PermissionReadOthersBots = &Permission{
+ "read_others_bots",
+ "authentication.permissions.read_others_bots.name",
+ "authentication.permissions.read_others_bots.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageBots = &Permission{
+ "manage_bots",
+ "authentication.permissions.manage_bots.name",
+ "authentication.permissions.manage_bots.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageOthersBots = &Permission{
+ "manage_others_bots",
+ "authentication.permissions.manage_others_bots.name",
+ "authentication.permissions.manage_others_bots.description",
+ PermissionScopeSystem,
+ }
+ PermissionReadJobs = &Permission{
+ "read_jobs",
+ "authentication.permisssions.read_jobs.name",
+ "authentication.permisssions.read_jobs.description",
+ PermissionScopeSystem,
+ }
+ PermissionManageJobs = &Permission{
+ "manage_jobs",
+ "authentication.permisssions.manage_jobs.name",
+ "authentication.permisssions.manage_jobs.description",
+ PermissionScopeSystem,
+ }
+ PermissionViewMembers = &Permission{
+ "view_members",
+ "authentication.permisssions.view_members.name",
+ "authentication.permisssions.view_members.description",
+ PermissionScopeTeam,
+ }
+ PermissionInviteGuest = &Permission{
+ "invite_guest",
+ "authentication.permissions.invite_guest.name",
+ "authentication.permissions.invite_guest.description",
+ PermissionScopeTeam,
+ }
+ PermissionPromoteGuest = &Permission{
+ "promote_guest",
+ "authentication.permissions.promote_guest.name",
+ "authentication.permissions.promote_guest.description",
+ PermissionScopeSystem,
+ }
+ PermissionDemoteToGuest = &Permission{
+ "demote_to_guest",
+ "authentication.permissions.demote_to_guest.name",
+ "authentication.permissions.demote_to_guest.description",
+ PermissionScopeSystem,
+ }
+ PermissionUseChannelMentions = &Permission{
+ "use_channel_mentions",
+ "authentication.permissions.use_channel_mentions.name",
+ "authentication.permissions.use_channel_mentions.description",
+ PermissionScopeChannel,
+ }
+ PermissionUseGroupMentions = &Permission{
+ "use_group_mentions",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeChannel,
+ }
+ PermissionReadOtherUsersTeams = &Permission{
+ "read_other_users_teams",
+ "authentication.permissions.read_other_users_teams.name",
+ "authentication.permissions.read_other_users_teams.description",
+ PermissionScopeSystem,
+ }
+ PermissionEditBrand = &Permission{
+ "edit_brand",
+ "authentication.permissions.edit_brand.name",
+ "authentication.permissions.edit_brand.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadAbout = &Permission{
+ "sysconsole_read_about",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteAbout = &Permission{
+ "sysconsole_write_about",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAboutEditionAndLicense = &Permission{
+ "sysconsole_read_about_edition_and_license",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAboutEditionAndLicense = &Permission{
+ "sysconsole_write_about_edition_and_license",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadBilling = &Permission{
+ "sysconsole_read_billing",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteBilling = &Permission{
+ "sysconsole_write_billing",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadReporting = &Permission{
+ "sysconsole_read_reporting",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteReporting = &Permission{
+ "sysconsole_write_reporting",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadReportingSiteStatistics = &Permission{
+ "sysconsole_read_reporting_site_statistics",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteReportingSiteStatistics = &Permission{
+ "sysconsole_write_reporting_site_statistics",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadReportingTeamStatistics = &Permission{
+ "sysconsole_read_reporting_team_statistics",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteReportingTeamStatistics = &Permission{
+ "sysconsole_write_reporting_team_statistics",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadReportingServerLogs = &Permission{
+ "sysconsole_read_reporting_server_logs",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteReportingServerLogs = &Permission{
+ "sysconsole_write_reporting_server_logs",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadUserManagementUsers = &Permission{
+ "sysconsole_read_user_management_users",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteUserManagementUsers = &Permission{
+ "sysconsole_write_user_management_users",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadUserManagementGroups = &Permission{
+ "sysconsole_read_user_management_groups",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteUserManagementGroups = &Permission{
+ "sysconsole_write_user_management_groups",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadUserManagementTeams = &Permission{
+ "sysconsole_read_user_management_teams",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteUserManagementTeams = &Permission{
+ "sysconsole_write_user_management_teams",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadUserManagementChannels = &Permission{
+ "sysconsole_read_user_management_channels",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteUserManagementChannels = &Permission{
+ "sysconsole_write_user_management_channels",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadUserManagementPermissions = &Permission{
+ "sysconsole_read_user_management_permissions",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteUserManagementPermissions = &Permission{
+ "sysconsole_write_user_management_permissions",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadUserManagementSystemRoles = &Permission{
+ "sysconsole_read_user_management_system_roles",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteUserManagementSystemRoles = &Permission{
+ "sysconsole_write_user_management_system_roles",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadEnvironment = &Permission{
+ "sysconsole_read_environment",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteEnvironment = &Permission{
+ "sysconsole_write_environment",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentWebServer = &Permission{
+ "sysconsole_read_environment_web_server",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentWebServer = &Permission{
+ "sysconsole_write_environment_web_server",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentDatabase = &Permission{
+ "sysconsole_read_environment_database",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentDatabase = &Permission{
+ "sysconsole_write_environment_database",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentElasticsearch = &Permission{
+ "sysconsole_read_environment_elasticsearch",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentElasticsearch = &Permission{
+ "sysconsole_write_environment_elasticsearch",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentFileStorage = &Permission{
+ "sysconsole_read_environment_file_storage",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentFileStorage = &Permission{
+ "sysconsole_write_environment_file_storage",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentImageProxy = &Permission{
+ "sysconsole_read_environment_image_proxy",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentImageProxy = &Permission{
+ "sysconsole_write_environment_image_proxy",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentSMTP = &Permission{
+ "sysconsole_read_environment_smtp",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentSMTP = &Permission{
+ "sysconsole_write_environment_smtp",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentPushNotificationServer = &Permission{
+ "sysconsole_read_environment_push_notification_server",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentPushNotificationServer = &Permission{
+ "sysconsole_write_environment_push_notification_server",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentHighAvailability = &Permission{
+ "sysconsole_read_environment_high_availability",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentHighAvailability = &Permission{
+ "sysconsole_write_environment_high_availability",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentRateLimiting = &Permission{
+ "sysconsole_read_environment_rate_limiting",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentRateLimiting = &Permission{
+ "sysconsole_write_environment_rate_limiting",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentLogging = &Permission{
+ "sysconsole_read_environment_logging",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentLogging = &Permission{
+ "sysconsole_write_environment_logging",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentSessionLengths = &Permission{
+ "sysconsole_read_environment_session_lengths",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentSessionLengths = &Permission{
+ "sysconsole_write_environment_session_lengths",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentPerformanceMonitoring = &Permission{
+ "sysconsole_read_environment_performance_monitoring",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentPerformanceMonitoring = &Permission{
+ "sysconsole_write_environment_performance_monitoring",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadEnvironmentDeveloper = &Permission{
+ "sysconsole_read_environment_developer",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteEnvironmentDeveloper = &Permission{
+ "sysconsole_write_environment_developer",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadSite = &Permission{
+ "sysconsole_read_site",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteSite = &Permission{
+ "sysconsole_write_site",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+
+ PermissionSysconsoleReadSiteCustomization = &Permission{
+ "sysconsole_read_site_customization",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteCustomization = &Permission{
+ "sysconsole_write_site_customization",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteLocalization = &Permission{
+ "sysconsole_read_site_localization",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteLocalization = &Permission{
+ "sysconsole_write_site_localization",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteUsersAndTeams = &Permission{
+ "sysconsole_read_site_users_and_teams",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteUsersAndTeams = &Permission{
+ "sysconsole_write_site_users_and_teams",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteNotifications = &Permission{
+ "sysconsole_read_site_notifications",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteNotifications = &Permission{
+ "sysconsole_write_site_notifications",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteAnnouncementBanner = &Permission{
+ "sysconsole_read_site_announcement_banner",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteAnnouncementBanner = &Permission{
+ "sysconsole_write_site_announcement_banner",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteEmoji = &Permission{
+ "sysconsole_read_site_emoji",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteEmoji = &Permission{
+ "sysconsole_write_site_emoji",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSitePosts = &Permission{
+ "sysconsole_read_site_posts",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSitePosts = &Permission{
+ "sysconsole_write_site_posts",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteFileSharingAndDownloads = &Permission{
+ "sysconsole_read_site_file_sharing_and_downloads",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteFileSharingAndDownloads = &Permission{
+ "sysconsole_write_site_file_sharing_and_downloads",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSitePublicLinks = &Permission{
+ "sysconsole_read_site_public_links",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSitePublicLinks = &Permission{
+ "sysconsole_write_site_public_links",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadSiteNotices = &Permission{
+ "sysconsole_read_site_notices",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteSiteNotices = &Permission{
+ "sysconsole_write_site_notices",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ // Deprecated
+ PermissionSysconsoleReadAuthentication = &Permission{
+ "sysconsole_read_authentication",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // Deprecated
+ PermissionSysconsoleWriteAuthentication = &Permission{
+ "sysconsole_write_authentication",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationSignup = &Permission{
+ "sysconsole_read_authentication_signup",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationSignup = &Permission{
+ "sysconsole_write_authentication_signup",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationEmail = &Permission{
+ "sysconsole_read_authentication_email",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationEmail = &Permission{
+ "sysconsole_write_authentication_email",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationPassword = &Permission{
+ "sysconsole_read_authentication_password",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationPassword = &Permission{
+ "sysconsole_write_authentication_password",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationMfa = &Permission{
+ "sysconsole_read_authentication_mfa",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationMfa = &Permission{
+ "sysconsole_write_authentication_mfa",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationLdap = &Permission{
+ "sysconsole_read_authentication_ldap",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationLdap = &Permission{
+ "sysconsole_write_authentication_ldap",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationSaml = &Permission{
+ "sysconsole_read_authentication_saml",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationSaml = &Permission{
+ "sysconsole_write_authentication_saml",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationOpenid = &Permission{
+ "sysconsole_read_authentication_openid",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationOpenid = &Permission{
+ "sysconsole_write_authentication_openid",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadAuthenticationGuestAccess = &Permission{
+ "sysconsole_read_authentication_guest_access",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteAuthenticationGuestAccess = &Permission{
+ "sysconsole_write_authentication_guest_access",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadPlugins = &Permission{
+ "sysconsole_read_plugins",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWritePlugins = &Permission{
+ "sysconsole_write_plugins",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadIntegrations = &Permission{
+ "sysconsole_read_integrations",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteIntegrations = &Permission{
+ "sysconsole_write_integrations",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadIntegrationsIntegrationManagement = &Permission{
+ "sysconsole_read_integrations_integration_management",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteIntegrationsIntegrationManagement = &Permission{
+ "sysconsole_write_integrations_integration_management",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadIntegrationsBotAccounts = &Permission{
+ "sysconsole_read_integrations_bot_accounts",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteIntegrationsBotAccounts = &Permission{
+ "sysconsole_write_integrations_bot_accounts",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadIntegrationsGif = &Permission{
+ "sysconsole_read_integrations_gif",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteIntegrationsGif = &Permission{
+ "sysconsole_write_integrations_gif",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadIntegrationsCors = &Permission{
+ "sysconsole_read_integrations_cors",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteIntegrationsCors = &Permission{
+ "sysconsole_write_integrations_cors",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadCompliance = &Permission{
+ "sysconsole_read_compliance",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteCompliance = &Permission{
+ "sysconsole_write_compliance",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadComplianceDataRetentionPolicy = &Permission{
+ "sysconsole_read_compliance_data_retention_policy",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteComplianceDataRetentionPolicy = &Permission{
+ "sysconsole_write_compliance_data_retention_policy",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadComplianceComplianceExport = &Permission{
+ "sysconsole_read_compliance_compliance_export",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteComplianceComplianceExport = &Permission{
+ "sysconsole_write_compliance_compliance_export",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadComplianceComplianceMonitoring = &Permission{
+ "sysconsole_read_compliance_compliance_monitoring",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteComplianceComplianceMonitoring = &Permission{
+ "sysconsole_write_compliance_compliance_monitoring",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadComplianceCustomTermsOfService = &Permission{
+ "sysconsole_read_compliance_custom_terms_of_service",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteComplianceCustomTermsOfService = &Permission{
+ "sysconsole_write_compliance_custom_terms_of_service",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleReadExperimental = &Permission{
+ "sysconsole_read_experimental",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ // DEPRECATED
+ PermissionSysconsoleWriteExperimental = &Permission{
+ "sysconsole_write_experimental",
+ "authentication.permissions.use_group_mentions.name",
+ "authentication.permissions.use_group_mentions.description",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadExperimentalFeatures = &Permission{
+ "sysconsole_read_experimental_features",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteExperimentalFeatures = &Permission{
+ "sysconsole_write_experimental_features",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadExperimentalFeatureFlags = &Permission{
+ "sysconsole_read_experimental_feature_flags",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteExperimentalFeatureFlags = &Permission{
+ "sysconsole_write_experimental_feature_flags",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleReadExperimentalBleve = &Permission{
+ "sysconsole_read_experimental_bleve",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+ PermissionSysconsoleWriteExperimentalBleve = &Permission{
+ "sysconsole_write_experimental_bleve",
+ "",
+ "",
+ PermissionScopeSystem,
+ }
+
+ SysconsoleReadPermissions = []*Permission{
+ PermissionSysconsoleReadAboutEditionAndLicense,
+ PermissionSysconsoleReadBilling,
+ PermissionSysconsoleReadReportingSiteStatistics,
+ PermissionSysconsoleReadReportingTeamStatistics,
+ PermissionSysconsoleReadReportingServerLogs,
+ PermissionSysconsoleReadUserManagementUsers,
+ PermissionSysconsoleReadUserManagementGroups,
+ PermissionSysconsoleReadUserManagementTeams,
+ PermissionSysconsoleReadUserManagementChannels,
+ PermissionSysconsoleReadUserManagementPermissions,
+ PermissionSysconsoleReadUserManagementSystemRoles,
+ PermissionSysconsoleReadEnvironmentWebServer,
+ PermissionSysconsoleReadEnvironmentDatabase,
+ PermissionSysconsoleReadEnvironmentElasticsearch,
+ PermissionSysconsoleReadEnvironmentFileStorage,
+ PermissionSysconsoleReadEnvironmentImageProxy,
+ PermissionSysconsoleReadEnvironmentSMTP,
+ PermissionSysconsoleReadEnvironmentPushNotificationServer,
+ PermissionSysconsoleReadEnvironmentHighAvailability,
+ PermissionSysconsoleReadEnvironmentRateLimiting,
+ PermissionSysconsoleReadEnvironmentLogging,
+ PermissionSysconsoleReadEnvironmentSessionLengths,
+ PermissionSysconsoleReadEnvironmentPerformanceMonitoring,
+ PermissionSysconsoleReadEnvironmentDeveloper,
+ PermissionSysconsoleReadSiteCustomization,
+ PermissionSysconsoleReadSiteLocalization,
+ PermissionSysconsoleReadSiteUsersAndTeams,
+ PermissionSysconsoleReadSiteNotifications,
+ PermissionSysconsoleReadSiteAnnouncementBanner,
+ PermissionSysconsoleReadSiteEmoji,
+ PermissionSysconsoleReadSitePosts,
+ PermissionSysconsoleReadSiteFileSharingAndDownloads,
+ PermissionSysconsoleReadSitePublicLinks,
+ PermissionSysconsoleReadSiteNotices,
+ PermissionSysconsoleReadAuthenticationSignup,
+ PermissionSysconsoleReadAuthenticationEmail,
+ PermissionSysconsoleReadAuthenticationPassword,
+ PermissionSysconsoleReadAuthenticationMfa,
+ PermissionSysconsoleReadAuthenticationLdap,
+ PermissionSysconsoleReadAuthenticationSaml,
+ PermissionSysconsoleReadAuthenticationOpenid,
+ PermissionSysconsoleReadAuthenticationGuestAccess,
+ PermissionSysconsoleReadPlugins,
+ PermissionSysconsoleReadIntegrationsIntegrationManagement,
+ PermissionSysconsoleReadIntegrationsBotAccounts,
+ PermissionSysconsoleReadIntegrationsGif,
+ PermissionSysconsoleReadIntegrationsCors,
+ PermissionSysconsoleReadComplianceDataRetentionPolicy,
+ PermissionSysconsoleReadComplianceComplianceExport,
+ PermissionSysconsoleReadComplianceComplianceMonitoring,
+ PermissionSysconsoleReadComplianceCustomTermsOfService,
+ PermissionSysconsoleReadExperimentalFeatures,
+ PermissionSysconsoleReadExperimentalFeatureFlags,
+ PermissionSysconsoleReadExperimentalBleve,
+ }
+
+ SysconsoleWritePermissions = []*Permission{
+ PermissionSysconsoleWriteAboutEditionAndLicense,
+ PermissionSysconsoleWriteBilling,
+ PermissionSysconsoleWriteReportingSiteStatistics,
+ PermissionSysconsoleWriteReportingTeamStatistics,
+ PermissionSysconsoleWriteReportingServerLogs,
+ PermissionSysconsoleWriteUserManagementUsers,
+ PermissionSysconsoleWriteUserManagementGroups,
+ PermissionSysconsoleWriteUserManagementTeams,
+ PermissionSysconsoleWriteUserManagementChannels,
+ PermissionSysconsoleWriteUserManagementPermissions,
+ PermissionSysconsoleWriteUserManagementSystemRoles,
+ PermissionSysconsoleWriteEnvironmentWebServer,
+ PermissionSysconsoleWriteEnvironmentDatabase,
+ PermissionSysconsoleWriteEnvironmentElasticsearch,
+ PermissionSysconsoleWriteEnvironmentFileStorage,
+ PermissionSysconsoleWriteEnvironmentImageProxy,
+ PermissionSysconsoleWriteEnvironmentSMTP,
+ PermissionSysconsoleWriteEnvironmentPushNotificationServer,
+ PermissionSysconsoleWriteEnvironmentHighAvailability,
+ PermissionSysconsoleWriteEnvironmentRateLimiting,
+ PermissionSysconsoleWriteEnvironmentLogging,
+ PermissionSysconsoleWriteEnvironmentSessionLengths,
+ PermissionSysconsoleWriteEnvironmentPerformanceMonitoring,
+ PermissionSysconsoleWriteEnvironmentDeveloper,
+ PermissionSysconsoleWriteSiteCustomization,
+ PermissionSysconsoleWriteSiteLocalization,
+ PermissionSysconsoleWriteSiteUsersAndTeams,
+ PermissionSysconsoleWriteSiteNotifications,
+ PermissionSysconsoleWriteSiteAnnouncementBanner,
+ PermissionSysconsoleWriteSiteEmoji,
+ PermissionSysconsoleWriteSitePosts,
+ PermissionSysconsoleWriteSiteFileSharingAndDownloads,
+ PermissionSysconsoleWriteSitePublicLinks,
+ PermissionSysconsoleWriteSiteNotices,
+ PermissionSysconsoleWriteAuthenticationSignup,
+ PermissionSysconsoleWriteAuthenticationEmail,
+ PermissionSysconsoleWriteAuthenticationPassword,
+ PermissionSysconsoleWriteAuthenticationMfa,
+ PermissionSysconsoleWriteAuthenticationLdap,
+ PermissionSysconsoleWriteAuthenticationSaml,
+ PermissionSysconsoleWriteAuthenticationOpenid,
+ PermissionSysconsoleWriteAuthenticationGuestAccess,
+ PermissionSysconsoleWritePlugins,
+ PermissionSysconsoleWriteIntegrationsIntegrationManagement,
+ PermissionSysconsoleWriteIntegrationsBotAccounts,
+ PermissionSysconsoleWriteIntegrationsGif,
+ PermissionSysconsoleWriteIntegrationsCors,
+ PermissionSysconsoleWriteComplianceDataRetentionPolicy,
+ PermissionSysconsoleWriteComplianceComplianceExport,
+ PermissionSysconsoleWriteComplianceComplianceMonitoring,
+ PermissionSysconsoleWriteComplianceCustomTermsOfService,
+ PermissionSysconsoleWriteExperimentalFeatures,
+ PermissionSysconsoleWriteExperimentalFeatureFlags,
+ PermissionSysconsoleWriteExperimentalBleve,
+ }
+
+ SystemScopedPermissionsMinusSysconsole := []*Permission{
+ PermissionAssignSystemAdminRole,
+ PermissionManageRoles,
+ PermissionManageSystem,
+ PermissionCreateDirectChannel,
+ PermissionCreateGroupChannel,
+ PermissionListPublicTeams,
+ PermissionJoinPublicTeams,
+ PermissionListPrivateTeams,
+ PermissionJoinPrivateTeams,
+ PermissionEditOtherUsers,
+ PermissionReadOtherUsersTeams,
+ PermissionGetPublicLink,
+ PermissionManageOAuth,
+ PermissionManageSystemWideOAuth,
+ PermissionCreateTeam,
+ PermissionListUsersWithoutTeam,
+ PermissionCreateUserAccessToken,
+ PermissionReadUserAccessToken,
+ PermissionRevokeUserAccessToken,
+ PermissionCreateBot,
+ PermissionAssignBot,
+ PermissionReadBots,
+ PermissionReadOthersBots,
+ PermissionManageBots,
+ PermissionManageOthersBots,
+ PermissionReadJobs,
+ PermissionManageJobs,
+ PermissionPromoteGuest,
+ PermissionDemoteToGuest,
+ PermissionEditBrand,
+ PermissionManageSharedChannels,
+ PermissionManageSecureConnections,
+ PermissionDownloadComplianceExportResult,
+ PermissionCreateDataRetentionJob,
+ PermissionReadDataRetentionJob,
+ PermissionCreateComplianceExportJob,
+ PermissionReadComplianceExportJob,
+ PermissionReadAudits,
+ PermissionTestSiteURL,
+ PermissionTestElasticsearch,
+ PermissionTestS3,
+ PermissionReloadConfig,
+ PermissionInvalidateCaches,
+ PermissionRecycleDatabaseConnections,
+ PermissionPurgeElasticsearchIndexes,
+ PermissionTestEmail,
+ PermissionCreateElasticsearchPostIndexingJob,
+ PermissionCreateElasticsearchPostAggregationJob,
+ PermissionReadElasticsearchPostIndexingJob,
+ PermissionReadElasticsearchPostAggregationJob,
+ PermissionPurgeBleveIndexes,
+ PermissionCreatePostBleveIndexesJob,
+ PermissionCreateLdapSyncJob,
+ PermissionReadLdapSyncJob,
+ PermissionTestLdap,
+ PermissionInvalidateEmailInvite,
+ PermissionGetSamlMetadataFromIdp,
+ PermissionAddSamlPublicCert,
+ PermissionAddSamlPrivateCert,
+ PermissionAddSamlIdpCert,
+ PermissionRemoveSamlPublicCert,
+ PermissionRemoveSamlPrivateCert,
+ PermissionRemoveSamlIdpCert,
+ PermissionGetSamlCertStatus,
+ PermissionAddLdapPublicCert,
+ PermissionAddLdapPrivateCert,
+ PermissionRemoveLdapPublicCert,
+ PermissionRemoveLdapPrivateCert,
+ PermissionGetAnalytics,
+ PermissionGetLogs,
+ PermissionReadLicenseInformation,
+ PermissionManageLicenseInformation,
+ }
+
+ TeamScopedPermissions := []*Permission{
+ PermissionInviteUser,
+ PermissionAddUserToTeam,
+ PermissionManageSlashCommands,
+ PermissionManageOthersSlashCommands,
+ PermissionCreatePublicChannel,
+ PermissionCreatePrivateChannel,
+ PermissionManageTeamRoles,
+ PermissionListTeamChannels,
+ PermissionJoinPublicChannels,
+ PermissionReadPublicChannel,
+ PermissionManageIncomingWebhooks,
+ PermissionManageOutgoingWebhooks,
+ PermissionManageOthersIncomingWebhooks,
+ PermissionManageOthersOutgoingWebhooks,
+ PermissionCreateEmojis,
+ PermissionDeleteEmojis,
+ PermissionDeleteOthersEmojis,
+ PermissionRemoveUserFromTeam,
+ PermissionManageTeam,
+ PermissionImportTeam,
+ PermissionViewTeam,
+ PermissionViewMembers,
+ PermissionInviteGuest,
+ }
+
+ ChannelScopedPermissions := []*Permission{
+ PermissionUseSlashCommands,
+ PermissionManagePublicChannelMembers,
+ PermissionManagePrivateChannelMembers,
+ PermissionManageChannelRoles,
+ PermissionManagePublicChannelProperties,
+ PermissionManagePrivateChannelProperties,
+ PermissionConvertPublicChannelToPrivate,
+ PermissionConvertPrivateChannelToPublic,
+ PermissionDeletePublicChannel,
+ PermissionDeletePrivateChannel,
+ PermissionReadChannel,
+ PermissionReadPublicChannelGroups,
+ PermissionReadPrivateChannelGroups,
+ PermissionAddReaction,
+ PermissionRemoveReaction,
+ PermissionRemoveOthersReactions,
+ PermissionUploadFile,
+ PermissionCreatePost,
+ PermissionCreatePostPublic,
+ PermissionCreatePostEphemeral,
+ PermissionEditPost,
+ PermissionEditOthersPosts,
+ PermissionDeletePost,
+ PermissionDeleteOthersPosts,
+ PermissionUseChannelMentions,
+ PermissionUseGroupMentions,
+ }
+
+ DeprecatedPermissions = []*Permission{
+ PermissionPermanentDeleteUser,
+ PermissionManageWebhooks,
+ PermissionManageOthersWebhooks,
+ PermissionManageEmojis,
+ PermissionManageOthersEmojis,
+ PermissionSysconsoleReadAuthentication,
+ PermissionSysconsoleWriteAuthentication,
+ PermissionSysconsoleReadSite,
+ PermissionSysconsoleWriteSite,
+ PermissionSysconsoleReadEnvironment,
+ PermissionSysconsoleWriteEnvironment,
+ PermissionSysconsoleReadReporting,
+ PermissionSysconsoleWriteReporting,
+ PermissionSysconsoleReadAbout,
+ PermissionSysconsoleWriteAbout,
+ PermissionSysconsoleReadExperimental,
+ PermissionSysconsoleWriteExperimental,
+ PermissionSysconsoleReadIntegrations,
+ PermissionSysconsoleWriteIntegrations,
+ PermissionSysconsoleReadCompliance,
+ PermissionSysconsoleWriteCompliance,
+ }
+
+ AllPermissions = []*Permission{}
+ AllPermissions = append(AllPermissions, SystemScopedPermissionsMinusSysconsole...)
+ AllPermissions = append(AllPermissions, TeamScopedPermissions...)
+ AllPermissions = append(AllPermissions, ChannelScopedPermissions...)
+ AllPermissions = append(AllPermissions, SysconsoleReadPermissions...)
+ AllPermissions = append(AllPermissions, SysconsoleWritePermissions...)
+
+ ChannelModeratedPermissions = []string{
+ PermissionCreatePost.Id,
+ "create_reactions",
+ "manage_members",
+ PermissionUseChannelMentions.Id,
+ }
+
+ ChannelModeratedPermissionsMap = map[string]string{
+ PermissionCreatePost.Id: ChannelModeratedPermissions[0],
+ PermissionAddReaction.Id: ChannelModeratedPermissions[1],
+ PermissionRemoveReaction.Id: ChannelModeratedPermissions[1],
+ PermissionManagePublicChannelMembers.Id: ChannelModeratedPermissions[2],
+ PermissionManagePrivateChannelMembers.Id: ChannelModeratedPermissions[2],
+ PermissionUseChannelMentions.Id: ChannelModeratedPermissions[3],
+ }
+}
+
+func init() {
+ initializePermissions()
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_cluster_event.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_cluster_event.go
new file mode 100644
index 00000000..9e227447
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_cluster_event.go
@@ -0,0 +1,28 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ PluginClusterEventSendTypeReliable = ClusterSendReliable
+ PluginClusterEventSendTypeBestEffort = ClusterSendBestEffort
+)
+
+// PluginClusterEvent is used to allow intra-cluster plugin communication.
+type PluginClusterEvent struct {
+ // Id is the unique identifier for the event.
+ Id string
+ // Data is the event payload.
+ Data []byte
+}
+
+// PluginClusterEventSendOptions defines some properties that apply when sending
+// plugin events across a cluster.
+type PluginClusterEventSendOptions struct {
+ // SendType defines the type of communication channel used to send the event.
+ SendType string
+ // TargetId identifies the cluster node to which the event should be sent.
+ // It should match the cluster id of the receiving instance.
+ // If empty, the event gets broadcasted to all other nodes.
+ TargetId string
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_event_data.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_event_data.go
new file mode 100644
index 00000000..1253533d
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_event_data.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+// PluginEventData used to notify peers about plugin changes.
+type PluginEventData struct {
+ Id string `json:"id"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_key_value.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_key_value.go
new file mode 100644
index 00000000..ad5971dd
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_key_value.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "unicode/utf8"
+)
+
+const (
+ KeyValuePluginIdMaxRunes = 190
+ KeyValueKeyMaxRunes = 50
+)
+
+type PluginKeyValue struct {
+ PluginId string `json:"plugin_id"`
+ Key string `json:"key" db:"PKey"`
+ Value []byte `json:"value" db:"PValue"`
+ ExpireAt int64 `json:"expire_at"`
+}
+
+func (kv *PluginKeyValue) IsValid() *AppError {
+ if kv.PluginId == "" || utf8.RuneCountInString(kv.PluginId) > KeyValuePluginIdMaxRunes {
+ return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.plugin_id.app_error", map[string]interface{}{"Max": KeyValueKeyMaxRunes, "Min": 0}, "key="+kv.Key, http.StatusBadRequest)
+ }
+
+ if kv.Key == "" || utf8.RuneCountInString(kv.Key) > KeyValueKeyMaxRunes {
+ return NewAppError("PluginKeyValue.IsValid", "model.plugin_key_value.is_valid.key.app_error", map[string]interface{}{"Max": KeyValueKeyMaxRunes, "Min": 0}, "key="+kv.Key, http.StatusBadRequest)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_kvset_options.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_kvset_options.go
new file mode 100644
index 00000000..1d374c80
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_kvset_options.go
@@ -0,0 +1,47 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+)
+
+// PluginKVSetOptions contains information on how to store a value in the plugin KV store.
+type PluginKVSetOptions struct {
+ Atomic bool // Only store the value if the current value matches the oldValue
+ OldValue []byte // The value to compare with the current value. Only used when Atomic is true
+ ExpireInSeconds int64 // Set an expire counter
+}
+
+// IsValid returns nil if the chosen options are valid.
+func (opt *PluginKVSetOptions) IsValid() *AppError {
+ if !opt.Atomic && opt.OldValue != nil {
+ return NewAppError(
+ "PluginKVSetOptions.IsValid",
+ "model.plugin_kvset_options.is_valid.old_value.app_error",
+ nil,
+ "",
+ http.StatusBadRequest,
+ )
+ }
+
+ return nil
+}
+
+// NewPluginKeyValueFromOptions return a PluginKeyValue given a pluginID, a KV pair and options.
+func NewPluginKeyValueFromOptions(pluginId, key string, value []byte, opt PluginKVSetOptions) (*PluginKeyValue, *AppError) {
+ expireAt := int64(0)
+ if opt.ExpireInSeconds != 0 {
+ expireAt = GetMillis() + (opt.ExpireInSeconds * 1000)
+ }
+
+ kv := &PluginKeyValue{
+ PluginId: pluginId,
+ Key: key,
+ Value: value,
+ ExpireAt: expireAt,
+ }
+
+ return kv, nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_status.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_status.go
new file mode 100644
index 00000000..c206505b
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_status.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ PluginStateNotRunning = 0
+ PluginStateStarting = 1 // unused by server
+ PluginStateRunning = 2
+ PluginStateFailedToStart = 3
+ PluginStateFailedToStayRunning = 4
+ 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
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_valid.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_valid.go
new file mode 100644
index 00000000..b6144513
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugin_valid.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "regexp"
+ "unicode/utf8"
+)
+
+const (
+ MinIdLength = 3
+ MaxIdLength = 190
+ ValidIdRegex = `^[a-zA-Z0-9-_\.]+$`
+)
+
+// ValidId constrains the set of valid plugin identifiers:
+// ^[a-zA-Z0-9-_\.]+
+var validId *regexp.Regexp
+
+func init() {
+ validId = regexp.MustCompile(ValidIdRegex)
+}
+
+// IsValidPluginId verifies that the plugin id has a minimum length of 3, maximum length of 190, and
+// contains only alphanumeric characters, dashes, underscores and periods.
+//
+// These constraints are necessary since the plugin id is used as part of a filesystem path.
+func IsValidPluginId(id string) bool {
+ if utf8.RuneCountInString(id) < MinIdLength {
+ return false
+ }
+
+ if utf8.RuneCountInString(id) > MaxIdLength {
+ return false
+ }
+
+ return validId.MatchString(id)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/plugins_response.go b/vendor/github.com/mattermost/mattermost-server/v6/model/plugins_response.go
new file mode 100644
index 00000000..5aed0b3c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/plugins_response.go
@@ -0,0 +1,13 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type PluginInfo struct {
+ Manifest
+}
+
+type PluginsResponse struct {
+ Active []*PluginInfo `json:"active"`
+ Inactive []*PluginInfo `json:"inactive"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/post.go b/vendor/github.com/mattermost/mattermost-server/v6/model/post.go
new file mode 100644
index 00000000..d13fef0a
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/post.go
@@ -0,0 +1,719 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+ "unicode/utf8"
+
+ "github.com/mattermost/mattermost-server/v6/shared/markdown"
+)
+
+const (
+ PostSystemMessagePrefix = "system_"
+ PostTypeDefault = ""
+ PostTypeSlackAttachment = "slack_attachment"
+ PostTypeSystemGeneric = "system_generic"
+ PostTypeJoinLeave = "system_join_leave" // Deprecated, use PostJoinChannel or PostLeaveChannel instead
+ PostTypeJoinChannel = "system_join_channel"
+ PostTypeGuestJoinChannel = "system_guest_join_channel"
+ PostTypeLeaveChannel = "system_leave_channel"
+ PostTypeJoinTeam = "system_join_team"
+ PostTypeLeaveTeam = "system_leave_team"
+ PostTypeAutoResponder = "system_auto_responder"
+ PostTypeAddRemove = "system_add_remove" // Deprecated, use PostAddToChannel or PostRemoveFromChannel instead
+ PostTypeAddToChannel = "system_add_to_channel"
+ PostTypeAddGuestToChannel = "system_add_guest_to_chan"
+ PostTypeRemoveFromChannel = "system_remove_from_channel"
+ PostTypeMoveChannel = "system_move_channel"
+ PostTypeAddToTeam = "system_add_to_team"
+ PostTypeRemoveFromTeam = "system_remove_from_team"
+ PostTypeHeaderChange = "system_header_change"
+ PostTypeDisplaynameChange = "system_displayname_change"
+ PostTypeConvertChannel = "system_convert_channel"
+ PostTypePurposeChange = "system_purpose_change"
+ PostTypeChannelDeleted = "system_channel_deleted"
+ PostTypeChannelRestored = "system_channel_restored"
+ PostTypeEphemeral = "system_ephemeral"
+ PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
+ PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
+ PostTypeSystemWarnMetricStatus = "warn_metric_status"
+ PostTypeMe = "me"
+ PostCustomTypePrefix = "custom_"
+
+ PostFileidsMaxRunes = 300
+ PostFilenamesMaxRunes = 4000
+ PostHashtagsMaxRunes = 1000
+ PostMessageMaxRunesV1 = 4000
+ PostMessageMaxBytesV2 = 65535 // Maximum size of a TEXT column in MySQL
+ PostMessageMaxRunesV2 = PostMessageMaxBytesV2 / 4 // Assume a worst-case representation
+ PostPropsMaxRunes = 800000
+ PostPropsMaxUserRunes = PostPropsMaxRunes - 40000 // Leave some room for system / pre-save modifications
+
+ PropsAddChannelMember = "add_channel_member"
+
+ PostPropsAddedUserId = "addedUserId"
+ PostPropsDeleteBy = "deleteBy"
+ PostPropsOverrideIconURL = "override_icon_url"
+ PostPropsOverrideIconEmoji = "override_icon_emoji"
+
+ PostPropsMentionHighlightDisabled = "mentionHighlightDisabled"
+ PostPropsGroupHighlightDisabled = "disable_group_highlight"
+
+ PostPropsPreviewedPost = "previewed_post"
+)
+
+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"`
+ 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"`
+ propsMu sync.RWMutex `db:"-"` // Unexported mutex used to guard Post.Props.
+ Props StringInterface `json:"props"` // Deprecated: use GetProps()
+ Hashtags string `json:"hashtags"`
+ Filenames StringArray `json:"-"` // 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"`
+ RemoteId *string `json:"remote_id,omitempty"`
+
+ // Transient data populated before sending a post to the client
+ ReplyCount int64 `json:"reply_count" db:"-"`
+ LastReplyAt int64 `json:"last_reply_at" db:"-"`
+ Participants []*User `json:"participants" db:"-"`
+ IsFollowing *bool `json:"is_following,omitempty" db:"-"` // for root posts in collapsed thread mode indicates if the current user is following this thread
+ Metadata *PostMetadata `json:"metadata,omitempty" db:"-"`
+}
+
+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"`
+}
+
+type AnalyticsPostCountsOptions struct {
+ TeamId string
+ BotsOnly bool
+ YesterdayOnly bool
+}
+
+func (o *PostPatch) WithRewrittenImageURLs(f func(string) string) *PostPatch {
+ copy := *o
+ if copy.Message != nil {
+ *copy.Message = RewriteImageURLs(*o.Message, f)
+ }
+ return &copy
+}
+
+type PostForExport struct {
+ Post
+ TeamName string
+ ChannelName string
+ Username string
+ ReplyCount int
+}
+
+type DirectPostForExport struct {
+ Post
+ User string
+ ChannelMembers *[]string
+}
+
+type ReplyForExport struct {
+ Post
+ Username string
+}
+
+type PostForIndexing struct {
+ Post
+ TeamId string `json:"team_id"`
+ ParentCreateAt *int64 `json:"parent_create_at"`
+}
+
+type FileForIndexing struct {
+ FileInfo
+ ChannelId string `json:"channel_id"`
+ Content string `json:"content"`
+}
+
+// ShallowCopy is an utility function to shallow copy a Post to the given
+// destination without touching the internal RWMutex.
+func (o *Post) ShallowCopy(dst *Post) error {
+ if dst == nil {
+ return errors.New("dst cannot be nil")
+ }
+ o.propsMu.RLock()
+ defer o.propsMu.RUnlock()
+ dst.propsMu.Lock()
+ defer dst.propsMu.Unlock()
+ dst.Id = o.Id
+ dst.CreateAt = o.CreateAt
+ dst.UpdateAt = o.UpdateAt
+ dst.EditAt = o.EditAt
+ dst.DeleteAt = o.DeleteAt
+ dst.IsPinned = o.IsPinned
+ dst.UserId = o.UserId
+ dst.ChannelId = o.ChannelId
+ dst.RootId = o.RootId
+ dst.OriginalId = o.OriginalId
+ dst.Message = o.Message
+ dst.MessageSource = o.MessageSource
+ dst.Type = o.Type
+ dst.Props = o.Props
+ dst.Hashtags = o.Hashtags
+ dst.Filenames = o.Filenames
+ dst.FileIds = o.FileIds
+ dst.PendingPostId = o.PendingPostId
+ dst.HasReactions = o.HasReactions
+ dst.ReplyCount = o.ReplyCount
+ dst.Participants = o.Participants
+ dst.LastReplyAt = o.LastReplyAt
+ dst.Metadata = o.Metadata
+ if o.IsFollowing != nil {
+ dst.IsFollowing = NewBool(*o.IsFollowing)
+ }
+ dst.RemoteId = o.RemoteId
+ return nil
+}
+
+// Clone shallowly copies the post and returns the copy.
+func (o *Post) Clone() *Post {
+ copy := &Post{}
+ o.ShallowCopy(copy)
+ return copy
+}
+
+func (o *Post) ToJSON() (string, error) {
+ copy := o.Clone()
+ copy.StripActionIntegrations()
+ b, err := json.Marshal(copy)
+ return string(b), err
+}
+
+type GetPostsSinceOptions struct {
+ UserId string
+ ChannelId string
+ Time int64
+ SkipFetchThreads bool
+ CollapsedThreads bool
+ CollapsedThreadsExtended bool
+ SortAscending bool
+}
+
+type GetPostsSinceForSyncCursor struct {
+ LastPostUpdateAt int64
+ LastPostId string
+}
+
+type GetPostsSinceForSyncOptions struct {
+ ChannelId string
+ ExcludeRemoteId string
+ IncludeDeleted bool
+}
+
+type GetPostsOptions struct {
+ UserId string
+ ChannelId string
+ PostId string
+ Page int
+ PerPage int
+ SkipFetchThreads bool
+ CollapsedThreads bool
+ CollapsedThreadsExtended bool
+}
+
+func (o *Post) Etag() string {
+ return Etag(o.Id, o.UpdateAt)
+}
+
+func (o *Post) IsValid(maxPostSize int) *AppError {
+ if !IsValidId(o.Id) {
+ 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 !IsValidId(o.UserId) {
+ return NewAppError("Post.IsValid", "model.post.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.ChannelId) {
+ return NewAppError("Post.IsValid", "model.post.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(IsValidId(o.RootId) || o.RootId == "") {
+ return NewAppError("Post.IsValid", "model.post.is_valid.root_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !(len(o.OriginalId) == 26 || o.OriginalId == "") {
+ 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) > PostHashtagsMaxRunes {
+ return NewAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ switch o.Type {
+ case
+ PostTypeDefault,
+ PostTypeSystemGeneric,
+ PostTypeJoinLeave,
+ PostTypeAutoResponder,
+ PostTypeAddRemove,
+ PostTypeJoinChannel,
+ PostTypeGuestJoinChannel,
+ PostTypeLeaveChannel,
+ PostTypeJoinTeam,
+ PostTypeLeaveTeam,
+ PostTypeAddToChannel,
+ PostTypeAddGuestToChannel,
+ PostTypeRemoveFromChannel,
+ PostTypeMoveChannel,
+ PostTypeAddToTeam,
+ PostTypeRemoveFromTeam,
+ PostTypeSlackAttachment,
+ PostTypeHeaderChange,
+ PostTypePurposeChange,
+ PostTypeDisplaynameChange,
+ PostTypeConvertChannel,
+ PostTypeChannelDeleted,
+ PostTypeChannelRestored,
+ PostTypeChangeChannelPrivacy,
+ PostTypeAddBotTeamsChannels,
+ PostTypeSystemWarnMetricStatus,
+ PostTypeMe:
+ default:
+ if !strings.HasPrefix(o.Type, PostCustomTypePrefix) {
+ return NewAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type, http.StatusBadRequest)
+ }
+ }
+
+ if utf8.RuneCountInString(ArrayToJSON(o.Filenames)) > PostFilenamesMaxRunes {
+ return NewAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(ArrayToJSON(o.FileIds)) > PostFileidsMaxRunes {
+ return NewAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(StringInterfaceToJSON(o.GetProps())) > PostPropsMaxRunes {
+ return NewAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (o *Post) SanitizeProps() {
+ if o == nil {
+ return
+ }
+ membersToSanitize := []string{
+ PropsAddChannelMember,
+ }
+
+ for _, member := range membersToSanitize {
+ if _, ok := o.GetProps()[member]; ok {
+ o.DelProp(member)
+ }
+ }
+ for _, p := range o.Participants {
+ p.Sanitize(map[string]bool{})
+ }
+}
+
+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.GetProps() == nil {
+ o.SetProps(make(map[string]interface{}))
+ }
+
+ if o.Filenames == nil {
+ o.Filenames = []string{}
+ }
+
+ if o.FileIds == nil {
+ o.FileIds = []string{}
+ }
+
+ o.GenerateActionIds()
+
+ // There's a rare bug where the client sends up duplicate FileIds so protect against that
+ o.FileIds = RemoveDuplicateStrings(o.FileIds)
+}
+
+func (o *Post) MakeNonNil() {
+ if o.GetProps() == nil {
+ o.SetProps(make(map[string]interface{}))
+ }
+}
+
+func (o *Post) DelProp(key string) {
+ o.propsMu.Lock()
+ defer o.propsMu.Unlock()
+ propsCopy := make(map[string]interface{}, len(o.Props)-1)
+ for k, v := range o.Props {
+ propsCopy[k] = v
+ }
+ delete(propsCopy, key)
+ o.Props = propsCopy
+}
+
+func (o *Post) AddProp(key string, value interface{}) {
+ o.propsMu.Lock()
+ defer o.propsMu.Unlock()
+ propsCopy := make(map[string]interface{}, len(o.Props)+1)
+ for k, v := range o.Props {
+ propsCopy[k] = v
+ }
+ propsCopy[key] = value
+ o.Props = propsCopy
+}
+
+func (o *Post) GetProps() StringInterface {
+ o.propsMu.RLock()
+ defer o.propsMu.RUnlock()
+ return o.Props
+}
+
+func (o *Post) SetProps(props StringInterface) {
+ o.propsMu.Lock()
+ defer o.propsMu.Unlock()
+ o.Props = props
+}
+
+func (o *Post) GetProp(key string) interface{} {
+ o.propsMu.RLock()
+ defer o.propsMu.RUnlock()
+ return o.Props[key]
+}
+
+func (o *Post) IsSystemMessage() bool {
+ return len(o.Type) >= len(PostSystemMessagePrefix) && o.Type[:len(PostSystemMessagePrefix)] == PostSystemMessagePrefix
+}
+
+// IsRemote returns true if the post originated on a remote cluster.
+func (o *Post) IsRemote() bool {
+ return o.RemoteId != nil && *o.RemoteId != ""
+}
+
+// GetRemoteID safely returns the remoteID or empty string if not remote.
+func (o *Post) GetRemoteID() string {
+ if o.RemoteId != nil {
+ return *o.RemoteId
+ }
+ return ""
+}
+
+func (o *Post) IsJoinLeaveMessage() bool {
+ return o.Type == PostTypeJoinLeave ||
+ o.Type == PostTypeAddRemove ||
+ o.Type == PostTypeJoinChannel ||
+ o.Type == PostTypeLeaveChannel ||
+ o.Type == PostTypeJoinTeam ||
+ o.Type == PostTypeLeaveTeam ||
+ o.Type == PostTypeAddToChannel ||
+ o.Type == PostTypeRemoveFromChannel ||
+ o.Type == PostTypeAddToTeam ||
+ o.Type == PostTypeRemoveFromTeam
+}
+
+func (o *Post) Patch(patch *PostPatch) {
+ if patch.IsPinned != nil {
+ o.IsPinned = *patch.IsPinned
+ }
+
+ if patch.Message != nil {
+ o.Message = *patch.Message
+ }
+
+ if patch.Props != nil {
+ newProps := *patch.Props
+ o.SetProps(newProps)
+ }
+
+ if patch.FileIds != nil {
+ o.FileIds = *patch.FileIds
+ }
+
+ if patch.HasReactions != nil {
+ o.HasReactions = *patch.HasReactions
+ }
+}
+
+func (o *Post) ChannelMentions() []string {
+ return ChannelMentions(o.Message)
+}
+
+// DisableMentionHighlights disables a posts mention highlighting and returns the first channel mention that was present in the message.
+func (o *Post) DisableMentionHighlights() string {
+ mention, hasMentions := findAtChannelMention(o.Message)
+ if hasMentions {
+ o.AddProp(PostPropsMentionHighlightDisabled, true)
+ }
+ return mention
+}
+
+// DisableMentionHighlights disables mention highlighting for a post patch if required.
+func (o *PostPatch) DisableMentionHighlights() {
+ if o.Message == nil {
+ return
+ }
+ if _, hasMentions := findAtChannelMention(*o.Message); hasMentions {
+ if o.Props == nil {
+ o.Props = &StringInterface{}
+ }
+ (*o.Props)[PostPropsMentionHighlightDisabled] = true
+ }
+}
+
+func findAtChannelMention(message string) (mention string, found bool) {
+ re := regexp.MustCompile(`(?i)\B@(channel|all|here)\b`)
+ matched := re.FindStringSubmatch(message)
+ if found = (len(matched) > 0); found {
+ mention = strings.ToLower(matched[0])
+ }
+ return
+}
+
+func (o *Post) Attachments() []*SlackAttachment {
+ if attachments, ok := o.GetProp("attachments").([]*SlackAttachment); ok {
+ return attachments
+ }
+ var ret []*SlackAttachment
+ if attachments, ok := o.GetProp("attachments").([]interface{}); ok {
+ for _, attachment := range attachments {
+ if enc, err := json.Marshal(attachment); err == nil {
+ var decoded SlackAttachment
+ if json.Unmarshal(enc, &decoded) == nil {
+ // Ignoring nil actions
+ i := 0
+ for _, action := range decoded.Actions {
+ if action != nil {
+ decoded.Actions[i] = action
+ i++
+ }
+ }
+ decoded.Actions = decoded.Actions[:i]
+
+ // Ignoring nil fields
+ i = 0
+ for _, field := range decoded.Fields {
+ if field != nil {
+ decoded.Fields[i] = field
+ i++
+ }
+ }
+ decoded.Fields = decoded.Fields[:i]
+ ret = append(ret, &decoded)
+ }
+ }
+ }
+ }
+ return ret
+}
+
+func (o *Post) AttachmentsEqual(input *Post) bool {
+ attachments := o.Attachments()
+ inputAttachments := input.Attachments()
+
+ if len(attachments) != len(inputAttachments) {
+ return false
+ }
+
+ for i := range attachments {
+ if !attachments[i].Equals(inputAttachments[i]) {
+ return false
+ }
+ }
+
+ return true
+}
+
+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.Clone()
+ copy.Message = RewriteImageURLs(o.Message, f)
+ if copy.MessageSource == "" && copy.Message != o.Message {
+ copy.MessageSource = o.Message
+ }
+ return copy
+}
+
+// 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)
+}
+
+func (o *Post) IsFromOAuthBot() bool {
+ props := o.GetProps()
+ return props["from_webhook"] == "true" && props["override_username"] != ""
+}
+
+func (o *Post) ToNilIfInvalid() *Post {
+ if o.Id == "" {
+ return nil
+ }
+ return o
+}
+
+func (o *Post) RemovePreviewPost() {
+ if o.Metadata == nil || o.Metadata.Embeds == nil {
+ return
+ }
+ n := 0
+ for _, embed := range o.Metadata.Embeds {
+ if embed.Type != PostEmbedPermalink {
+ o.Metadata.Embeds[n] = embed
+ n++
+ }
+ }
+ o.Metadata.Embeds = o.Metadata.Embeds[:n]
+}
+
+func (o *Post) GetPreviewPost() *PreviewPost {
+ for _, embed := range o.Metadata.Embeds {
+ if embed.Type == PostEmbedPermalink {
+ if previewPost, ok := embed.Data.(*PreviewPost); ok {
+ return previewPost
+ }
+ }
+ }
+ return nil
+}
+
+func (o *Post) GetPreviewedPostProp() string {
+ if val, ok := o.GetProp(PostPropsPreviewedPost).(string); ok {
+ return val
+ }
+ return ""
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/post_embed.go b/vendor/github.com/mattermost/mattermost-server/v6/model/post_embed.go
new file mode 100644
index 00000000..ea3e2c5b
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/post_embed.go
@@ -0,0 +1,24 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ PostEmbedImage PostEmbedType = "image"
+ PostEmbedMessageAttachment PostEmbedType = "message_attachment"
+ PostEmbedOpengraph PostEmbedType = "opengraph"
+ PostEmbedLink PostEmbedType = "link"
+ PostEmbedPermalink PostEmbedType = "permalink"
+)
+
+type PostEmbedType string
+
+type PostEmbed struct {
+ Type PostEmbedType `json:"type"`
+
+ // The URL of the embedded content. Used for image and OpenGraph embeds.
+ URL string `json:"url,omitempty"`
+
+ // Any additional data for the embedded content. Only used for OpenGraph embeds.
+ Data interface{} `json:"data,omitempty"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/post_list.go b/vendor/github.com/mattermost/mattermost-server/v6/model/post_list.go
new file mode 100644
index 00000000..933d1f8f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/post_list.go
@@ -0,0 +1,177 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "sort"
+)
+
+type PostList struct {
+ Order []string `json:"order"`
+ Posts map[string]*Post `json:"posts"`
+ NextPostId string `json:"next_post_id"`
+ PrevPostId string `json:"prev_post_id"`
+}
+
+func NewPostList() *PostList {
+ return &PostList{
+ Order: make([]string, 0),
+ Posts: make(map[string]*Post),
+ NextPostId: "",
+ PrevPostId: "",
+ }
+}
+
+func (o *PostList) Clone() *PostList {
+ orderCopy := make([]string, len(o.Order))
+ postsCopy := make(map[string]*Post)
+ for i, v := range o.Order {
+ orderCopy[i] = v
+ }
+ for k, v := range o.Posts {
+ postsCopy[k] = v.Clone()
+ }
+ return &PostList{
+ Order: orderCopy,
+ Posts: postsCopy,
+ NextPostId: o.NextPostId,
+ PrevPostId: o.PrevPostId,
+ }
+}
+
+func (o *PostList) ToSlice() []*Post {
+ var posts []*Post
+
+ if l := len(o.Posts); l > 0 {
+ posts = make([]*Post, 0, l)
+ }
+
+ for _, id := range o.Order {
+ posts = append(posts, o.Posts[id])
+ }
+ return posts
+}
+
+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 &copy
+}
+
+func (o *PostList) StripActionIntegrations() {
+ posts := o.Posts
+ o.Posts = make(map[string]*Post)
+ for id, post := range posts {
+ pcopy := post.Clone()
+ pcopy.StripActionIntegrations()
+ o.Posts[id] = pcopy
+ }
+}
+
+func (o *PostList) ToJSON() (string, error) {
+ copy := *o
+ copy.StripActionIntegrations()
+ b, err := json.Marshal(&copy)
+ return string(b), err
+}
+
+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) UniqueOrder() {
+ keys := make(map[string]bool)
+ order := []string{}
+ for _, postId := range o.Order {
+ if _, value := keys[postId]; !value {
+ keys[postId] = true
+ order = append(order, postId)
+ }
+ }
+
+ o.Order = order
+}
+
+func (o *PostList) Extend(other *PostList) {
+ for postId := range other.Posts {
+ o.AddPost(other.Posts[postId])
+ }
+
+ for _, postId := range other.Order {
+ o.AddOrder(postId)
+ }
+
+ o.UniqueOrder()
+}
+
+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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/post_metadata.go b/vendor/github.com/mattermost/mattermost-server/v6/model/post_metadata.go
new file mode 100644
index 00000000..0f9e61d3
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/post_metadata.go
@@ -0,0 +1,64 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type PostMetadata struct {
+ // Embeds holds information required to render content embedded in the post. This includes the OpenGraph metadata
+ // for links in the post.
+ Embeds []*PostEmbed `json:"embeds,omitempty"`
+
+ // Emojis holds all custom emojis used in the post or used in reaction to the post.
+ Emojis []*Emoji `json:"emojis,omitempty"`
+
+ // Files holds information about the file attachments on the post.
+ Files []*FileInfo `json:"files,omitempty"`
+
+ // Images holds the dimensions of all external images in the post as a map of the image URL to its diemsnions.
+ // This includes image embeds (when the message contains a plaintext link to an image), Markdown images, images
+ // contained in the OpenGraph metadata, and images contained in message attachments. It does not contain
+ // the dimensions of any file attachments as those are stored in FileInfos.
+ Images map[string]*PostImage `json:"images,omitempty"`
+
+ // Reactions holds reactions made to the post.
+ Reactions []*Reaction `json:"reactions,omitempty"`
+}
+
+type PostImage struct {
+ Width int `json:"width"`
+ Height int `json:"height"`
+
+ // Format is the name of the image format as used by image/go such as "png", "gif", or "jpeg".
+ Format string `json:"format"`
+
+ // FrameCount stores the number of frames in this image, if it is an animated gif. It will be 0 for other formats.
+ FrameCount int `json:"frame_count"`
+}
+
+// Copy does a deep copy
+func (p *PostMetadata) Copy() *PostMetadata {
+ embedsCopy := make([]*PostEmbed, len(p.Embeds))
+ copy(embedsCopy, p.Embeds)
+
+ emojisCopy := make([]*Emoji, len(p.Emojis))
+ copy(emojisCopy, p.Emojis)
+
+ filesCopy := make([]*FileInfo, len(p.Files))
+ copy(filesCopy, p.Files)
+
+ imagesCopy := map[string]*PostImage{}
+ for k, v := range p.Images {
+ imagesCopy[k] = v
+ }
+
+ reactionsCopy := make([]*Reaction, len(p.Reactions))
+ copy(reactionsCopy, p.Reactions)
+
+ return &PostMetadata{
+ Embeds: embedsCopy,
+ Emojis: emojisCopy,
+ Files: filesCopy,
+ Images: imagesCopy,
+ Reactions: reactionsCopy,
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/post_search_results.go b/vendor/github.com/mattermost/mattermost-server/v6/model/post_search_results.go
new file mode 100644
index 00000000..92e044a7
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/post_search_results.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"
+)
+
+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, error) {
+ copy := *o
+ copy.PostList.StripActionIntegrations()
+ b, err := json.Marshal(&copy)
+ return string(b), err
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/preference.go b/vendor/github.com/mattermost/mattermost-server/v6/model/preference.go
new file mode 100644
index 00000000..41a58235
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/preference.go
@@ -0,0 +1,122 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "net/http"
+ "regexp"
+ "strings"
+ "unicode/utf8"
+)
+
+const (
+ PreferenceCategoryDirectChannelShow = "direct_channel_show"
+ PreferenceCategoryGroupChannelShow = "group_channel_show"
+ PreferenceCategoryTutorialSteps = "tutorial_step"
+ PreferenceCategoryAdvancedSettings = "advanced_settings"
+ PreferenceCategoryFlaggedPost = "flagged_post"
+ PreferenceCategoryFavoriteChannel = "favorite_channel"
+ PreferenceCategorySidebarSettings = "sidebar_settings"
+
+ PreferenceCategoryDisplaySettings = "display_settings"
+ PreferenceNameCollapsedThreadsEnabled = "collapsed_reply_threads"
+ PreferenceNameChannelDisplayMode = "channel_display_mode"
+ PreferenceNameCollapseSetting = "collapse_previews"
+ PreferenceNameMessageDisplay = "message_display"
+ PreferenceNameNameFormat = "name_format"
+ PreferenceNameUseMilitaryTime = "use_military_time"
+ PreferenceRecommendedNextSteps = "recommended_next_steps"
+
+ PreferenceCategoryTheme = "theme"
+ // the name for theme props is the team id
+
+ PreferenceCategoryAuthorizedOAuthApp = "oauth_app"
+ // the name for oauth_app is the client_id and value is the current scope
+
+ PreferenceCategoryLast = "last"
+ PreferenceNameLastChannel = "channel"
+ PreferenceNameLastTeam = "team"
+
+ PreferenceCategoryCustomStatus = "custom_status"
+ PreferenceNameRecentCustomStatuses = "recent_custom_statuses"
+ PreferenceNameCustomStatusTutorialState = "custom_status_tutorial_state"
+
+ PreferenceCustomStatusModalViewed = "custom_status_modal_viewed"
+
+ PreferenceCategoryNotifications = "notifications"
+ PreferenceNameEmailInterval = "email_interval"
+
+ PreferenceEmailIntervalNoBatchingSeconds = "30" // the "immediate" setting is actually 30s
+ PreferenceEmailIntervalBatchingSeconds = "900" // fifteen minutes is 900 seconds
+ PreferenceEmailIntervalImmediately = "immediately"
+ PreferenceEmailIntervalFifteen = "fifteen"
+ PreferenceEmailIntervalFifteenAsSeconds = "900"
+ PreferenceEmailIntervalHour = "hour"
+ PreferenceEmailIntervalHourAsSeconds = "3600"
+)
+
+type Preference struct {
+ UserId string `json:"user_id"`
+ Category string `json:"category"`
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
+
+type Preferences []Preference
+
+func (o *Preference) IsValid() *AppError {
+ if !IsValidId(o.UserId) {
+ return NewAppError("Preference.IsValid", "model.preference.is_valid.id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest)
+ }
+
+ if o.Category == "" || 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 == PreferenceCategoryTheme {
+ 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 == PreferenceCategoryTheme {
+ // 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/v6/model/product_notices.go b/vendor/github.com/mattermost/mattermost-server/v6/model/product_notices.go
new file mode 100644
index 00000000..94343cb4
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/product_notices.go
@@ -0,0 +1,220 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+
+ "github.com/pkg/errors"
+)
+
+type ProductNotices []ProductNotice
+
+func (r *ProductNotices) Marshal() ([]byte, error) {
+ return json.Marshal(r)
+}
+
+func UnmarshalProductNotices(data []byte) (ProductNotices, error) {
+ var r ProductNotices
+ err := json.Unmarshal(data, &r)
+ return r, err
+}
+
+// List of product notices. Order is important and is used to resolve priorities.
+// Each notice will only be show if conditions are met.
+type ProductNotice struct {
+ Conditions Conditions `json:"conditions"`
+ ID string `json:"id"` // Unique identifier for this notice. Can be a running number. Used for storing 'viewed'; state on the server.
+ LocalizedMessages map[string]NoticeMessageInternal `json:"localizedMessages"` // Notice message data, organized by locale.; Example:; "localizedMessages": {; "en": { "title": "English", description: "English description"},; "frFR": { "title": "Frances", description: "French description"}; }
+ Repeatable *bool `json:"repeatable,omitempty"` // Configurable flag if the notice should reappear after itโ€™s seen and dismissed
+}
+
+func (n *ProductNotice) SysAdminOnly() bool {
+ return n.Conditions.Audience != nil && *n.Conditions.Audience == NoticeAudienceSysadmin
+}
+
+func (n *ProductNotice) TeamAdminOnly() bool {
+ return n.Conditions.Audience != nil && *n.Conditions.Audience == NoticeAudienceTeamAdmin
+}
+
+type Conditions struct {
+ Audience *NoticeAudience `json:"audience,omitempty"`
+ ClientType *NoticeClientType `json:"clientType,omitempty"` // Only show the notice on specific clients. Defaults to 'all'
+ DesktopVersion []string `json:"desktopVersion,omitempty"` // What desktop client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
+ DisplayDate *string `json:"displayDate,omitempty"` // When to display the notice.; Examples:; "2020-03-01T00:00:00Z" - show on specified date; ">= 2020-03-01T00:00:00Z" - show after specified date; "< 2020-03-01T00:00:00Z" - show before the specified date; "> 2020-03-01T00:00:00Z <= 2020-04-01T00:00:00Z" - show only between the specified dates
+ InstanceType *NoticeInstanceType `json:"instanceType,omitempty"`
+ MobileVersion []string `json:"mobileVersion,omitempty"` // What mobile client versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
+ NumberOfPosts *int64 `json:"numberOfPosts,omitempty"` // Only show the notice when server has more than specified number of posts
+ NumberOfUsers *int64 `json:"numberOfUsers,omitempty"` // Only show the notice when server has more than specified number of users
+ ServerConfig map[string]interface{} `json:"serverConfig,omitempty"` // Map of mattermost server config paths and their values. Notice will be displayed only if; the values match the target server config; Example: serverConfig: { "PluginSettings.Enable": true, "GuestAccountsSettings.Enable":; false }
+ ServerVersion []string `json:"serverVersion,omitempty"` // What server versions does this notice apply to.; Format: semver ranges (https://devhints.io/semver); Example: [">=1.2.3 < ~2.4.x"]; Example: ["<v5.19", "v5.20-v5.22"]
+ Sku *NoticeSKU `json:"sku,omitempty"`
+ UserConfig map[string]interface{} `json:"userConfig,omitempty"` // Map of user's settings and their values. Notice will be displayed only if the values; match the viewing users' config; Example: userConfig: { "new_sidebar.disabled": true }
+ DeprecatingDependency *ExternalDependency `json:"deprecating_dependency,omitempty"` // External dependency which is going to be deprecated
+}
+
+type NoticeMessageInternal struct {
+ Action *NoticeAction `json:"action,omitempty"` // Optional action to perform on action button click. (defaults to closing the notice)
+ ActionParam *string `json:"actionParam,omitempty"` // Optional action parameter.; Example: {"action": "url", actionParam: "/console/some-page"}
+ ActionText *string `json:"actionText,omitempty"` // Optional override for the action button text (defaults to OK)
+ Description string `json:"description"` // Notice content. Use {{Mattermost}} instead of plain text to support white-labeling. Text; supports Markdown.
+ Image *string `json:"image,omitempty"`
+ Title string `json:"title"` // Notice title. Use {{Mattermost}} instead of plain text to support white-labeling. Text; supports Markdown.
+}
+type NoticeMessages []NoticeMessage
+
+type NoticeMessage struct {
+ NoticeMessageInternal
+ ID string `json:"id"`
+ SysAdminOnly bool `json:"sysAdminOnly"`
+ TeamAdminOnly bool `json:"teamAdminOnly"`
+}
+
+func (r *NoticeMessages) Marshal() ([]byte, error) {
+ return json.Marshal(r)
+}
+
+func UnmarshalProductNoticeMessages(data io.Reader) (NoticeMessages, error) {
+ var r NoticeMessages
+ err := json.NewDecoder(data).Decode(&r)
+ return r, err
+}
+
+// User role, i.e. who will see the notice. Defaults to "all"
+type NoticeAudience string
+
+func NewNoticeAudience(s NoticeAudience) *NoticeAudience {
+ return &s
+}
+
+func (a *NoticeAudience) Matches(sysAdmin bool, teamAdmin bool) bool {
+ switch *a {
+ case NoticeAudienceAll:
+ return true
+ case NoticeAudienceMember:
+ return !sysAdmin && !teamAdmin
+ case NoticeAudienceSysadmin:
+ return sysAdmin
+ case NoticeAudienceTeamAdmin:
+ return teamAdmin
+ }
+ return false
+}
+
+const (
+ NoticeAudienceAll NoticeAudience = "all"
+ NoticeAudienceMember NoticeAudience = "member"
+ NoticeAudienceSysadmin NoticeAudience = "sysadmin"
+ NoticeAudienceTeamAdmin NoticeAudience = "teamadmin"
+)
+
+// Only show the notice on specific clients. Defaults to 'all'
+//
+// Client type. Defaults to "all"
+type NoticeClientType string
+
+func NewNoticeClientType(s NoticeClientType) *NoticeClientType { return &s }
+
+func (c *NoticeClientType) Matches(other NoticeClientType) bool {
+ switch *c {
+ case NoticeClientTypeAll:
+ return true
+ case NoticeClientTypeMobile:
+ return other == NoticeClientTypeMobileIos || other == NoticeClientTypeMobileAndroid
+ default:
+ return *c == other
+ }
+}
+
+const (
+ NoticeClientTypeAll NoticeClientType = "all"
+ NoticeClientTypeDesktop NoticeClientType = "desktop"
+ NoticeClientTypeMobile NoticeClientType = "mobile"
+ NoticeClientTypeMobileAndroid NoticeClientType = "mobile-android"
+ NoticeClientTypeMobileIos NoticeClientType = "mobile-ios"
+ NoticeClientTypeWeb NoticeClientType = "web"
+)
+
+func NoticeClientTypeFromString(s string) (NoticeClientType, error) {
+ switch s {
+ case "web":
+ return NoticeClientTypeWeb, nil
+ case "mobile-ios":
+ return NoticeClientTypeMobileIos, nil
+ case "mobile-android":
+ return NoticeClientTypeMobileAndroid, nil
+ case "desktop":
+ return NoticeClientTypeDesktop, nil
+ }
+ return NoticeClientTypeAll, errors.New("Invalid client type supplied")
+}
+
+// Instance type. Defaults to "both"
+type NoticeInstanceType string
+
+func NewNoticeInstanceType(n NoticeInstanceType) *NoticeInstanceType { return &n }
+func (t *NoticeInstanceType) Matches(isCloud bool) bool {
+ if *t == NoticeInstanceTypeBoth {
+ return true
+ }
+ if *t == NoticeInstanceTypeCloud && !isCloud {
+ return false
+ }
+ if *t == NoticeInstanceTypeOnPrem && isCloud {
+ return false
+ }
+ return true
+}
+
+const (
+ NoticeInstanceTypeBoth NoticeInstanceType = "both"
+ NoticeInstanceTypeCloud NoticeInstanceType = "cloud"
+ NoticeInstanceTypeOnPrem NoticeInstanceType = "onprem"
+)
+
+// SKU. Defaults to "all"
+type NoticeSKU string
+
+func NewNoticeSKU(s NoticeSKU) *NoticeSKU { return &s }
+func (c *NoticeSKU) Matches(s string) bool {
+ switch *c {
+ case NoticeSKUAll:
+ return true
+ case NoticeSKUE0, NoticeSKUTeam:
+ return s == ""
+ default:
+ return s == string(*c)
+ }
+}
+
+const (
+ NoticeSKUE0 NoticeSKU = "e0"
+ NoticeSKUE10 NoticeSKU = "e10"
+ NoticeSKUE20 NoticeSKU = "e20"
+ NoticeSKUAll NoticeSKU = "all"
+ NoticeSKUTeam NoticeSKU = "team"
+)
+
+// Optional action to perform on action button click. (defaults to closing the notice)
+//
+// Possible actions to execute on button press
+type NoticeAction string
+
+const (
+ URL NoticeAction = "url"
+)
+
+// Definition of the table keeping the 'viewed' state of each in-product notice per user
+type ProductNoticeViewState struct {
+ UserId string
+ NoticeId string
+ Viewed int32
+ Timestamp int64
+}
+
+type ExternalDependency struct {
+ Name string `json:"name"`
+ MinimumVersion string `json:"minimum_version"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/push_notification.go b/vendor/github.com/mattermost/mattermost-server/v6/model/push_notification.go
new file mode 100644
index 00000000..cad84f83
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/push_notification.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "strings"
+)
+
+const (
+ PushNotifyApple = "apple"
+ PushNotifyAndroid = "android"
+ PushNotifyAppleReactNative = "apple_rn"
+ PushNotifyAndroidReactNative = "android_rn"
+
+ PushTypeMessage = "message"
+ PushTypeClear = "clear"
+ PushTypeUpdateBadge = "update_badge"
+ PushTypeSession = "session"
+ PushMessageV2 = "v2"
+
+ PushSoundNone = "none"
+
+ // The category is set to handle a set of interactive Actions
+ // with the push notifications
+ CategoryCanReply = "CAN_REPLY"
+
+ MHPNS = "https://push.mattermost.com"
+
+ PushSendPrepare = "Prepared to send"
+ PushSendSuccess = "Successful"
+ PushNotSent = "Not Sent due to preferences"
+ PushReceived = "Received by device"
+)
+
+type PushNotificationAck struct {
+ Id string `json:"id"`
+ ClientReceivedAt int64 `json:"received_at"`
+ ClientPlatform string `json:"platform"`
+ NotificationType string `json:"type"`
+ PostId string `json:"post_id,omitempty"`
+ IsIdLoaded bool `json:"is_id_loaded"`
+}
+
+type PushNotification struct {
+ AckId string `json:"ack_id"`
+ Platform string `json:"platform"`
+ ServerId string `json:"server_id"`
+ DeviceId string `json:"device_id"`
+ PostId string `json:"post_id"`
+ Category string `json:"category,omitempty"`
+ Sound string `json:"sound,omitempty"`
+ Message string `json:"message,omitempty"`
+ Badge int `json:"badge,omitempty"`
+ ContentAvailable int `json:"cont_ava,omitempty"`
+ TeamId string `json:"team_id,omitempty"`
+ ChannelId string `json:"channel_id,omitempty"`
+ RootId string `json:"root_id,omitempty"`
+ ChannelName string `json:"channel_name,omitempty"`
+ Type string `json:"type,omitempty"`
+ SenderId string `json:"sender_id,omitempty"`
+ SenderName string `json:"sender_name,omitempty"`
+ OverrideUsername string `json:"override_username,omitempty"`
+ OverrideIconURL string `json:"override_icon_url,omitempty"`
+ FromWebhook string `json:"from_webhook,omitempty"`
+ Version string `json:"version,omitempty"`
+ IsIdLoaded bool `json:"is_id_loaded"`
+}
+
+func (pn *PushNotification) DeepCopy() *PushNotification {
+ copy := *pn
+ return &copy
+}
+
+func (pn *PushNotification) SetDeviceIdAndPlatform(deviceId string) {
+ index := strings.Index(deviceId, ":")
+
+ if index > -1 {
+ pn.Platform = deviceId[:index]
+ pn.DeviceId = deviceId[index+1:]
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/push_response.go b/vendor/github.com/mattermost/mattermost-server/v6/model/push_response.go
new file mode 100644
index 00000000..a88b4339
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/push_response.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const (
+ PushStatus = "status"
+ PushStatusOk = "OK"
+ PushStatusFail = "FAIL"
+ PushStatusRemove = "REMOVE"
+ PushStatusErrorMsg = "error"
+)
+
+type PushResponse map[string]string
+
+func NewOkPushResponse() PushResponse {
+ m := make(map[string]string)
+ m[PushStatus] = PushStatusOk
+ return m
+}
+
+func NewRemovePushResponse() PushResponse {
+ m := make(map[string]string)
+ m[PushStatus] = PushStatusRemove
+ return m
+}
+
+func NewErrorPushResponse(message string) PushResponse {
+ m := make(map[string]string)
+ m[PushStatus] = PushStatusFail
+ m[PushStatusErrorMsg] = message
+ return m
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/reaction.go b/vendor/github.com/mattermost/mattermost-server/v6/model/reaction.go
new file mode 100644
index 00000000..335cb904
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/reaction.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "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"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ RemoteId *string `json:"remote_id"`
+}
+
+func (o *Reaction) IsValid() *AppError {
+ if !IsValidId(o.UserId) {
+ return NewAppError("Reaction.IsValid", "model.reaction.is_valid.user_id.app_error", nil, "user_id="+o.UserId, http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.PostId) {
+ 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 o.EmojiName == "" || len(o.EmojiName) > EmojiNameMaxLength || !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)
+ }
+
+ if o.UpdateAt == 0 {
+ return NewAppError("Reaction.IsValid", "model.reaction.is_valid.update_at.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (o *Reaction) PreSave() {
+ if o.CreateAt == 0 {
+ o.CreateAt = GetMillis()
+ }
+ o.UpdateAt = GetMillis()
+ o.DeleteAt = 0
+
+ if o.RemoteId == nil {
+ o.RemoteId = NewString("")
+ }
+}
+
+func (o *Reaction) PreUpdate() {
+ o.UpdateAt = GetMillis()
+
+ if o.RemoteId == nil {
+ o.RemoteId = NewString("")
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/remote_cluster.go b/vendor/github.com/mattermost/mattermost-server/v6/model/remote_cluster.go
new file mode 100644
index 00000000..44d11f16
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/remote_cluster.go
@@ -0,0 +1,302 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/json"
+ "errors"
+ "io"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "golang.org/x/crypto/scrypt"
+)
+
+const (
+ RemoteOfflineAfterMillis = 1000 * 60 * 5 // 5 minutes
+ RemoteNameMinLength = 1
+ RemoteNameMaxLength = 64
+)
+
+var (
+ validRemoteNameChars = regexp.MustCompile(`^[a-zA-Z0-9\.\-\_]+$`)
+)
+
+type RemoteCluster struct {
+ RemoteId string `json:"remote_id"`
+ RemoteTeamId string `json:"remote_team_id"`
+ Name string `json:"name"`
+ DisplayName string `json:"display_name"`
+ SiteURL string `json:"site_url"`
+ CreateAt int64 `json:"create_at"`
+ LastPingAt int64 `json:"last_ping_at"`
+ Token string `json:"token"`
+ RemoteToken string `json:"remote_token"`
+ Topics string `json:"topics"`
+ CreatorId string `json:"creator_id"`
+}
+
+func (rc *RemoteCluster) PreSave() {
+ if rc.RemoteId == "" {
+ rc.RemoteId = NewId()
+ }
+
+ if rc.DisplayName == "" {
+ rc.DisplayName = rc.Name
+ }
+
+ rc.Name = SanitizeUnicode(rc.Name)
+ rc.DisplayName = SanitizeUnicode(rc.DisplayName)
+ rc.Name = NormalizeRemoteName(rc.Name)
+
+ if rc.Token == "" {
+ rc.Token = NewId()
+ }
+
+ if rc.CreateAt == 0 {
+ rc.CreateAt = GetMillis()
+ }
+ rc.fixTopics()
+}
+
+func (rc *RemoteCluster) IsValid() *AppError {
+ if !IsValidId(rc.RemoteId) {
+ return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "id="+rc.RemoteId, http.StatusBadRequest)
+ }
+
+ if !IsValidRemoteName(rc.Name) {
+ return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.name.app_error", nil, "name="+rc.Name, http.StatusBadRequest)
+ }
+
+ if rc.CreateAt == 0 {
+ return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.create_at.app_error", nil, "create_at=0", http.StatusBadRequest)
+ }
+
+ if !IsValidId(rc.CreatorId) {
+ return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "creator_id="+rc.CreatorId, http.StatusBadRequest)
+ }
+ return nil
+}
+
+func IsValidRemoteName(s string) bool {
+ if len(s) < RemoteNameMinLength || len(s) > RemoteNameMaxLength {
+ return false
+ }
+ return validRemoteNameChars.MatchString(s)
+}
+
+func (rc *RemoteCluster) PreUpdate() {
+ if rc.DisplayName == "" {
+ rc.DisplayName = rc.Name
+ }
+
+ rc.Name = SanitizeUnicode(rc.Name)
+ rc.DisplayName = SanitizeUnicode(rc.DisplayName)
+ rc.Name = NormalizeRemoteName(rc.Name)
+ rc.fixTopics()
+}
+
+func (rc *RemoteCluster) IsOnline() bool {
+ return rc.LastPingAt > GetMillis()-RemoteOfflineAfterMillis
+}
+
+// fixTopics ensures all topics are separated by one, and only one, space.
+func (rc *RemoteCluster) fixTopics() {
+ trimmed := strings.TrimSpace(rc.Topics)
+ if trimmed == "" || trimmed == "*" {
+ rc.Topics = trimmed
+ return
+ }
+
+ var sb strings.Builder
+ sb.WriteString(" ")
+
+ ss := strings.Split(rc.Topics, " ")
+ for _, c := range ss {
+ cc := strings.TrimSpace(c)
+ if cc != "" {
+ sb.WriteString(cc)
+ sb.WriteString(" ")
+ }
+ }
+ rc.Topics = sb.String()
+}
+
+func (rc *RemoteCluster) ToRemoteClusterInfo() RemoteClusterInfo {
+ return RemoteClusterInfo{
+ Name: rc.Name,
+ DisplayName: rc.DisplayName,
+ CreateAt: rc.CreateAt,
+ LastPingAt: rc.LastPingAt,
+ }
+}
+
+func NormalizeRemoteName(name string) string {
+ return strings.ToLower(name)
+}
+
+// RemoteClusterInfo provides a subset of RemoteCluster fields suitable for sending to clients.
+type RemoteClusterInfo struct {
+ Name string `json:"name"`
+ DisplayName string `json:"display_name"`
+ CreateAt int64 `json:"create_at"`
+ LastPingAt int64 `json:"last_ping_at"`
+}
+
+// RemoteClusterFrame wraps a `RemoteClusterMsg` with credentials specific to a remote cluster.
+type RemoteClusterFrame struct {
+ RemoteId string `json:"remote_id"`
+ Msg RemoteClusterMsg `json:"msg"`
+}
+
+func (f *RemoteClusterFrame) IsValid() *AppError {
+ if !IsValidId(f.RemoteId) {
+ return NewAppError("RemoteClusterFrame.IsValid", "api.remote_cluster.invalid_id.app_error", nil, "RemoteId="+f.RemoteId, http.StatusBadRequest)
+ }
+
+ if err := f.Msg.IsValid(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// RemoteClusterMsg represents a message that is sent and received between clusters.
+// These are processed and routed via the RemoteClusters service.
+type RemoteClusterMsg struct {
+ Id string `json:"id"`
+ Topic string `json:"topic"`
+ CreateAt int64 `json:"create_at"`
+ Payload json.RawMessage `json:"payload"`
+}
+
+func NewRemoteClusterMsg(topic string, payload json.RawMessage) RemoteClusterMsg {
+ return RemoteClusterMsg{
+ Id: NewId(),
+ Topic: topic,
+ CreateAt: GetMillis(),
+ Payload: payload,
+ }
+}
+
+func (m RemoteClusterMsg) IsValid() *AppError {
+ if !IsValidId(m.Id) {
+ return NewAppError("RemoteClusterMsg.IsValid", "api.remote_cluster.invalid_id.app_error", nil, "Id="+m.Id, http.StatusBadRequest)
+ }
+
+ if m.Topic == "" {
+ return NewAppError("RemoteClusterMsg.IsValid", "api.remote_cluster.invalid_topic.app_error", nil, "Topic empty", http.StatusBadRequest)
+ }
+
+ if len(m.Payload) == 0 {
+ return NewAppError("RemoteClusterMsg.IsValid", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": "PayLoad"}, "", http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+// RemoteClusterPing represents a ping that is sent and received between clusters
+// to indicate a connection is alive. This is the payload for a `RemoteClusterMsg`.
+type RemoteClusterPing struct {
+ SentAt int64 `json:"sent_at"`
+ RecvAt int64 `json:"recv_at"`
+}
+
+// RemoteClusterInvite represents an invitation to establish a simple trust with a remote cluster.
+type RemoteClusterInvite struct {
+ RemoteId string `json:"remote_id"`
+ RemoteTeamId string `json:"remote_team_id"`
+ SiteURL string `json:"site_url"`
+ Token string `json:"token"`
+}
+
+func (rci *RemoteClusterInvite) Encrypt(password string) ([]byte, error) {
+ raw, err := json.Marshal(&rci)
+ if err != nil {
+ return nil, err
+ }
+
+ // create random salt to be prepended to the blob.
+ salt := make([]byte, 16)
+ if _, err = io.ReadFull(rand.Reader, salt); err != nil {
+ return nil, err
+ }
+
+ key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
+ if err != nil {
+ return nil, err
+ }
+
+ block, err := aes.NewCipher(key[:])
+ if err != nil {
+ return nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+
+ // create random nonce
+ nonce := make([]byte, gcm.NonceSize())
+ if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
+ return nil, err
+ }
+
+ // prefix the nonce to the cyphertext so we don't need to keep track of it.
+ sealed := gcm.Seal(nonce, nonce, raw, nil)
+
+ return append(salt, sealed...), nil
+}
+
+func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error {
+ if len(encrypted) <= 16 {
+ return errors.New("invalid length")
+ }
+
+ // first 16 bytes is the salt that was used to derive a key
+ salt := encrypted[:16]
+ encrypted = encrypted[16:]
+
+ key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
+ if err != nil {
+ return err
+ }
+
+ block, err := aes.NewCipher(key[:])
+ if err != nil {
+ return err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return err
+ }
+
+ // nonce was prefixed to the cyphertext when encrypting so we need to extract it.
+ nonceSize := gcm.NonceSize()
+ nonce, cyphertext := encrypted[:nonceSize], encrypted[nonceSize:]
+
+ plain, err := gcm.Open(nil, nonce, cyphertext, nil)
+ if err != nil {
+ return err
+ }
+
+ // try to unmarshall the decrypted JSON to this invite struct.
+ return json.Unmarshal(plain, &rci)
+}
+
+// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
+type RemoteClusterQueryFilter struct {
+ ExcludeOffline bool
+ InChannel string
+ NotInChannel string
+ Topic string
+ CreatorId string
+ OnlyConfirmed bool
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/role.go b/vendor/github.com/mattermost/mattermost-server/v6/model/role.go
new file mode 100644
index 00000000..68697838
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/role.go
@@ -0,0 +1,939 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "strings"
+)
+
+// SysconsoleAncillaryPermissions maps the non-sysconsole permissions required by each sysconsole view.
+var SysconsoleAncillaryPermissions map[string][]*Permission
+var SystemManagerDefaultPermissions []string
+var SystemUserManagerDefaultPermissions []string
+var SystemReadOnlyAdminDefaultPermissions []string
+
+var BuiltInSchemeManagedRoleIDs []string
+
+var NewSystemRoleIDs []string
+
+func init() {
+ NewSystemRoleIDs = []string{
+ SystemUserManagerRoleId,
+ SystemReadOnlyAdminRoleId,
+ SystemManagerRoleId,
+ }
+
+ BuiltInSchemeManagedRoleIDs = append([]string{
+ SystemGuestRoleId,
+ SystemUserRoleId,
+ SystemAdminRoleId,
+ SystemPostAllRoleId,
+ SystemPostAllPublicRoleId,
+ SystemUserAccessTokenRoleId,
+
+ TeamGuestRoleId,
+ TeamUserRoleId,
+ TeamAdminRoleId,
+ TeamPostAllRoleId,
+ TeamPostAllPublicRoleId,
+
+ ChannelGuestRoleId,
+ ChannelUserRoleId,
+ ChannelAdminRoleId,
+ }, NewSystemRoleIDs...)
+
+ // When updating the values here, the values in mattermost-redux must also be updated.
+ SysconsoleAncillaryPermissions = map[string][]*Permission{
+ PermissionSysconsoleReadAboutEditionAndLicense.Id: {
+ PermissionReadLicenseInformation,
+ },
+ PermissionSysconsoleWriteAboutEditionAndLicense.Id: {
+ PermissionManageLicenseInformation,
+ },
+ PermissionSysconsoleReadUserManagementChannels.Id: {
+ PermissionReadPublicChannel,
+ PermissionReadChannel,
+ PermissionReadPublicChannelGroups,
+ PermissionReadPrivateChannelGroups,
+ },
+ PermissionSysconsoleReadUserManagementUsers.Id: {
+ PermissionReadOtherUsersTeams,
+ PermissionGetAnalytics,
+ },
+ PermissionSysconsoleReadUserManagementTeams.Id: {
+ PermissionListPrivateTeams,
+ PermissionListPublicTeams,
+ PermissionViewTeam,
+ },
+ PermissionSysconsoleReadEnvironmentElasticsearch.Id: {
+ PermissionReadElasticsearchPostIndexingJob,
+ PermissionReadElasticsearchPostAggregationJob,
+ },
+ PermissionSysconsoleWriteEnvironmentWebServer.Id: {
+ PermissionTestSiteURL,
+ PermissionReloadConfig,
+ PermissionInvalidateCaches,
+ },
+ PermissionSysconsoleWriteEnvironmentDatabase.Id: {
+ PermissionRecycleDatabaseConnections,
+ },
+ PermissionSysconsoleWriteEnvironmentElasticsearch.Id: {
+ PermissionTestElasticsearch,
+ PermissionCreateElasticsearchPostIndexingJob,
+ PermissionCreateElasticsearchPostAggregationJob,
+ PermissionPurgeElasticsearchIndexes,
+ },
+ PermissionSysconsoleWriteEnvironmentFileStorage.Id: {
+ PermissionTestS3,
+ },
+ PermissionSysconsoleWriteEnvironmentSMTP.Id: {
+ PermissionTestEmail,
+ },
+ PermissionSysconsoleReadReportingServerLogs.Id: {
+ PermissionGetLogs,
+ },
+ PermissionSysconsoleReadReportingSiteStatistics.Id: {
+ PermissionGetAnalytics,
+ },
+ PermissionSysconsoleReadReportingTeamStatistics.Id: {
+ PermissionViewTeam,
+ },
+ PermissionSysconsoleWriteUserManagementUsers.Id: {
+ PermissionEditOtherUsers,
+ PermissionDemoteToGuest,
+ PermissionPromoteGuest,
+ },
+ PermissionSysconsoleWriteUserManagementChannels.Id: {
+ PermissionManageTeam,
+ PermissionManagePublicChannelProperties,
+ PermissionManagePrivateChannelProperties,
+ PermissionManagePrivateChannelMembers,
+ PermissionManagePublicChannelMembers,
+ PermissionDeletePrivateChannel,
+ PermissionDeletePublicChannel,
+ PermissionManageChannelRoles,
+ PermissionConvertPublicChannelToPrivate,
+ PermissionConvertPrivateChannelToPublic,
+ },
+ PermissionSysconsoleWriteUserManagementTeams.Id: {
+ PermissionManageTeam,
+ PermissionManageTeamRoles,
+ PermissionRemoveUserFromTeam,
+ PermissionJoinPrivateTeams,
+ PermissionJoinPublicTeams,
+ PermissionAddUserToTeam,
+ },
+ PermissionSysconsoleWriteUserManagementGroups.Id: {
+ PermissionManageTeam,
+ PermissionManagePrivateChannelMembers,
+ PermissionManagePublicChannelMembers,
+ PermissionConvertPublicChannelToPrivate,
+ PermissionConvertPrivateChannelToPublic,
+ },
+ PermissionSysconsoleWriteSiteCustomization.Id: {
+ PermissionEditBrand,
+ },
+ PermissionSysconsoleWriteComplianceDataRetentionPolicy.Id: {
+ PermissionCreateDataRetentionJob,
+ },
+ PermissionSysconsoleReadComplianceDataRetentionPolicy.Id: {
+ PermissionReadDataRetentionJob,
+ },
+ PermissionSysconsoleWriteComplianceComplianceExport.Id: {
+ PermissionCreateComplianceExportJob,
+ PermissionDownloadComplianceExportResult,
+ },
+ PermissionSysconsoleReadComplianceComplianceExport.Id: {
+ PermissionReadComplianceExportJob,
+ PermissionDownloadComplianceExportResult,
+ },
+ PermissionSysconsoleReadComplianceCustomTermsOfService.Id: {
+ PermissionReadAudits,
+ },
+ PermissionSysconsoleWriteExperimentalBleve.Id: {
+ PermissionCreatePostBleveIndexesJob,
+ PermissionPurgeBleveIndexes,
+ },
+ PermissionSysconsoleWriteAuthenticationLdap.Id: {
+ PermissionCreateLdapSyncJob,
+ PermissionAddLdapPublicCert,
+ PermissionRemoveLdapPublicCert,
+ PermissionAddLdapPrivateCert,
+ PermissionRemoveLdapPrivateCert,
+ },
+ PermissionSysconsoleReadAuthenticationLdap.Id: {
+ PermissionTestLdap,
+ PermissionReadLdapSyncJob,
+ },
+ PermissionSysconsoleWriteAuthenticationEmail.Id: {
+ PermissionInvalidateEmailInvite,
+ },
+ PermissionSysconsoleWriteAuthenticationSaml.Id: {
+ PermissionGetSamlMetadataFromIdp,
+ PermissionAddSamlPublicCert,
+ PermissionAddSamlPrivateCert,
+ PermissionAddSamlIdpCert,
+ PermissionRemoveSamlPublicCert,
+ PermissionRemoveSamlPrivateCert,
+ PermissionRemoveSamlIdpCert,
+ PermissionGetSamlCertStatus,
+ },
+ }
+
+ SystemUserManagerDefaultPermissions = []string{
+ PermissionSysconsoleReadUserManagementGroups.Id,
+ PermissionSysconsoleReadUserManagementTeams.Id,
+ PermissionSysconsoleReadUserManagementChannels.Id,
+ PermissionSysconsoleReadUserManagementPermissions.Id,
+ PermissionSysconsoleWriteUserManagementGroups.Id,
+ PermissionSysconsoleWriteUserManagementTeams.Id,
+ PermissionSysconsoleWriteUserManagementChannels.Id,
+ PermissionSysconsoleReadAuthenticationSignup.Id,
+ PermissionSysconsoleReadAuthenticationEmail.Id,
+ PermissionSysconsoleReadAuthenticationPassword.Id,
+ PermissionSysconsoleReadAuthenticationMfa.Id,
+ PermissionSysconsoleReadAuthenticationLdap.Id,
+ PermissionSysconsoleReadAuthenticationSaml.Id,
+ PermissionSysconsoleReadAuthenticationOpenid.Id,
+ PermissionSysconsoleReadAuthenticationGuestAccess.Id,
+ }
+
+ SystemReadOnlyAdminDefaultPermissions = []string{
+ PermissionSysconsoleReadAboutEditionAndLicense.Id,
+ PermissionSysconsoleReadReportingSiteStatistics.Id,
+ PermissionSysconsoleReadReportingTeamStatistics.Id,
+ PermissionSysconsoleReadReportingServerLogs.Id,
+ PermissionSysconsoleReadUserManagementUsers.Id,
+ PermissionSysconsoleReadUserManagementGroups.Id,
+ PermissionSysconsoleReadUserManagementTeams.Id,
+ PermissionSysconsoleReadUserManagementChannels.Id,
+ PermissionSysconsoleReadUserManagementPermissions.Id,
+ PermissionSysconsoleReadEnvironmentWebServer.Id,
+ PermissionSysconsoleReadEnvironmentDatabase.Id,
+ PermissionSysconsoleReadEnvironmentElasticsearch.Id,
+ PermissionSysconsoleReadEnvironmentFileStorage.Id,
+ PermissionSysconsoleReadEnvironmentImageProxy.Id,
+ PermissionSysconsoleReadEnvironmentSMTP.Id,
+ PermissionSysconsoleReadEnvironmentPushNotificationServer.Id,
+ PermissionSysconsoleReadEnvironmentHighAvailability.Id,
+ PermissionSysconsoleReadEnvironmentRateLimiting.Id,
+ PermissionSysconsoleReadEnvironmentLogging.Id,
+ PermissionSysconsoleReadEnvironmentSessionLengths.Id,
+ PermissionSysconsoleReadEnvironmentPerformanceMonitoring.Id,
+ PermissionSysconsoleReadEnvironmentDeveloper.Id,
+ PermissionSysconsoleReadSiteCustomization.Id,
+ PermissionSysconsoleReadSiteLocalization.Id,
+ PermissionSysconsoleReadSiteUsersAndTeams.Id,
+ PermissionSysconsoleReadSiteNotifications.Id,
+ PermissionSysconsoleReadSiteAnnouncementBanner.Id,
+ PermissionSysconsoleReadSiteEmoji.Id,
+ PermissionSysconsoleReadSitePosts.Id,
+ PermissionSysconsoleReadSiteFileSharingAndDownloads.Id,
+ PermissionSysconsoleReadSitePublicLinks.Id,
+ PermissionSysconsoleReadSiteNotices.Id,
+ PermissionSysconsoleReadAuthenticationSignup.Id,
+ PermissionSysconsoleReadAuthenticationEmail.Id,
+ PermissionSysconsoleReadAuthenticationPassword.Id,
+ PermissionSysconsoleReadAuthenticationMfa.Id,
+ PermissionSysconsoleReadAuthenticationLdap.Id,
+ PermissionSysconsoleReadAuthenticationSaml.Id,
+ PermissionSysconsoleReadAuthenticationOpenid.Id,
+ PermissionSysconsoleReadAuthenticationGuestAccess.Id,
+ PermissionSysconsoleReadPlugins.Id,
+ PermissionSysconsoleReadIntegrationsIntegrationManagement.Id,
+ PermissionSysconsoleReadIntegrationsBotAccounts.Id,
+ PermissionSysconsoleReadIntegrationsGif.Id,
+ PermissionSysconsoleReadIntegrationsCors.Id,
+ PermissionSysconsoleReadComplianceDataRetentionPolicy.Id,
+ PermissionSysconsoleReadComplianceComplianceExport.Id,
+ PermissionSysconsoleReadComplianceComplianceMonitoring.Id,
+ PermissionSysconsoleReadComplianceCustomTermsOfService.Id,
+ PermissionSysconsoleReadExperimentalFeatures.Id,
+ PermissionSysconsoleReadExperimentalFeatureFlags.Id,
+ PermissionSysconsoleReadExperimentalBleve.Id,
+ }
+
+ SystemManagerDefaultPermissions = []string{
+ PermissionSysconsoleReadAboutEditionAndLicense.Id,
+ PermissionSysconsoleReadReportingSiteStatistics.Id,
+ PermissionSysconsoleReadReportingTeamStatistics.Id,
+ PermissionSysconsoleReadReportingServerLogs.Id,
+ PermissionSysconsoleReadUserManagementGroups.Id,
+ PermissionSysconsoleReadUserManagementTeams.Id,
+ PermissionSysconsoleReadUserManagementChannels.Id,
+ PermissionSysconsoleReadUserManagementPermissions.Id,
+ PermissionSysconsoleWriteUserManagementGroups.Id,
+ PermissionSysconsoleWriteUserManagementTeams.Id,
+ PermissionSysconsoleWriteUserManagementChannels.Id,
+ PermissionSysconsoleWriteUserManagementPermissions.Id,
+ PermissionSysconsoleReadEnvironmentWebServer.Id,
+ PermissionSysconsoleReadEnvironmentDatabase.Id,
+ PermissionSysconsoleReadEnvironmentElasticsearch.Id,
+ PermissionSysconsoleReadEnvironmentFileStorage.Id,
+ PermissionSysconsoleReadEnvironmentImageProxy.Id,
+ PermissionSysconsoleReadEnvironmentSMTP.Id,
+ PermissionSysconsoleReadEnvironmentPushNotificationServer.Id,
+ PermissionSysconsoleReadEnvironmentHighAvailability.Id,
+ PermissionSysconsoleReadEnvironmentRateLimiting.Id,
+ PermissionSysconsoleReadEnvironmentLogging.Id,
+ PermissionSysconsoleReadEnvironmentSessionLengths.Id,
+ PermissionSysconsoleReadEnvironmentPerformanceMonitoring.Id,
+ PermissionSysconsoleReadEnvironmentDeveloper.Id,
+ PermissionSysconsoleWriteEnvironmentWebServer.Id,
+ PermissionSysconsoleWriteEnvironmentDatabase.Id,
+ PermissionSysconsoleWriteEnvironmentElasticsearch.Id,
+ PermissionSysconsoleWriteEnvironmentFileStorage.Id,
+ PermissionSysconsoleWriteEnvironmentImageProxy.Id,
+ PermissionSysconsoleWriteEnvironmentSMTP.Id,
+ PermissionSysconsoleWriteEnvironmentPushNotificationServer.Id,
+ PermissionSysconsoleWriteEnvironmentHighAvailability.Id,
+ PermissionSysconsoleWriteEnvironmentRateLimiting.Id,
+ PermissionSysconsoleWriteEnvironmentLogging.Id,
+ PermissionSysconsoleWriteEnvironmentSessionLengths.Id,
+ PermissionSysconsoleWriteEnvironmentPerformanceMonitoring.Id,
+ PermissionSysconsoleWriteEnvironmentDeveloper.Id,
+ PermissionSysconsoleReadSiteCustomization.Id,
+ PermissionSysconsoleWriteSiteCustomization.Id,
+ PermissionSysconsoleReadSiteLocalization.Id,
+ PermissionSysconsoleWriteSiteLocalization.Id,
+ PermissionSysconsoleReadSiteUsersAndTeams.Id,
+ PermissionSysconsoleWriteSiteUsersAndTeams.Id,
+ PermissionSysconsoleReadSiteNotifications.Id,
+ PermissionSysconsoleWriteSiteNotifications.Id,
+ PermissionSysconsoleReadSiteAnnouncementBanner.Id,
+ PermissionSysconsoleWriteSiteAnnouncementBanner.Id,
+ PermissionSysconsoleReadSiteEmoji.Id,
+ PermissionSysconsoleWriteSiteEmoji.Id,
+ PermissionSysconsoleReadSitePosts.Id,
+ PermissionSysconsoleWriteSitePosts.Id,
+ PermissionSysconsoleReadSiteFileSharingAndDownloads.Id,
+ PermissionSysconsoleWriteSiteFileSharingAndDownloads.Id,
+ PermissionSysconsoleReadSitePublicLinks.Id,
+ PermissionSysconsoleWriteSitePublicLinks.Id,
+ PermissionSysconsoleReadSiteNotices.Id,
+ PermissionSysconsoleWriteSiteNotices.Id,
+ PermissionSysconsoleReadAuthenticationSignup.Id,
+ PermissionSysconsoleReadAuthenticationEmail.Id,
+ PermissionSysconsoleReadAuthenticationPassword.Id,
+ PermissionSysconsoleReadAuthenticationMfa.Id,
+ PermissionSysconsoleReadAuthenticationLdap.Id,
+ PermissionSysconsoleReadAuthenticationSaml.Id,
+ PermissionSysconsoleReadAuthenticationOpenid.Id,
+ PermissionSysconsoleReadAuthenticationGuestAccess.Id,
+ PermissionSysconsoleReadPlugins.Id,
+ PermissionSysconsoleReadIntegrationsIntegrationManagement.Id,
+ PermissionSysconsoleReadIntegrationsBotAccounts.Id,
+ PermissionSysconsoleReadIntegrationsGif.Id,
+ PermissionSysconsoleReadIntegrationsCors.Id,
+ PermissionSysconsoleWriteIntegrationsIntegrationManagement.Id,
+ PermissionSysconsoleWriteIntegrationsBotAccounts.Id,
+ PermissionSysconsoleWriteIntegrationsGif.Id,
+ PermissionSysconsoleWriteIntegrationsCors.Id,
+ }
+
+ // Add the ancillary permissions to each system role
+ SystemUserManagerDefaultPermissions = AddAncillaryPermissions(SystemUserManagerDefaultPermissions)
+ SystemReadOnlyAdminDefaultPermissions = AddAncillaryPermissions(SystemReadOnlyAdminDefaultPermissions)
+ SystemManagerDefaultPermissions = AddAncillaryPermissions(SystemManagerDefaultPermissions)
+}
+
+type RoleType string
+type RoleScope string
+
+const (
+ SystemGuestRoleId = "system_guest"
+ SystemUserRoleId = "system_user"
+ SystemAdminRoleId = "system_admin"
+ SystemPostAllRoleId = "system_post_all"
+ SystemPostAllPublicRoleId = "system_post_all_public"
+ SystemUserAccessTokenRoleId = "system_user_access_token"
+ SystemUserManagerRoleId = "system_user_manager"
+ SystemReadOnlyAdminRoleId = "system_read_only_admin"
+ SystemManagerRoleId = "system_manager"
+
+ TeamGuestRoleId = "team_guest"
+ TeamUserRoleId = "team_user"
+ TeamAdminRoleId = "team_admin"
+ TeamPostAllRoleId = "team_post_all"
+ TeamPostAllPublicRoleId = "team_post_all_public"
+
+ ChannelGuestRoleId = "channel_guest"
+ ChannelUserRoleId = "channel_user"
+ ChannelAdminRoleId = "channel_admin"
+
+ RoleNameMaxLength = 64
+ RoleDisplayNameMaxLength = 128
+ RoleDescriptionMaxLength = 1024
+
+ RoleScopeSystem RoleScope = "System"
+ RoleScopeTeam RoleScope = "Team"
+ RoleScopeChannel RoleScope = "Channel"
+
+ RoleTypeGuest RoleType = "Guest"
+ RoleTypeUser RoleType = "User"
+ RoleTypeAdmin RoleType = "Admin"
+)
+
+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"`
+}
+
+type RolePermissions struct {
+ RoleID string
+ Permissions []string
+}
+
+func (r *Role) Patch(patch *RolePatch) {
+ if patch.Permissions != nil {
+ r.Permissions = *patch.Permissions
+ }
+}
+
+// MergeChannelHigherScopedPermissions is meant to be invoked on a channel scheme's role and merges the higher-scoped
+// channel role's permissions.
+func (r *Role) MergeChannelHigherScopedPermissions(higherScopedPermissions *RolePermissions) {
+ mergedPermissions := []string{}
+
+ higherScopedPermissionsMap := asStringBoolMap(higherScopedPermissions.Permissions)
+ rolePermissionsMap := asStringBoolMap(r.Permissions)
+
+ for _, cp := range AllPermissions {
+ if cp.Scope != PermissionScopeChannel {
+ continue
+ }
+
+ _, presentOnHigherScope := higherScopedPermissionsMap[cp.Id]
+
+ // For the channel admin role always look to the higher scope to determine if the role has their permission.
+ // The channel admin is a special case because they're not part of the UI to be "channel moderated", only
+ // channel members and channel guests are.
+ if higherScopedPermissions.RoleID == ChannelAdminRoleId && presentOnHigherScope {
+ mergedPermissions = append(mergedPermissions, cp.Id)
+ continue
+ }
+
+ _, permissionIsModerated := ChannelModeratedPermissionsMap[cp.Id]
+ if permissionIsModerated {
+ _, presentOnRole := rolePermissionsMap[cp.Id]
+ if presentOnRole && presentOnHigherScope {
+ mergedPermissions = append(mergedPermissions, cp.Id)
+ }
+ } else {
+ if presentOnHigherScope {
+ mergedPermissions = append(mergedPermissions, cp.Id)
+ }
+ }
+ }
+
+ r.Permissions = mergedPermissions
+}
+
+// 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 ChannelModeratedPermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
+ var result []string
+
+ if role == nil {
+ return result
+ }
+
+ if patch.Permissions == nil {
+ return result
+ }
+
+ roleMap := make(map[string]bool)
+ patchMap := make(map[string]bool)
+
+ for _, permission := range role.Permissions {
+ if channelModeratedPermissionName, found := ChannelModeratedPermissionsMap[permission]; found {
+ roleMap[channelModeratedPermissionName] = true
+ }
+ }
+
+ for _, permission := range *patch.Permissions {
+ if channelModeratedPermissionName, found := ChannelModeratedPermissionsMap[permission]; found {
+ patchMap[channelModeratedPermissionName] = true
+ }
+ }
+
+ for permissionKey := range roleMap {
+ if !patchMap[permissionKey] {
+ result = append(result, permissionKey)
+ }
+ }
+
+ for permissionKey := range patchMap {
+ if !roleMap[permissionKey] {
+ result = append(result, permissionKey)
+ }
+ }
+
+ return result
+}
+
+// GetChannelModeratedPermissions returns a map of channel moderated permissions that the role has access to
+func (r *Role) GetChannelModeratedPermissions(channelType ChannelType) map[string]bool {
+ moderatedPermissions := make(map[string]bool)
+ for _, permission := range r.Permissions {
+ if _, found := ChannelModeratedPermissionsMap[permission]; !found {
+ continue
+ }
+
+ for moderated, moderatedPermissionValue := range ChannelModeratedPermissionsMap {
+ // the moderated permission has already been found to be true so skip this iteration
+ if moderatedPermissions[moderatedPermissionValue] {
+ continue
+ }
+
+ if moderated == permission {
+ // Special case where the channel moderated permission for `manage_members` is different depending on whether the channel is private or public
+ if moderated == PermissionManagePublicChannelMembers.Id || moderated == PermissionManagePrivateChannelMembers.Id {
+ canManagePublic := channelType == ChannelTypeOpen && moderated == PermissionManagePublicChannelMembers.Id
+ canManagePrivate := channelType == ChannelTypePrivate && moderated == PermissionManagePrivateChannelMembers.Id
+ moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
+ } else {
+ moderatedPermissions[moderatedPermissionValue] = true
+ }
+ }
+ }
+ }
+
+ return moderatedPermissions
+}
+
+// RolePatchFromChannelModerationsPatch Creates and returns a RolePatch based on a slice of ChannelModerationPatchs, roleName is expected to be either "members" or "guests".
+func (r *Role) RolePatchFromChannelModerationsPatch(channelModerationsPatch []*ChannelModerationPatch, roleName string) *RolePatch {
+ permissionsToAddToPatch := make(map[string]bool)
+
+ // Iterate through the list of existing permissions on the role and append permissions that we want to keep.
+ for _, permission := range r.Permissions {
+ // Permission is not moderated so dont add it to the patch and skip the channelModerationsPatch
+ if _, isModerated := ChannelModeratedPermissionsMap[permission]; !isModerated {
+ continue
+ }
+
+ permissionEnabled := true
+ // Check if permission has a matching moderated permission name inside the channel moderation patch
+ for _, channelModerationPatch := range channelModerationsPatch {
+ if *channelModerationPatch.Name == ChannelModeratedPermissionsMap[permission] {
+ // Permission key exists in patch with a value of false so skip over it
+ if roleName == "members" {
+ if channelModerationPatch.Roles.Members != nil && !*channelModerationPatch.Roles.Members {
+ permissionEnabled = false
+ }
+ } else if roleName == "guests" {
+ if channelModerationPatch.Roles.Guests != nil && !*channelModerationPatch.Roles.Guests {
+ permissionEnabled = false
+ }
+ }
+ }
+ }
+
+ if permissionEnabled {
+ permissionsToAddToPatch[permission] = true
+ }
+ }
+
+ // Iterate through the patch and add any permissions that dont already exist on the role
+ for _, channelModerationPatch := range channelModerationsPatch {
+ for permission, moderatedPermissionName := range ChannelModeratedPermissionsMap {
+ if roleName == "members" && channelModerationPatch.Roles.Members != nil && *channelModerationPatch.Roles.Members && *channelModerationPatch.Name == moderatedPermissionName {
+ permissionsToAddToPatch[permission] = true
+ }
+
+ if roleName == "guests" && channelModerationPatch.Roles.Guests != nil && *channelModerationPatch.Roles.Guests && *channelModerationPatch.Name == moderatedPermissionName {
+ permissionsToAddToPatch[permission] = true
+ }
+ }
+ }
+
+ patchPermissions := make([]string, 0, len(permissionsToAddToPatch))
+ for permission := range permissionsToAddToPatch {
+ patchPermissions = append(patchPermissions, permission)
+ }
+
+ return &RolePatch{Permissions: &patchPermissions}
+}
+
+func (r *Role) IsValid() bool {
+ if !IsValidId(r.Id) {
+ return false
+ }
+
+ return r.IsValidWithoutId()
+}
+
+func (r *Role) IsValidWithoutId() bool {
+ if !IsValidRoleName(r.Name) {
+ return false
+ }
+
+ if r.DisplayName == "" || len(r.DisplayName) > RoleDisplayNameMaxLength {
+ return false
+ }
+
+ if len(r.Description) > RoleDescriptionMaxLength {
+ return false
+ }
+
+ check := func(perms []*Permission, permission string) bool {
+ for _, p := range perms {
+ if permission == p.Id {
+ return true
+ }
+ }
+ return false
+ }
+ for _, permission := range r.Permissions {
+ permissionValidated := check(AllPermissions, permission) || check(DeprecatedPermissions, permission)
+ if !permissionValidated {
+ return false
+ }
+ }
+
+ return true
+}
+
+func CleanRoleNames(roleNames []string) ([]string, bool) {
+ var cleanedRoleNames []string
+ for _, roleName := range roleNames {
+ if strings.TrimSpace(roleName) == "" {
+ continue
+ }
+
+ if !IsValidRoleName(roleName) {
+ return roleNames, false
+ }
+
+ cleanedRoleNames = append(cleanedRoleNames, roleName)
+ }
+
+ return cleanedRoleNames, true
+}
+
+func IsValidRoleName(roleName string) bool {
+ if roleName == "" || len(roleName) > RoleNameMaxLength {
+ return false
+ }
+
+ if strings.TrimLeft(roleName, "abcdefghijklmnopqrstuvwxyz0123456789_") != "" {
+ return false
+ }
+
+ return true
+}
+
+func MakeDefaultRoles() map[string]*Role {
+ roles := make(map[string]*Role)
+
+ roles[ChannelGuestRoleId] = &Role{
+ Name: "channel_guest",
+ DisplayName: "authentication.roles.channel_guest.name",
+ Description: "authentication.roles.channel_guest.description",
+ Permissions: []string{
+ PermissionReadChannel.Id,
+ PermissionAddReaction.Id,
+ PermissionRemoveReaction.Id,
+ PermissionUploadFile.Id,
+ PermissionEditPost.Id,
+ PermissionCreatePost.Id,
+ PermissionUseChannelMentions.Id,
+ PermissionUseSlashCommands.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[ChannelUserRoleId] = &Role{
+ Name: "channel_user",
+ DisplayName: "authentication.roles.channel_user.name",
+ Description: "authentication.roles.channel_user.description",
+ Permissions: []string{
+ PermissionReadChannel.Id,
+ PermissionAddReaction.Id,
+ PermissionRemoveReaction.Id,
+ PermissionManagePublicChannelMembers.Id,
+ PermissionUploadFile.Id,
+ PermissionGetPublicLink.Id,
+ PermissionCreatePost.Id,
+ PermissionUseChannelMentions.Id,
+ PermissionUseSlashCommands.Id,
+ PermissionManagePublicChannelProperties.Id,
+ PermissionDeletePublicChannel.Id,
+ PermissionManagePrivateChannelProperties.Id,
+ PermissionDeletePrivateChannel.Id,
+ PermissionManagePrivateChannelMembers.Id,
+ PermissionDeletePost.Id,
+ PermissionEditPost.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[ChannelAdminRoleId] = &Role{
+ Name: "channel_admin",
+ DisplayName: "authentication.roles.channel_admin.name",
+ Description: "authentication.roles.channel_admin.description",
+ Permissions: []string{
+ PermissionManageChannelRoles.Id,
+ PermissionUseGroupMentions.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[TeamGuestRoleId] = &Role{
+ Name: "team_guest",
+ DisplayName: "authentication.roles.team_guest.name",
+ Description: "authentication.roles.team_guest.description",
+ Permissions: []string{
+ PermissionViewTeam.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[TeamUserRoleId] = &Role{
+ Name: "team_user",
+ DisplayName: "authentication.roles.team_user.name",
+ Description: "authentication.roles.team_user.description",
+ Permissions: []string{
+ PermissionListTeamChannels.Id,
+ PermissionJoinPublicChannels.Id,
+ PermissionReadPublicChannel.Id,
+ PermissionViewTeam.Id,
+ PermissionCreatePublicChannel.Id,
+ PermissionCreatePrivateChannel.Id,
+ PermissionInviteUser.Id,
+ PermissionAddUserToTeam.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[TeamPostAllRoleId] = &Role{
+ Name: "team_post_all",
+ DisplayName: "authentication.roles.team_post_all.name",
+ Description: "authentication.roles.team_post_all.description",
+ Permissions: []string{
+ PermissionCreatePost.Id,
+ PermissionUseChannelMentions.Id,
+ },
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[TeamPostAllPublicRoleId] = &Role{
+ Name: "team_post_all_public",
+ DisplayName: "authentication.roles.team_post_all_public.name",
+ Description: "authentication.roles.team_post_all_public.description",
+ Permissions: []string{
+ PermissionCreatePostPublic.Id,
+ PermissionUseChannelMentions.Id,
+ },
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[TeamAdminRoleId] = &Role{
+ Name: "team_admin",
+ DisplayName: "authentication.roles.team_admin.name",
+ Description: "authentication.roles.team_admin.description",
+ Permissions: []string{
+ PermissionRemoveUserFromTeam.Id,
+ PermissionManageTeam.Id,
+ PermissionImportTeam.Id,
+ PermissionManageTeamRoles.Id,
+ PermissionManageChannelRoles.Id,
+ PermissionManageOthersIncomingWebhooks.Id,
+ PermissionManageOthersOutgoingWebhooks.Id,
+ PermissionManageSlashCommands.Id,
+ PermissionManageOthersSlashCommands.Id,
+ PermissionManageIncomingWebhooks.Id,
+ PermissionManageOutgoingWebhooks.Id,
+ PermissionConvertPublicChannelToPrivate.Id,
+ PermissionConvertPrivateChannelToPublic.Id,
+ PermissionDeletePost.Id,
+ PermissionDeleteOthersPosts.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[SystemGuestRoleId] = &Role{
+ Name: "system_guest",
+ DisplayName: "authentication.roles.global_guest.name",
+ Description: "authentication.roles.global_guest.description",
+ Permissions: []string{
+ PermissionCreateDirectChannel.Id,
+ PermissionCreateGroupChannel.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[SystemUserRoleId] = &Role{
+ Name: "system_user",
+ DisplayName: "authentication.roles.global_user.name",
+ Description: "authentication.roles.global_user.description",
+ Permissions: []string{
+ PermissionListPublicTeams.Id,
+ PermissionJoinPublicTeams.Id,
+ PermissionCreateDirectChannel.Id,
+ PermissionCreateGroupChannel.Id,
+ PermissionViewMembers.Id,
+ PermissionCreateTeam.Id,
+ },
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ roles[SystemPostAllRoleId] = &Role{
+ Name: "system_post_all",
+ DisplayName: "authentication.roles.system_post_all.name",
+ Description: "authentication.roles.system_post_all.description",
+ Permissions: []string{
+ PermissionCreatePost.Id,
+ PermissionUseChannelMentions.Id,
+ },
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[SystemPostAllPublicRoleId] = &Role{
+ Name: "system_post_all_public",
+ DisplayName: "authentication.roles.system_post_all_public.name",
+ Description: "authentication.roles.system_post_all_public.description",
+ Permissions: []string{
+ PermissionCreatePostPublic.Id,
+ PermissionUseChannelMentions.Id,
+ },
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[SystemUserAccessTokenRoleId] = &Role{
+ Name: "system_user_access_token",
+ DisplayName: "authentication.roles.system_user_access_token.name",
+ Description: "authentication.roles.system_user_access_token.description",
+ Permissions: []string{
+ PermissionCreateUserAccessToken.Id,
+ PermissionReadUserAccessToken.Id,
+ PermissionRevokeUserAccessToken.Id,
+ },
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[SystemUserManagerRoleId] = &Role{
+ Name: "system_user_manager",
+ DisplayName: "authentication.roles.system_user_manager.name",
+ Description: "authentication.roles.system_user_manager.description",
+ Permissions: SystemUserManagerDefaultPermissions,
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[SystemReadOnlyAdminRoleId] = &Role{
+ Name: "system_read_only_admin",
+ DisplayName: "authentication.roles.system_read_only_admin.name",
+ Description: "authentication.roles.system_read_only_admin.description",
+ Permissions: SystemReadOnlyAdminDefaultPermissions,
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ roles[SystemManagerRoleId] = &Role{
+ Name: "system_manager",
+ DisplayName: "authentication.roles.system_manager.name",
+ Description: "authentication.roles.system_manager.description",
+ Permissions: SystemManagerDefaultPermissions,
+ SchemeManaged: false,
+ BuiltIn: true,
+ }
+
+ allPermissionIDs := []string{}
+ for _, permission := range AllPermissions {
+ allPermissionIDs = append(allPermissionIDs, permission.Id)
+ }
+
+ roles[SystemAdminRoleId] = &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: allPermissionIDs,
+ SchemeManaged: true,
+ BuiltIn: true,
+ }
+
+ return roles
+}
+
+func AddAncillaryPermissions(permissions []string) []string {
+ for _, permission := range permissions {
+ if ancillaryPermissions, ok := SysconsoleAncillaryPermissions[permission]; ok {
+ for _, ancillaryPermission := range ancillaryPermissions {
+ permissions = append(permissions, ancillaryPermission.Id)
+ }
+ }
+ }
+ return permissions
+}
+
+func asStringBoolMap(list []string) map[string]bool {
+ listMap := make(map[string]bool, len(list))
+ for _, p := range list {
+ listMap[p] = true
+ }
+ return listMap
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/saml.go b/vendor/github.com/mattermost/mattermost-server/v6/model/saml.go
new file mode 100644
index 00000000..e9e987d8
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/saml.go
@@ -0,0 +1,176 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/xml"
+ "time"
+)
+
+const (
+ UserAuthServiceSaml = "saml"
+ UserAuthServiceSamlText = "SAML"
+ UserAuthServiceIsSaml = "isSaml"
+ UserAuthServiceIsMobile = "isMobile"
+ UserAuthServiceIsOAuth = "isOAuthUser"
+)
+
+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"`
+}
+
+type SamlMetadataResponse struct {
+ IdpDescriptorURL string `json:"idp_descriptor_url"`
+ IdpURL string `json:"idp_url"`
+ IdpPublicCertificate string `json:"idp_public_certificate"`
+}
+
+type NameIDFormat struct {
+ XMLName xml.Name
+ Format string `xml:",attr,omitempty"`
+ Value string `xml:",innerxml"`
+}
+
+type NameID struct {
+ NameQualifier string `xml:",attr"`
+ SPNameQualifier string `xml:",attr"`
+ Format string `xml:",attr,omitempty"`
+ SPProvidedID string `xml:",attr"`
+ Value string `xml:",chardata"`
+}
+
+type AttributeValue struct {
+ Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
+ Value string `xml:",chardata"`
+ NameID *NameID
+}
+
+type Attribute struct {
+ XMLName xml.Name
+ FriendlyName string `xml:",attr"`
+ Name string `xml:",attr"`
+ NameFormat string `xml:",attr"`
+ Values []AttributeValue `xml:"AttributeValue"`
+}
+
+type Endpoint struct {
+ XMLName xml.Name
+ Binding string `xml:"Binding,attr"`
+ Location string `xml:"Location,attr"`
+ ResponseLocation string `xml:"ResponseLocation,attr,omitempty"`
+}
+
+type IndexedEndpoint struct {
+ XMLName xml.Name
+ Binding string `xml:"Binding,attr"`
+ Location string `xml:"Location,attr"`
+ ResponseLocation *string `xml:"ResponseLocation,attr,omitempty"`
+ Index int `xml:"index,attr"`
+ IsDefault *bool `xml:"isDefault,attr"`
+}
+
+type IDPSSODescriptor struct {
+ XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
+ SSODescriptor
+ WantAuthnRequestsSigned *bool `xml:",attr"`
+
+ SingleSignOnServices []Endpoint `xml:"SingleSignOnService"`
+ NameIDMappingServices []Endpoint `xml:"NameIDMappingService"`
+ AssertionIDRequestServices []Endpoint `xml:"AssertionIDRequestService"`
+ AttributeProfiles []string `xml:"AttributeProfile"`
+ Attributes []Attribute `xml:"Attribute"`
+}
+
+type SSODescriptor struct {
+ XMLName xml.Name
+ RoleDescriptor
+ ArtifactResolutionServices []IndexedEndpoint `xml:"ArtifactResolutionService"`
+ SingleLogoutServices []Endpoint `xml:"SingleLogoutService"`
+ ManageNameIDServices []Endpoint `xml:"ManageNameIDService"`
+ NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
+}
+
+type X509Certificate struct {
+ XMLName xml.Name
+ Cert string `xml:",innerxml"`
+}
+
+type X509Data struct {
+ XMLName xml.Name
+ X509Certificate X509Certificate `xml:"X509Certificate"`
+}
+
+type KeyInfo struct {
+ XMLName xml.Name
+ DS string `xml:"xmlns:ds,attr"`
+ X509Data X509Data `xml:"X509Data"`
+}
+type EncryptionMethod struct {
+ Algorithm string `xml:"Algorithm,attr"`
+}
+
+type KeyDescriptor struct {
+ XMLName xml.Name
+ Use string `xml:"use,attr,omitempty"`
+ KeyInfo KeyInfo `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo,omitempty"`
+}
+
+type RoleDescriptor struct {
+ XMLName xml.Name
+ ID string `xml:",attr,omitempty"`
+ ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
+ CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
+ ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
+ ErrorURL string `xml:"errorURL,attr,omitempty"`
+ KeyDescriptors []KeyDescriptor `xml:"KeyDescriptor,omitempty"`
+ Organization *Organization `xml:"Organization,omitempty"`
+ ContactPersons []ContactPerson `xml:"ContactPerson,omitempty"`
+}
+
+type ContactPerson struct {
+ XMLName xml.Name
+ ContactType string `xml:"contactType,attr"`
+ Company string
+ GivenName string
+ SurName string
+ EmailAddresses []string `xml:"EmailAddress"`
+ TelephoneNumbers []string `xml:"TelephoneNumber"`
+}
+
+type LocalizedName struct {
+ Lang string `xml:"xml lang,attr"`
+ Value string `xml:",chardata"`
+}
+
+type LocalizedURI struct {
+ Lang string `xml:"xml lang,attr"`
+ Value string `xml:",chardata"`
+}
+
+type Organization struct {
+ XMLName xml.Name
+ OrganizationNames []LocalizedName `xml:"OrganizationName"`
+ OrganizationDisplayNames []LocalizedName `xml:"OrganizationDisplayName"`
+ OrganizationURLs []LocalizedURI `xml:"OrganizationURL"`
+}
+
+type EntityDescriptor struct {
+ XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata EntityDescriptor"`
+ EntityID string `xml:"entityID,attr"`
+ ID string `xml:",attr,omitempty"`
+ ValidUntil time.Time `xml:"validUntil,attr,omitempty"`
+ CacheDuration time.Duration `xml:"cacheDuration,attr,omitempty"`
+ RoleDescriptors []RoleDescriptor `xml:"RoleDescriptor"`
+ IDPSSODescriptors []IDPSSODescriptor `xml:"IDPSSODescriptor"`
+ Organization Organization `xml:"Organization"`
+ ContactPerson ContactPerson `xml:"ContactPerson"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/scheduled_task.go b/vendor/github.com/mattermost/mattermost-server/v6/model/scheduled_task.go
new file mode 100644
index 00000000..cf20db63
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/scheduled_task.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2015-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{}
+ fromNextIntervalTime bool
+}
+
+func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
+ return createTask(name, function, timeToExecution, false, false)
+}
+
+func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
+ return createTask(name, function, interval, true, false)
+}
+
+func CreateRecurringTaskFromNextIntervalTime(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
+ return createTask(name, function, interval, true, true)
+}
+
+func createTask(name string, function TaskFunc, interval time.Duration, recurring bool, fromNextIntervalTime bool) *ScheduledTask {
+ task := &ScheduledTask{
+ Name: name,
+ Interval: interval,
+ Recurring: recurring,
+ function: function,
+ cancel: make(chan struct{}),
+ cancelled: make(chan struct{}),
+ fromNextIntervalTime: fromNextIntervalTime,
+ }
+
+ go func() {
+ defer close(task.cancelled)
+
+ var firstTick <-chan time.Time
+ var ticker *time.Ticker
+
+ if task.fromNextIntervalTime {
+ currTime := time.Now()
+ first := currTime.Truncate(interval)
+ if first.Before(currTime) {
+ first = first.Add(interval)
+ }
+ firstTick = time.After(time.Until(first))
+ ticker = &time.Ticker{C: nil}
+ } else {
+ firstTick = nil
+ ticker = time.NewTicker(interval)
+ }
+ defer func() {
+ ticker.Stop()
+ }()
+
+ for {
+ select {
+ case <-firstTick:
+ ticker = time.NewTicker(interval)
+ function()
+ 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/v6/model/scheme.go b/vendor/github.com/mattermost/mattermost-server/v6/model/scheme.go
new file mode 100644
index 00000000..c861c1e6
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/scheme.go
@@ -0,0 +1,167 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "regexp"
+)
+
+const (
+ SchemeDisplayNameMaxLength = 128
+ SchemeNameMaxLength = 64
+ SchemeDescriptionMaxLength = 1024
+ SchemeScopeTeam = "team"
+ SchemeScopeChannel = "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"`
+ DefaultTeamGuestRole string `json:"default_team_guest_role"`
+ DefaultChannelGuestRole string `json:"default_channel_guest_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"`
+ TeamGuest string `json:"default_team_guest_role"`
+ ChannelAdmin string `json:"default_channel_admin_role"`
+ ChannelUser string `json:"default_channel_user_role"`
+ ChannelGuest string `json:"default_channel_guest_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,
+ DefaultTeamGuestRole: sc.TeamGuest,
+ DefaultChannelAdminRole: sc.ChannelAdmin,
+ DefaultChannelUserRole: sc.ChannelUser,
+ DefaultChannelGuestRole: sc.ChannelGuest,
+ }
+}
+
+type SchemeRoles struct {
+ SchemeAdmin bool `json:"scheme_admin"`
+ SchemeUser bool `json:"scheme_user"`
+ SchemeGuest bool `json:"scheme_guest"`
+}
+
+func (scheme *Scheme) IsValid() bool {
+ if !IsValidId(scheme.Id) {
+ return false
+ }
+
+ return scheme.IsValidForCreate()
+}
+
+func (scheme *Scheme) IsValidForCreate() bool {
+ if scheme.DisplayName == "" || len(scheme.DisplayName) > SchemeDisplayNameMaxLength {
+ return false
+ }
+
+ if !IsValidSchemeName(scheme.Name) {
+ return false
+ }
+
+ if len(scheme.Description) > SchemeDescriptionMaxLength {
+ return false
+ }
+
+ switch scheme.Scope {
+ case SchemeScopeTeam, SchemeScopeChannel:
+ default:
+ return false
+ }
+
+ if !IsValidRoleName(scheme.DefaultChannelAdminRole) {
+ return false
+ }
+
+ if !IsValidRoleName(scheme.DefaultChannelUserRole) {
+ return false
+ }
+
+ if !IsValidRoleName(scheme.DefaultChannelGuestRole) {
+ return false
+ }
+
+ if scheme.Scope == SchemeScopeTeam {
+ if !IsValidRoleName(scheme.DefaultTeamAdminRole) {
+ return false
+ }
+
+ if !IsValidRoleName(scheme.DefaultTeamUserRole) {
+ return false
+ }
+
+ if !IsValidRoleName(scheme.DefaultTeamGuestRole) {
+ return false
+ }
+ }
+
+ if scheme.Scope == SchemeScopeChannel {
+ if scheme.DefaultTeamAdminRole != "" {
+ return false
+ }
+
+ if scheme.DefaultTeamUserRole != "" {
+ return false
+ }
+
+ if scheme.DefaultTeamGuestRole != "" {
+ 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 IsValidSchemeName(name string) bool {
+ re := regexp.MustCompile(fmt.Sprintf("^[a-z0-9_]{2,%d}$", SchemeNameMaxLength))
+ return re.MatchString(name)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/search_params.go b/vendor/github.com/mattermost/mattermost-server/v6/model/search_params.go
new file mode 100644
index 00000000..41a2db2a
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/search_params.go
@@ -0,0 +1,397 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "regexp"
+ "strings"
+ "time"
+)
+
+var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`)
+var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`)
+
+type SearchParams struct {
+ Terms string
+ ExcludedTerms string
+ IsHashtag bool
+ InChannels []string
+ ExcludedChannels []string
+ FromUsers []string
+ ExcludedUsers []string
+ AfterDate string
+ ExcludedAfterDate string
+ BeforeDate string
+ ExcludedBeforeDate string
+ Extensions []string
+ ExcludedExtensions []string
+ OnDate string
+ ExcludedDate string
+ OrTerms bool
+ IncludeDeletedChannels bool
+ TimeZoneOffset int
+ // True if this search doesn't originate from a "current user".
+ SearchWithoutUserId bool
+}
+
+// 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 start of the day specified by SearchParams.ExcludedAfterDate
+func (p *SearchParams) GetExcludedAfterDateMillis() int64 {
+ date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedAfterDate))
+ 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 timestamp of the end of the day specified by SearchParams.ExcludedBeforeDate
+func (p *SearchParams) GetExcludedBeforeDateMillis() int64 {
+ date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedBeforeDate))
+ 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)
+}
+
+// Returns the epoch timestamps of the start and end of the day specified by SearchParams.ExcludedDate
+func (p *SearchParams) GetExcludedDateMillis() (int64, int64) {
+ date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedDate))
+ if err != nil {
+ return 0, 0
+ }
+
+ return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
+}
+
+var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on", "ext"}
+
+type flag struct {
+ name string
+ value string
+ exclude bool
+}
+
+type searchWord struct {
+ value string
+ exclude bool
+}
+
+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 {
+ nextStart := i
+ if i > 0 && text[i-1] == '-' {
+ nextStart = i - 1
+ }
+ words = append(words, strings.Fields(text[location:nextStart])...)
+ foundQuote = true
+ location = nextStart
+ }
+ }
+ }
+
+ words = append(words, strings.Fields(text[location:])...)
+
+ return words
+}
+
+func parseSearchFlags(input []string) ([]searchWord, []flag) {
+ words := []searchWord{}
+ flags := []flag{}
+
+ skipNextWord := false
+ for i, word := range input {
+ if skipNextWord {
+ skipNextWord = false
+ continue
+ }
+
+ isFlag := false
+
+ if colon := strings.Index(word, ":"); colon != -1 {
+ var flagName string
+ var exclude bool
+ if strings.HasPrefix(word, "-") {
+ flagName = word[1:colon]
+ exclude = true
+ } else {
+ flagName = word[:colon]
+ exclude = false
+ }
+
+ value := word[colon+1:]
+
+ for _, searchFlag := range searchFlags {
+ // check for case insensitive equality
+ if strings.EqualFold(flagName, searchFlag) {
+ if value != "" {
+ flags = append(flags, flag{
+ searchFlag,
+ value,
+ exclude,
+ })
+ isFlag = true
+ } else if i < len(input)-1 {
+ flags = append(flags, flag{
+ searchFlag,
+ input[i+1],
+ exclude,
+ })
+ skipNextWord = true
+ isFlag = true
+ }
+
+ if isFlag {
+ break
+ }
+ }
+ }
+ }
+
+ if !isFlag {
+ exclude := false
+ if strings.HasPrefix(word, "-") {
+ exclude = true
+ }
+ // 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 word != "" {
+ words = append(words, searchWord{
+ word,
+ exclude,
+ })
+ }
+ }
+ }
+
+ return words, flags
+}
+
+func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
+ words, flags := parseSearchFlags(splitWords(text))
+
+ hashtagTermList := []string{}
+ excludedHashtagTermList := []string{}
+ plainTermList := []string{}
+ excludedPlainTermList := []string{}
+
+ for _, word := range words {
+ if validHashtag.MatchString(word.value) {
+ if word.exclude {
+ excludedHashtagTermList = append(excludedHashtagTermList, word.value)
+ } else {
+ hashtagTermList = append(hashtagTermList, word.value)
+ }
+ } else {
+ if word.exclude {
+ excludedPlainTermList = append(excludedPlainTermList, word.value)
+ } else {
+ plainTermList = append(plainTermList, word.value)
+ }
+ }
+ }
+
+ hashtagTerms := strings.Join(hashtagTermList, " ")
+ excludedHashtagTerms := strings.Join(excludedHashtagTermList, " ")
+ plainTerms := strings.Join(plainTermList, " ")
+ excludedPlainTerms := strings.Join(excludedPlainTermList, " ")
+
+ inChannels := []string{}
+ excludedChannels := []string{}
+ fromUsers := []string{}
+ excludedUsers := []string{}
+ afterDate := ""
+ excludedAfterDate := ""
+ beforeDate := ""
+ excludedBeforeDate := ""
+ onDate := ""
+ excludedDate := ""
+ excludedExtensions := []string{}
+ extensions := []string{}
+
+ for _, flag := range flags {
+ if flag.name == "in" || flag.name == "channel" {
+ if flag.exclude {
+ excludedChannels = append(excludedChannels, flag.value)
+ } else {
+ inChannels = append(inChannels, flag.value)
+ }
+ } else if flag.name == "from" {
+ if flag.exclude {
+ excludedUsers = append(excludedUsers, flag.value)
+ } else {
+ fromUsers = append(fromUsers, flag.value)
+ }
+ } else if flag.name == "after" {
+ if flag.exclude {
+ excludedAfterDate = flag.value
+ } else {
+ afterDate = flag.value
+ }
+ } else if flag.name == "before" {
+ if flag.exclude {
+ excludedBeforeDate = flag.value
+ } else {
+ beforeDate = flag.value
+ }
+ } else if flag.name == "on" {
+ if flag.exclude {
+ excludedDate = flag.value
+ } else {
+ onDate = flag.value
+ }
+ } else if flag.name == "ext" {
+ if flag.exclude {
+ excludedExtensions = append(excludedExtensions, flag.value)
+ } else {
+ extensions = append(extensions, flag.value)
+ }
+ }
+ }
+
+ paramsList := []*SearchParams{}
+
+ if plainTerms != "" || excludedPlainTerms != "" {
+ paramsList = append(paramsList, &SearchParams{
+ Terms: plainTerms,
+ ExcludedTerms: excludedPlainTerms,
+ IsHashtag: false,
+ InChannels: inChannels,
+ ExcludedChannels: excludedChannels,
+ FromUsers: fromUsers,
+ ExcludedUsers: excludedUsers,
+ AfterDate: afterDate,
+ ExcludedAfterDate: excludedAfterDate,
+ BeforeDate: beforeDate,
+ ExcludedBeforeDate: excludedBeforeDate,
+ Extensions: extensions,
+ ExcludedExtensions: excludedExtensions,
+ OnDate: onDate,
+ ExcludedDate: excludedDate,
+ TimeZoneOffset: timeZoneOffset,
+ })
+ }
+
+ if hashtagTerms != "" || excludedHashtagTerms != "" {
+ paramsList = append(paramsList, &SearchParams{
+ Terms: hashtagTerms,
+ ExcludedTerms: excludedHashtagTerms,
+ IsHashtag: true,
+ InChannels: inChannels,
+ ExcludedChannels: excludedChannels,
+ FromUsers: fromUsers,
+ ExcludedUsers: excludedUsers,
+ AfterDate: afterDate,
+ ExcludedAfterDate: excludedAfterDate,
+ BeforeDate: beforeDate,
+ ExcludedBeforeDate: excludedBeforeDate,
+ Extensions: extensions,
+ ExcludedExtensions: excludedExtensions,
+ OnDate: onDate,
+ ExcludedDate: excludedDate,
+ TimeZoneOffset: timeZoneOffset,
+ })
+ }
+
+ // special case for when no terms are specified but we still have a filter
+ if plainTerms == "" && hashtagTerms == "" &&
+ excludedPlainTerms == "" && excludedHashtagTerms == "" &&
+ (len(inChannels) != 0 || len(fromUsers) != 0 ||
+ len(excludedChannels) != 0 || len(excludedUsers) != 0 ||
+ len(extensions) != 0 || len(excludedExtensions) != 0 ||
+ afterDate != "" || excludedAfterDate != "" ||
+ beforeDate != "" || excludedBeforeDate != "" ||
+ onDate != "" || excludedDate != "") {
+ paramsList = append(paramsList, &SearchParams{
+ Terms: "",
+ ExcludedTerms: "",
+ IsHashtag: false,
+ InChannels: inChannels,
+ ExcludedChannels: excludedChannels,
+ FromUsers: fromUsers,
+ ExcludedUsers: excludedUsers,
+ AfterDate: afterDate,
+ ExcludedAfterDate: excludedAfterDate,
+ BeforeDate: beforeDate,
+ ExcludedBeforeDate: excludedBeforeDate,
+ Extensions: extensions,
+ ExcludedExtensions: excludedExtensions,
+ OnDate: onDate,
+ ExcludedDate: excludedDate,
+ TimeZoneOffset: timeZoneOffset,
+ })
+ }
+
+ return paramsList
+}
+
+func IsSearchParamsListValid(paramsList []*SearchParams) *AppError {
+ // All SearchParams should have same IncludeDeletedChannels value.
+ for _, params := range paramsList {
+ if params.IncludeDeletedChannels != paramsList[0].IncludeDeletedChannels {
+ return NewAppError("IsSearchParamsListValid", "model.search_params_list.is_valid.include_deleted_channels.app_error", nil, "", http.StatusInternalServerError)
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/security_bulletin.go b/vendor/github.com/mattermost/mattermost-server/v6/model/security_bulletin.go
new file mode 100644
index 00000000..fa5662cf
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/security_bulletin.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type SecurityBulletin struct {
+ Id string `json:"id"`
+ AppliesToVersion string `json:"applies_to_version"`
+}
+
+type SecurityBulletins []SecurityBulletin
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/session.go b/vendor/github.com/mattermost/mattermost-server/v6/model/session.go
new file mode 100644
index 00000000..d3bbc6e4
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/session.go
@@ -0,0 +1,197 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+const (
+ SessionCookieToken = "MMAUTHTOKEN"
+ SessionCookieUser = "MMUSERID"
+ SessionCookieCsrf = "MMCSRF"
+ SessionCacheSize = 35000
+ SessionPropPlatform = "platform"
+ SessionPropOs = "os"
+ SessionPropBrowser = "browser"
+ SessionPropType = "type"
+ SessionPropUserAccessTokenId = "user_access_token_id"
+ SessionPropIsBot = "is_bot"
+ SessionPropIsBotValue = "true"
+ SessionTypeUserAccessToken = "UserAccessToken"
+ SessionTypeCloudKey = "CloudKey"
+ SessionTypeRemoteclusterToken = "RemoteClusterToken"
+ SessionPropIsGuest = "is_guest"
+ SessionActivityTimeout = 1000 * 60 * 5 // 5 minutes
+ SessionUserAccessTokenExpiry = 100 * 365 // 100 years
+)
+
+//msgp StringMap
+type StringMap map[string]string
+
+//msgp:tuple Session
+
+// Session contains the user session details.
+// This struct's serializer methods are auto-generated. If a new field is added/removed,
+// please run make gen-serialized.
+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"`
+ ExpiredNotify bool `json:"expired_notify"`
+ Props StringMap `json:"props"`
+ TeamMembers []*TeamMember `json:"team_members" db:"-"`
+ Local bool `json:"local" db:"-"`
+}
+
+// Returns true if the session is unrestricted, which should grant it
+// with all permissions. This is used for local mode sessions
+func (s *Session) IsUnrestricted() bool {
+ return s.Local
+}
+
+func (s *Session) DeepCopy() *Session {
+ copySession := *s
+
+ if s.Props != nil {
+ copySession.Props = CopyStringMap(s.Props)
+ }
+
+ if s.TeamMembers != nil {
+ copySession.TeamMembers = make([]*TeamMember, len(s.TeamMembers))
+ for index, tm := range s.TeamMembers {
+ copySession.TeamMembers[index] = new(TeamMember)
+ *copySession.TeamMembers[index] = *tm
+ }
+ }
+
+ return &copySession
+}
+
+func (s *Session) PreSave() {
+ if s.Id == "" {
+ s.Id = NewId()
+ }
+
+ if s.Token == "" {
+ s.Token = NewId()
+ }
+
+ s.CreateAt = GetMillis()
+ s.LastActivityAt = s.CreateAt
+
+ if s.Props == nil {
+ s.Props = make(map[string]string)
+ }
+}
+
+func (s *Session) Sanitize() {
+ s.Token = ""
+}
+
+func (s *Session) IsExpired() bool {
+
+ if s.ExpiresAt <= 0 {
+ return false
+ }
+
+ if GetMillis() > s.ExpiresAt {
+ return true
+ }
+
+ return false
+}
+
+func (s *Session) AddProp(key string, value string) {
+
+ if s.Props == nil {
+ s.Props = make(map[string]string)
+ }
+
+ s.Props[key] = value
+}
+
+func (s *Session) GetTeamByTeamId(teamId string) *TeamMember {
+ for _, team := range s.TeamMembers {
+ if team.TeamId == teamId {
+ return team
+ }
+ }
+
+ return nil
+}
+
+func (s *Session) IsMobileApp() bool {
+ return s.DeviceId != "" || s.IsMobile()
+}
+
+func (s *Session) IsMobile() bool {
+ val, ok := s.Props[UserAuthServiceIsMobile]
+ if !ok {
+ return false
+ }
+ isMobile, err := strconv.ParseBool(val)
+ if err != nil {
+ mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
+ return false
+ }
+ return isMobile
+}
+
+func (s *Session) IsSaml() bool {
+ val, ok := s.Props[UserAuthServiceIsSaml]
+ if !ok {
+ return false
+ }
+ isSaml, err := strconv.ParseBool(val)
+ if err != nil {
+ mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
+ return false
+ }
+ return isSaml
+}
+
+func (s *Session) IsOAuthUser() bool {
+ val, ok := s.Props[UserAuthServiceIsOAuth]
+ if !ok {
+ return false
+ }
+ isOAuthUser, err := strconv.ParseBool(val)
+ if err != nil {
+ mlog.Debug("Error parsing boolean property from Session", mlog.Err(err))
+ return false
+ }
+ return isOAuthUser
+}
+
+func (s *Session) IsSSOLogin() bool {
+ return s.IsOAuthUser() || s.IsSaml()
+}
+
+func (s *Session) GetUserRoles() []string {
+ return strings.Fields(s.Roles)
+}
+
+func (s *Session) GenerateCSRF() string {
+ token := NewId()
+ s.AddProp("csrf", token)
+ return token
+}
+
+func (s *Session) GetCSRF() string {
+ if s.Props == nil {
+ return ""
+ }
+
+ return s.Props["csrf"]
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/session_serial_gen.go b/vendor/github.com/mattermost/mattermost-server/v6/model/session_serial_gen.go
new file mode 100644
index 00000000..612bbb89
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/session_serial_gen.go
@@ -0,0 +1,540 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+// Code generated by github.com/tinylib/msgp DO NOT EDIT.
+
+import (
+ "github.com/tinylib/msgp/msgp"
+)
+
+// DecodeMsg implements msgp.Decodable
+func (z *Session) DecodeMsg(dc *msgp.Reader) (err error) {
+ var zb0001 uint32
+ zb0001, err = dc.ReadArrayHeader()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0001 != 13 {
+ err = msgp.ArrayError{Wanted: 13, Got: zb0001}
+ return
+ }
+ z.Id, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Id")
+ return
+ }
+ z.Token, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Token")
+ return
+ }
+ z.CreateAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "CreateAt")
+ return
+ }
+ z.ExpiresAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "ExpiresAt")
+ return
+ }
+ z.LastActivityAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "LastActivityAt")
+ return
+ }
+ z.UserId, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "UserId")
+ return
+ }
+ z.DeviceId, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "DeviceId")
+ return
+ }
+ z.Roles, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ z.IsOAuth, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "IsOAuth")
+ return
+ }
+ z.ExpiredNotify, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "ExpiredNotify")
+ return
+ }
+ var zb0002 uint32
+ zb0002, err = dc.ReadMapHeader()
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ if z.Props == nil {
+ z.Props = make(StringMap, zb0002)
+ } else if len(z.Props) > 0 {
+ for key := range z.Props {
+ delete(z.Props, key)
+ }
+ }
+ for zb0002 > 0 {
+ zb0002--
+ var za0001 string
+ var za0002 string
+ za0001, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ za0002, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Props", za0001)
+ return
+ }
+ z.Props[za0001] = za0002
+ }
+ var zb0003 uint32
+ zb0003, err = dc.ReadArrayHeader()
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers")
+ return
+ }
+ if cap(z.TeamMembers) >= int(zb0003) {
+ z.TeamMembers = (z.TeamMembers)[:zb0003]
+ } else {
+ z.TeamMembers = make([]*TeamMember, zb0003)
+ }
+ for za0003 := range z.TeamMembers {
+ if dc.IsNil() {
+ err = dc.ReadNil()
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers", za0003)
+ return
+ }
+ z.TeamMembers[za0003] = nil
+ } else {
+ if z.TeamMembers[za0003] == nil {
+ z.TeamMembers[za0003] = new(TeamMember)
+ }
+ err = z.TeamMembers[za0003].DecodeMsg(dc)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers", za0003)
+ return
+ }
+ }
+ }
+ z.Local, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "Local")
+ return
+ }
+ return
+}
+
+// EncodeMsg implements msgp.Encodable
+func (z *Session) EncodeMsg(en *msgp.Writer) (err error) {
+ // array header, size 13
+ err = en.Append(0x9d)
+ if err != nil {
+ return
+ }
+ err = en.WriteString(z.Id)
+ if err != nil {
+ err = msgp.WrapError(err, "Id")
+ return
+ }
+ err = en.WriteString(z.Token)
+ if err != nil {
+ err = msgp.WrapError(err, "Token")
+ return
+ }
+ err = en.WriteInt64(z.CreateAt)
+ if err != nil {
+ err = msgp.WrapError(err, "CreateAt")
+ return
+ }
+ err = en.WriteInt64(z.ExpiresAt)
+ if err != nil {
+ err = msgp.WrapError(err, "ExpiresAt")
+ return
+ }
+ err = en.WriteInt64(z.LastActivityAt)
+ if err != nil {
+ err = msgp.WrapError(err, "LastActivityAt")
+ return
+ }
+ err = en.WriteString(z.UserId)
+ if err != nil {
+ err = msgp.WrapError(err, "UserId")
+ return
+ }
+ err = en.WriteString(z.DeviceId)
+ if err != nil {
+ err = msgp.WrapError(err, "DeviceId")
+ return
+ }
+ err = en.WriteString(z.Roles)
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ err = en.WriteBool(z.IsOAuth)
+ if err != nil {
+ err = msgp.WrapError(err, "IsOAuth")
+ return
+ }
+ err = en.WriteBool(z.ExpiredNotify)
+ if err != nil {
+ err = msgp.WrapError(err, "ExpiredNotify")
+ return
+ }
+ err = en.WriteMapHeader(uint32(len(z.Props)))
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ for za0001, za0002 := range z.Props {
+ err = en.WriteString(za0001)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ err = en.WriteString(za0002)
+ if err != nil {
+ err = msgp.WrapError(err, "Props", za0001)
+ return
+ }
+ }
+ err = en.WriteArrayHeader(uint32(len(z.TeamMembers)))
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers")
+ return
+ }
+ for za0003 := range z.TeamMembers {
+ if z.TeamMembers[za0003] == nil {
+ err = en.WriteNil()
+ if err != nil {
+ return
+ }
+ } else {
+ err = z.TeamMembers[za0003].EncodeMsg(en)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers", za0003)
+ return
+ }
+ }
+ }
+ err = en.WriteBool(z.Local)
+ if err != nil {
+ err = msgp.WrapError(err, "Local")
+ return
+ }
+ return
+}
+
+// MarshalMsg implements msgp.Marshaler
+func (z *Session) MarshalMsg(b []byte) (o []byte, err error) {
+ o = msgp.Require(b, z.Msgsize())
+ // array header, size 13
+ o = append(o, 0x9d)
+ o = msgp.AppendString(o, z.Id)
+ o = msgp.AppendString(o, z.Token)
+ o = msgp.AppendInt64(o, z.CreateAt)
+ o = msgp.AppendInt64(o, z.ExpiresAt)
+ o = msgp.AppendInt64(o, z.LastActivityAt)
+ o = msgp.AppendString(o, z.UserId)
+ o = msgp.AppendString(o, z.DeviceId)
+ o = msgp.AppendString(o, z.Roles)
+ o = msgp.AppendBool(o, z.IsOAuth)
+ o = msgp.AppendBool(o, z.ExpiredNotify)
+ o = msgp.AppendMapHeader(o, uint32(len(z.Props)))
+ for za0001, za0002 := range z.Props {
+ o = msgp.AppendString(o, za0001)
+ o = msgp.AppendString(o, za0002)
+ }
+ o = msgp.AppendArrayHeader(o, uint32(len(z.TeamMembers)))
+ for za0003 := range z.TeamMembers {
+ if z.TeamMembers[za0003] == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o, err = z.TeamMembers[za0003].MarshalMsg(o)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers", za0003)
+ return
+ }
+ }
+ }
+ o = msgp.AppendBool(o, z.Local)
+ return
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *Session) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ var zb0001 uint32
+ zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0001 != 13 {
+ err = msgp.ArrayError{Wanted: 13, Got: zb0001}
+ return
+ }
+ z.Id, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Id")
+ return
+ }
+ z.Token, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Token")
+ return
+ }
+ z.CreateAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "CreateAt")
+ return
+ }
+ z.ExpiresAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "ExpiresAt")
+ return
+ }
+ z.LastActivityAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "LastActivityAt")
+ return
+ }
+ z.UserId, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "UserId")
+ return
+ }
+ z.DeviceId, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "DeviceId")
+ return
+ }
+ z.Roles, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ z.IsOAuth, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "IsOAuth")
+ return
+ }
+ z.ExpiredNotify, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "ExpiredNotify")
+ return
+ }
+ var zb0002 uint32
+ zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ if z.Props == nil {
+ z.Props = make(StringMap, zb0002)
+ } else if len(z.Props) > 0 {
+ for key := range z.Props {
+ delete(z.Props, key)
+ }
+ }
+ for zb0002 > 0 {
+ var za0001 string
+ var za0002 string
+ zb0002--
+ za0001, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ za0002, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Props", za0001)
+ return
+ }
+ z.Props[za0001] = za0002
+ }
+ var zb0003 uint32
+ zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers")
+ return
+ }
+ if cap(z.TeamMembers) >= int(zb0003) {
+ z.TeamMembers = (z.TeamMembers)[:zb0003]
+ } else {
+ z.TeamMembers = make([]*TeamMember, zb0003)
+ }
+ for za0003 := range z.TeamMembers {
+ if msgp.IsNil(bts) {
+ bts, err = msgp.ReadNilBytes(bts)
+ if err != nil {
+ return
+ }
+ z.TeamMembers[za0003] = nil
+ } else {
+ if z.TeamMembers[za0003] == nil {
+ z.TeamMembers[za0003] = new(TeamMember)
+ }
+ bts, err = z.TeamMembers[za0003].UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamMembers", za0003)
+ return
+ }
+ }
+ }
+ z.Local, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Local")
+ return
+ }
+ o = bts
+ return
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z *Session) Msgsize() (s int) {
+ s = 1 + msgp.StringPrefixSize + len(z.Id) + msgp.StringPrefixSize + len(z.Token) + msgp.Int64Size + msgp.Int64Size + msgp.Int64Size + msgp.StringPrefixSize + len(z.UserId) + msgp.StringPrefixSize + len(z.DeviceId) + msgp.StringPrefixSize + len(z.Roles) + msgp.BoolSize + msgp.BoolSize + msgp.MapHeaderSize
+ if z.Props != nil {
+ for za0001, za0002 := range z.Props {
+ _ = za0002
+ s += msgp.StringPrefixSize + len(za0001) + msgp.StringPrefixSize + len(za0002)
+ }
+ }
+ s += msgp.ArrayHeaderSize
+ for za0003 := range z.TeamMembers {
+ if z.TeamMembers[za0003] == nil {
+ s += msgp.NilSize
+ } else {
+ s += z.TeamMembers[za0003].Msgsize()
+ }
+ }
+ s += msgp.BoolSize
+ return
+}
+
+// DecodeMsg implements msgp.Decodable
+func (z *StringMap) DecodeMsg(dc *msgp.Reader) (err error) {
+ var zb0003 uint32
+ zb0003, err = dc.ReadMapHeader()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if (*z) == nil {
+ (*z) = make(StringMap, zb0003)
+ } else if len((*z)) > 0 {
+ for key := range *z {
+ delete((*z), key)
+ }
+ }
+ for zb0003 > 0 {
+ zb0003--
+ var zb0001 string
+ var zb0002 string
+ zb0001, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ zb0002, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ (*z)[zb0001] = zb0002
+ }
+ return
+}
+
+// EncodeMsg implements msgp.Encodable
+func (z StringMap) EncodeMsg(en *msgp.Writer) (err error) {
+ err = en.WriteMapHeader(uint32(len(z)))
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ for zb0004, zb0005 := range z {
+ err = en.WriteString(zb0004)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ err = en.WriteString(zb0005)
+ if err != nil {
+ err = msgp.WrapError(err, zb0004)
+ return
+ }
+ }
+ return
+}
+
+// MarshalMsg implements msgp.Marshaler
+func (z StringMap) MarshalMsg(b []byte) (o []byte, err error) {
+ o = msgp.Require(b, z.Msgsize())
+ o = msgp.AppendMapHeader(o, uint32(len(z)))
+ for zb0004, zb0005 := range z {
+ o = msgp.AppendString(o, zb0004)
+ o = msgp.AppendString(o, zb0005)
+ }
+ return
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *StringMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ var zb0003 uint32
+ zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if (*z) == nil {
+ (*z) = make(StringMap, zb0003)
+ } else if len((*z)) > 0 {
+ for key := range *z {
+ delete((*z), key)
+ }
+ }
+ for zb0003 > 0 {
+ var zb0001 string
+ var zb0002 string
+ zb0003--
+ zb0001, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ zb0002, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ (*z)[zb0001] = zb0002
+ }
+ o = bts
+ return
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z StringMap) Msgsize() (s int) {
+ s = msgp.MapHeaderSize
+ if z != nil {
+ for zb0004, zb0005 := range z {
+ _ = zb0005
+ s += msgp.StringPrefixSize + len(zb0004) + msgp.StringPrefixSize + len(zb0005)
+ }
+ }
+ return
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/shared_channel.go b/vendor/github.com/mattermost/mattermost-server/v6/model/shared_channel.go
new file mode 100644
index 00000000..08a29292
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/shared_channel.go
@@ -0,0 +1,249 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+ "unicode/utf8"
+)
+
+// SharedChannel represents a channel that can be synchronized with a remote cluster.
+// If "home" is true, then the shared channel is homed locally and "SharedChannelRemote"
+// table contains the remote clusters that have been invited.
+// If "home" is false, then the shared channel is homed remotely, and "RemoteId"
+// field points to the remote cluster connection in "RemoteClusters" table.
+type SharedChannel struct {
+ ChannelId string `json:"id"`
+ TeamId string `json:"team_id"`
+ Home bool `json:"home"`
+ ReadOnly bool `json:"readonly"`
+ ShareName string `json:"name"`
+ ShareDisplayName string `json:"display_name"`
+ SharePurpose string `json:"purpose"`
+ ShareHeader string `json:"header"`
+ CreatorId string `json:"creator_id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ RemoteId string `json:"remote_id,omitempty"` // if not "home"
+ Type ChannelType `db:"-"`
+}
+
+func (sc *SharedChannel) IsValid() *AppError {
+ if !IsValidId(sc.ChannelId) {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.id.app_error", nil, "ChannelId="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if sc.Type != ChannelTypeDirect && !IsValidId(sc.TeamId) {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.id.app_error", nil, "TeamId="+sc.TeamId, http.StatusBadRequest)
+ }
+
+ if sc.CreateAt == 0 {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if sc.UpdateAt == 0 {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(sc.ShareDisplayName) > ChannelDisplayNameMaxRunes {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if !IsValidChannelIdentifier(sc.ShareName) {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(sc.ShareHeader) > ChannelHeaderMaxRunes {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if utf8.RuneCountInString(sc.SharePurpose) > ChannelPurposeMaxRunes {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if !IsValidId(sc.CreatorId) {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "CreatorId="+sc.CreatorId, http.StatusBadRequest)
+ }
+
+ if !sc.Home {
+ if !IsValidId(sc.RemoteId) {
+ return NewAppError("SharedChannel.IsValid", "model.channel.is_valid.id.app_error", nil, "RemoteId="+sc.RemoteId, http.StatusBadRequest)
+ }
+ }
+ return nil
+}
+
+func (sc *SharedChannel) PreSave() {
+ sc.ShareName = SanitizeUnicode(sc.ShareName)
+ sc.ShareDisplayName = SanitizeUnicode(sc.ShareDisplayName)
+
+ sc.CreateAt = GetMillis()
+ sc.UpdateAt = sc.CreateAt
+}
+
+func (sc *SharedChannel) PreUpdate() {
+ sc.UpdateAt = GetMillis()
+ sc.ShareName = SanitizeUnicode(sc.ShareName)
+ sc.ShareDisplayName = SanitizeUnicode(sc.ShareDisplayName)
+}
+
+// SharedChannelRemote represents a remote cluster that has been invited
+// to a shared channel.
+type SharedChannelRemote struct {
+ Id string `json:"id"`
+ ChannelId string `json:"channel_id"`
+ CreatorId string `json:"creator_id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ IsInviteAccepted bool `json:"is_invite_accepted"`
+ IsInviteConfirmed bool `json:"is_invite_confirmed"`
+ RemoteId string `json:"remote_id"`
+ LastPostUpdateAt int64 `json:"last_post_update_at"`
+ LastPostId string `json:"last_post_id"`
+}
+
+func (sc *SharedChannelRemote) IsValid() *AppError {
+ if !IsValidId(sc.Id) {
+ return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.id.app_error", nil, "Id="+sc.Id, http.StatusBadRequest)
+ }
+
+ if !IsValidId(sc.ChannelId) {
+ return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.id.app_error", nil, "ChannelId="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if sc.CreateAt == 0 {
+ return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if sc.UpdateAt == 0 {
+ return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+sc.ChannelId, http.StatusBadRequest)
+ }
+
+ if !IsValidId(sc.CreatorId) {
+ return NewAppError("SharedChannelRemote.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "id="+sc.CreatorId, http.StatusBadRequest)
+ }
+ return nil
+}
+
+func (sc *SharedChannelRemote) PreSave() {
+ if sc.Id == "" {
+ sc.Id = NewId()
+ }
+ sc.CreateAt = GetMillis()
+ sc.UpdateAt = sc.CreateAt
+}
+
+func (sc *SharedChannelRemote) PreUpdate() {
+ sc.UpdateAt = GetMillis()
+}
+
+type SharedChannelRemoteStatus struct {
+ ChannelId string `json:"channel_id"`
+ DisplayName string `json:"display_name"`
+ SiteURL string `json:"site_url"`
+ LastPingAt int64 `json:"last_ping_at"`
+ NextSyncAt int64 `json:"next_sync_at"`
+ ReadOnly bool `json:"readonly"`
+ IsInviteAccepted bool `json:"is_invite_accepted"`
+ Token string `json:"token"`
+}
+
+// SharedChannelUser stores a lastSyncAt timestamp on behalf of a remote cluster for
+// each user that has been synchronized.
+type SharedChannelUser struct {
+ Id string `json:"id"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ RemoteId string `json:"remote_id"`
+ CreateAt int64 `json:"create_at"`
+ LastSyncAt int64 `json:"last_sync_at"`
+}
+
+func (scu *SharedChannelUser) PreSave() {
+ scu.Id = NewId()
+ scu.CreateAt = GetMillis()
+}
+
+func (scu *SharedChannelUser) IsValid() *AppError {
+ if !IsValidId(scu.Id) {
+ return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "Id="+scu.Id, http.StatusBadRequest)
+ }
+
+ if !IsValidId(scu.UserId) {
+ return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "UserId="+scu.UserId, http.StatusBadRequest)
+ }
+
+ if !IsValidId(scu.ChannelId) {
+ return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "ChannelId="+scu.ChannelId, http.StatusBadRequest)
+ }
+
+ if !IsValidId(scu.RemoteId) {
+ return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.id.app_error", nil, "RemoteId="+scu.RemoteId, http.StatusBadRequest)
+ }
+
+ if scu.CreateAt == 0 {
+ return NewAppError("SharedChannelUser.IsValid", "model.channel.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
+ }
+ return nil
+}
+
+type GetUsersForSyncFilter struct {
+ CheckProfileImage bool
+ ChannelID string
+ Limit uint64
+}
+
+// SharedChannelAttachment stores a lastSyncAt timestamp on behalf of a remote cluster for
+// each file attachment that has been synchronized.
+type SharedChannelAttachment struct {
+ Id string `json:"id"`
+ FileId string `json:"file_id"`
+ RemoteId string `json:"remote_id"`
+ CreateAt int64 `json:"create_at"`
+ LastSyncAt int64 `json:"last_sync_at"`
+}
+
+func (scf *SharedChannelAttachment) PreSave() {
+ if scf.Id == "" {
+ scf.Id = NewId()
+ }
+ if scf.CreateAt == 0 {
+ scf.CreateAt = GetMillis()
+ scf.LastSyncAt = scf.CreateAt
+ } else {
+ scf.LastSyncAt = GetMillis()
+ }
+}
+
+func (scf *SharedChannelAttachment) IsValid() *AppError {
+ if !IsValidId(scf.Id) {
+ return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.id.app_error", nil, "Id="+scf.Id, http.StatusBadRequest)
+ }
+
+ if !IsValidId(scf.FileId) {
+ return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.id.app_error", nil, "FileId="+scf.FileId, http.StatusBadRequest)
+ }
+
+ if !IsValidId(scf.RemoteId) {
+ return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.id.app_error", nil, "RemoteId="+scf.RemoteId, http.StatusBadRequest)
+ }
+
+ if scf.CreateAt == 0 {
+ return NewAppError("SharedChannelAttachment.IsValid", "model.channel.is_valid.create_at.app_error", nil, "", http.StatusBadRequest)
+ }
+ return nil
+}
+
+type SharedChannelFilterOpts struct {
+ TeamId string
+ CreatorId string
+ ExcludeHome bool
+ ExcludeRemote bool
+}
+
+type SharedChannelRemoteFilterOpts struct {
+ ChannelId string
+ RemoteId string
+ InclUnconfirmed bool
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/slack_attachment.go b/vendor/github.com/mattermost/mattermost-server/v6/model/slack_attachment.go
new file mode 100644
index 00000000..94ba9935
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/slack_attachment.go
@@ -0,0 +1,196 @@
+// Copyright (c) 2015-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"`
+}
+
+func (s *SlackAttachment) Equals(input *SlackAttachment) bool {
+ // Direct comparison of simple types
+
+ if s.Id != input.Id {
+ return false
+ }
+
+ if s.Fallback != input.Fallback {
+ return false
+ }
+
+ if s.Color != input.Color {
+ return false
+ }
+
+ if s.Pretext != input.Pretext {
+ return false
+ }
+
+ if s.AuthorName != input.AuthorName {
+ return false
+ }
+
+ if s.AuthorLink != input.AuthorLink {
+ return false
+ }
+
+ if s.AuthorIcon != input.AuthorIcon {
+ return false
+ }
+
+ if s.Title != input.Title {
+ return false
+ }
+
+ if s.TitleLink != input.TitleLink {
+ return false
+ }
+
+ if s.Text != input.Text {
+ return false
+ }
+
+ if s.ImageURL != input.ImageURL {
+ return false
+ }
+
+ if s.ThumbURL != input.ThumbURL {
+ return false
+ }
+
+ if s.Footer != input.Footer {
+ return false
+ }
+
+ if s.FooterIcon != input.FooterIcon {
+ return false
+ }
+
+ // Compare length & slice values of fields
+ if len(s.Fields) != len(input.Fields) {
+ return false
+ }
+
+ for j := range s.Fields {
+ if !s.Fields[j].Equals(input.Fields[j]) {
+ return false
+ }
+ }
+
+ // Compare length & slice values of actions
+ if len(s.Actions) != len(input.Actions) {
+ return false
+ }
+
+ for j := range s.Actions {
+ if !s.Actions[j].Equals(input.Actions[j]) {
+ return false
+ }
+ }
+
+ return s.Timestamp == input.Timestamp
+}
+
+type SlackAttachmentField struct {
+ Title string `json:"title"`
+ Value interface{} `json:"value"`
+ Short SlackCompatibleBool `json:"short"`
+}
+
+func (s *SlackAttachmentField) Equals(input *SlackAttachmentField) bool {
+ if s.Title != input.Title {
+ return false
+ }
+
+ if s.Value != input.Value {
+ return false
+ }
+
+ if s.Short != input.Short {
+ return false
+ }
+
+ return true
+}
+
+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) {
+ if post.Type == "" {
+ post.Type = PostTypeSlackAttachment
+ }
+
+ postAttachments := []*SlackAttachment{}
+
+ for _, attachment := range attachments {
+ if attachment == nil {
+ continue
+ }
+
+ attachment.Text = ParseSlackLinksToMarkdown(attachment.Text)
+ attachment.Pretext = ParseSlackLinksToMarkdown(attachment.Pretext)
+
+ for _, field := range attachment.Fields {
+ if field == nil {
+ continue
+ }
+ if value, ok := field.Value.(string); ok {
+ field.Value = ParseSlackLinksToMarkdown(value)
+ }
+ }
+ postAttachments = append(postAttachments, attachment)
+ }
+ post.AddProp("attachments", postAttachments)
+}
+
+func ParseSlackLinksToMarkdown(text string) string {
+ return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/slack_compatibility.go b/vendor/github.com/mattermost/mattermost-server/v6/model/slack_compatibility.go
new file mode 100644
index 00000000..2d3e2878
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/slack_compatibility.go
@@ -0,0 +1,30 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "strings"
+)
+
+// SlackCompatibleBool is an alias for bool that implements json.Unmarshaler
+type SlackCompatibleBool bool
+
+// UnmarshalJSON implements json.Unmarshaler
+//
+// Slack allows bool values to be represented as strings ("true"/"false") or
+// literals (true/false). To maintain compatibility, we define an Unmarshaler
+// that supports both.
+func (b *SlackCompatibleBool) UnmarshalJSON(data []byte) error {
+ value := strings.ToLower(string(data))
+ if value == "true" || value == `"true"` {
+ *b = true
+ } else if value == "false" || value == `"false"` {
+ *b = false
+ } else {
+ return fmt.Errorf("unmarshal: unable to convert %s to bool", data)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/status.go b/vendor/github.com/mattermost/mattermost-server/v6/model/status.go
new file mode 100644
index 00000000..45a6d5d2
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/status.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+)
+
+const (
+ StatusOutOfOffice = "ooo"
+ StatusOffline = "offline"
+ StatusAway = "away"
+ StatusDnd = "dnd"
+ StatusOnline = "online"
+ StatusCacheSize = SessionCacheSize
+ StatusChannelTimeout = 20000 // 20 seconds
+ StatusMinUpdateTime = 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:"-"`
+ DNDEndTime int64 `json:"dnd_end_time"`
+ PrevStatus string `json:"-"`
+}
+
+func (s *Status) ToJSON() ([]byte, error) {
+ sCopy := *s
+ sCopy.ActiveChannel = ""
+ return json.Marshal(sCopy)
+}
+
+func StatusListToJSON(u []*Status) ([]byte, error) {
+ list := make([]Status, len(u))
+ for i, s := range u {
+ list[i] = *s
+ list[i].ActiveChannel = ""
+ }
+ return json.Marshal(list)
+}
+
+func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} {
+ interfaceMap := map[string]interface{}{}
+ for _, s := range statusMap {
+ // Omitted statues mean offline
+ if s.Status != StatusOffline {
+ interfaceMap[s.UserId] = s.Status
+ }
+ }
+ return interfaceMap
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/suggest_command.go b/vendor/github.com/mattermost/mattermost-server/v6/model/suggest_command.go
new file mode 100644
index 00000000..7fb045fc
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/suggest_command.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type SuggestCommand struct {
+ Suggestion string `json:"suggestion"`
+ Description string `json:"description"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/switch_request.go b/vendor/github.com/mattermost/mattermost-server/v6/model/switch_request.go
new file mode 100644
index 00000000..70694cb1
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/switch_request.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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) EmailToOAuth() bool {
+ return o.CurrentService == UserAuthServiceEmail &&
+ (o.NewService == UserAuthServiceSaml ||
+ o.NewService == UserAuthServiceGitlab ||
+ o.NewService == ServiceGoogle ||
+ o.NewService == ServiceOffice365 ||
+ o.NewService == ServiceOpenid)
+}
+
+func (o *SwitchRequest) OAuthToEmail() bool {
+ return (o.CurrentService == UserAuthServiceSaml ||
+ o.CurrentService == UserAuthServiceGitlab ||
+ o.CurrentService == ServiceGoogle ||
+ o.CurrentService == ServiceOffice365 ||
+ o.CurrentService == ServiceOpenid) && o.NewService == UserAuthServiceEmail
+}
+
+func (o *SwitchRequest) EmailToLdap() bool {
+ return o.CurrentService == UserAuthServiceEmail && o.NewService == UserAuthServiceLdap
+}
+
+func (o *SwitchRequest) LdapToEmail() bool {
+ return o.CurrentService == UserAuthServiceLdap && o.NewService == UserAuthServiceEmail
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/system.go b/vendor/github.com/mattermost/mattermost-server/v6/model/system.go
new file mode 100644
index 00000000..c8bcaba2
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/system.go
@@ -0,0 +1,182 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "math/big"
+)
+
+const (
+ SystemTelemetryId = "DiagnosticId"
+ SystemRanUnitTests = "RanUnitTests"
+ SystemLastSecurityTime = "LastSecurityTime"
+ SystemActiveLicenseId = "ActiveLicenseId"
+ SystemLicenseRenewalToken = "LicenseRenewalToken"
+ SystemLastComplianceTime = "LastComplianceTime"
+ SystemAsymmetricSigningKeyKey = "AsymmetricSigningKey"
+ SystemPostActionCookieSecretKey = "PostActionCookieSecret"
+ SystemInstallationDateKey = "InstallationDate"
+ SystemFirstServerRunTimestampKey = "FirstServerRunTimestamp"
+ SystemClusterEncryptionKey = "ClusterEncryptionKey"
+ SystemUpgradedFromTeId = "UpgradedFromTE"
+ SystemWarnMetricNumberOfTeams5 = "warn_metric_number_of_teams_5"
+ SystemWarnMetricNumberOfChannels50 = "warn_metric_number_of_channels_50"
+ SystemWarnMetricMfa = "warn_metric_mfa"
+ SystemWarnMetricEmailDomain = "warn_metric_email_domain"
+ SystemWarnMetricNumberOfActiveUsers100 = "warn_metric_number_of_active_users_100"
+ SystemWarnMetricNumberOfActiveUsers200 = "warn_metric_number_of_active_users_200"
+ SystemWarnMetricNumberOfActiveUsers300 = "warn_metric_number_of_active_users_300"
+ SystemWarnMetricNumberOfActiveUsers500 = "warn_metric_number_of_active_users_500"
+ SystemWarnMetricNumberOfPosts2m = "warn_metric_number_of_posts_2M"
+ SystemWarnMetricLastRunTimestampKey = "LastWarnMetricRunTimestamp"
+ SystemMetricSupportEmailNotConfigured = "warn_metric_support_email_not_configured"
+ SystemFirstAdminVisitMarketplace = "FirstAdminVisitMarketplace"
+ AwsMeteringReportInterval = 1
+ AwsMeteringDimensionUsageHrs = "UsageHrs"
+ UserLimitOverageCycleEndDate = "UserLimitOverageCycleEndDate"
+ OverUserLimitForgivenCount = "OverUserLimitForgivenCount"
+ OverUserLimitLastEmailSent = "OverUserLimitLastEmailSent"
+)
+
+const (
+ WarnMetricStatusLimitReached = "true"
+ WarnMetricStatusRunonce = "runonce"
+ WarnMetricStatusAck = "ack"
+ WarnMetricStatusStorePrefix = "warn_metric_"
+ WarnMetricJobInterval = 24 * 7
+ WarnMetricNumberOfActiveUsers25 = 25
+ WarnMetricJobWaitTime = 1000 * 3600 * 24 * 7 // 7 days
+)
+
+type System struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
+
+type SystemPostActionCookieSecret struct {
+ Secret []byte `json:"key,omitempty"`
+}
+
+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"`
+}
+
+// ServerBusyState provides serialization for app.Busy.
+type ServerBusyState struct {
+ Busy bool `json:"busy"`
+ Expires int64 `json:"expires"`
+ ExpiresTS string `json:"expires_ts,omitempty"`
+}
+
+type SupportPacket struct {
+ ServerOS string `yaml:"server_os"`
+ ServerArchitecture string `yaml:"server_architecture"`
+ DatabaseType string `yaml:"database_type"`
+ DatabaseVersion string `yaml:"database_version"`
+ LdapVendorName string `yaml:"ldap_vendor_name,omitempty"`
+ LdapVendorVersion string `yaml:"ldap_vendor_version,omitempty"`
+ ElasticServerVersion string `yaml:"elastic_server_version,omitempty"`
+ ElasticServerPlugins []string `yaml:"elastic_server_plugins,omitempty"`
+}
+
+type FileData struct {
+ Filename string
+ Body []byte
+}
+
+var WarnMetricsTable = map[string]WarnMetric{
+ SystemWarnMetricMfa: {
+ Id: SystemWarnMetricMfa,
+ Limit: -1,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricEmailDomain: {
+ Id: SystemWarnMetricEmailDomain,
+ Limit: -1,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfTeams5: {
+ Id: SystemWarnMetricNumberOfTeams5,
+ Limit: 5,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfChannels50: {
+ Id: SystemWarnMetricNumberOfChannels50,
+ Limit: 50,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfActiveUsers100: {
+ Id: SystemWarnMetricNumberOfActiveUsers100,
+ Limit: 100,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfActiveUsers200: {
+ Id: SystemWarnMetricNumberOfActiveUsers200,
+ Limit: 200,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfActiveUsers300: {
+ Id: SystemWarnMetricNumberOfActiveUsers300,
+ Limit: 300,
+ IsBotOnly: true,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfActiveUsers500: {
+ Id: SystemWarnMetricNumberOfActiveUsers500,
+ Limit: 500,
+ IsBotOnly: false,
+ IsRunOnce: true,
+ },
+ SystemWarnMetricNumberOfPosts2m: {
+ Id: SystemWarnMetricNumberOfPosts2m,
+ Limit: 2000000,
+ IsBotOnly: false,
+ IsRunOnce: true,
+ },
+ SystemMetricSupportEmailNotConfigured: {
+ Id: SystemMetricSupportEmailNotConfigured,
+ Limit: -1,
+ IsBotOnly: true,
+ IsRunOnce: false,
+ SkipAction: true,
+ },
+}
+
+type WarnMetric struct {
+ Id string
+ Limit int64
+ IsBotOnly bool
+ IsRunOnce bool
+ SkipAction bool
+}
+
+type WarnMetricDisplayTexts struct {
+ BotTitle string
+ BotMessageBody string
+ BotSuccessMessage string
+ EmailBody string
+}
+type WarnMetricStatus struct {
+ Id string `json:"id"`
+ Limit int64 `json:"limit"`
+ Acked bool `json:"acked"`
+ StoreStatus string `json:"store_status,omitempty"`
+}
+
+type SendWarnMetricAck struct {
+ ForceAck bool `json:"forceAck"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/team.go b/vendor/github.com/mattermost/mattermost-server/v6/model/team.go
new file mode 100644
index 00000000..d18d1620
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/team.go
@@ -0,0 +1,253 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+ "unicode/utf8"
+)
+
+const (
+ TeamOpen = "O"
+ TeamInvite = "I"
+ TeamAllowedDomainsMaxLength = 500
+ TeamCompanyNameMaxLength = 64
+ TeamDescriptionMaxLength = 255
+ TeamDisplayNameMaxRunes = 64
+ TeamEmailMaxLength = 128
+ TeamNameMaxLength = 64
+ TeamNameMinLength = 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"`
+ GroupConstrained *bool `json:"group_constrained"`
+ PolicyID *string `json:"policy_id" db:"-"`
+}
+
+type TeamPatch struct {
+ DisplayName *string `json:"display_name"`
+ Description *string `json:"description"`
+ CompanyName *string `json:"company_name"`
+ AllowedDomains *string `json:"allowed_domains"`
+ AllowOpenInvite *bool `json:"allow_open_invite"`
+ GroupConstrained *bool `json:"group_constrained"`
+}
+
+type TeamForExport struct {
+ Team
+ SchemeName *string
+}
+
+type Invites struct {
+ Invites []map[string]string `json:"invites"`
+}
+
+type TeamsWithCount struct {
+ Teams []*Team `json:"teams"`
+ TotalCount int64 `json:"total_count"`
+}
+
+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 *Team) Etag() string {
+ return Etag(o.Id, o.UpdateAt)
+}
+
+func (o *Team) IsValid() *AppError {
+ if !IsValidId(o.Id) {
+ 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) > TeamEmailMaxLength {
+ return NewAppError("Team.IsValid", "model.team.is_valid.email.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if o.Email != "" && !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) > TeamDisplayNameMaxRunes {
+ return NewAppError("Team.IsValid", "model.team.is_valid.name.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if len(o.Name) > TeamNameMaxLength {
+ return NewAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if len(o.Description) > TeamDescriptionMaxLength {
+ return NewAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if o.InviteId == "" {
+ return NewAppError("Team.IsValid", "model.team.is_valid.invite_id.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 == TeamOpen || o.Type == TeamInvite) {
+ return NewAppError("Team.IsValid", "model.team.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if len(o.CompanyName) > TeamCompanyNameMaxLength {
+ return NewAppError("Team.IsValid", "model.team.is_valid.company.app_error", nil, "id="+o.Id, http.StatusBadRequest)
+ }
+
+ if len(o.AllowedDomains) > TeamAllowedDomainsMaxLength {
+ 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
+
+ o.Name = SanitizeUnicode(o.Name)
+ o.DisplayName = SanitizeUnicode(o.DisplayName)
+ o.Description = SanitizeUnicode(o.Description)
+ o.CompanyName = SanitizeUnicode(o.CompanyName)
+
+ if o.InviteId == "" {
+ o.InviteId = NewId()
+ }
+}
+
+func (o *Team) PreUpdate() {
+ o.UpdateAt = GetMillis()
+ o.Name = SanitizeUnicode(o.Name)
+ o.DisplayName = SanitizeUnicode(o.DisplayName)
+ o.Description = SanitizeUnicode(o.Description)
+ o.CompanyName = SanitizeUnicode(o.CompanyName)
+}
+
+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) < TeamNameMinLength {
+ 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.InviteId = ""
+}
+
+func (o *Team) Patch(patch *TeamPatch) {
+ if patch.DisplayName != nil {
+ o.DisplayName = *patch.DisplayName
+ }
+
+ if patch.Description != nil {
+ o.Description = *patch.Description
+ }
+
+ if patch.CompanyName != nil {
+ o.CompanyName = *patch.CompanyName
+ }
+
+ if patch.AllowedDomains != nil {
+ o.AllowedDomains = *patch.AllowedDomains
+ }
+
+ if patch.AllowOpenInvite != nil {
+ o.AllowOpenInvite = *patch.AllowOpenInvite
+ }
+
+ if patch.GroupConstrained != nil {
+ o.GroupConstrained = patch.GroupConstrained
+ }
+}
+
+func (o *Team) IsGroupConstrained() bool {
+ return o.GroupConstrained != nil && *o.GroupConstrained
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/team_member.go b/vendor/github.com/mattermost/mattermost-server/v6/model/team_member.go
new file mode 100644
index 00000000..2c928d2d
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/team_member.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+)
+
+const (
+ USERNAME = "Username"
+)
+
+//msgp:tuple TeamMember
+// This struct's serializer methods are auto-generated. If a new field is added/removed,
+// please run make gen-serialized.
+type TeamMember struct {
+ TeamId string `json:"team_id"`
+ UserId string `json:"user_id"`
+ Roles string `json:"roles"`
+ DeleteAt int64 `json:"delete_at"`
+ SchemeGuest bool `json:"scheme_guest"`
+ SchemeUser bool `json:"scheme_user"`
+ SchemeAdmin bool `json:"scheme_admin"`
+ ExplicitRoles string `json:"explicit_roles"`
+}
+
+//msgp:ignore TeamUnread
+type TeamUnread struct {
+ TeamId string `json:"team_id"`
+ MsgCount int64 `json:"msg_count"`
+ MentionCount int64 `json:"mention_count"`
+ MentionCountRoot int64 `json:"mention_count_root"`
+ MsgCountRoot int64 `json:"msg_count_root"`
+ ThreadCount int64 `json:"thread_count"`
+ ThreadMentionCount int64 `json:"thread_mention_count"`
+}
+
+//msgp:ignore TeamMemberForExport
+type TeamMemberForExport struct {
+ TeamMember
+ TeamName string
+}
+
+//msgp:ignore TeamMemberWithError
+type TeamMemberWithError struct {
+ UserId string `json:"user_id"`
+ Member *TeamMember `json:"member"`
+ Error *AppError `json:"error"`
+}
+
+//msgp:ignore EmailInviteWithError
+type EmailInviteWithError struct {
+ Email string `json:"email"`
+ Error *AppError `json:"error"`
+}
+
+//msgp:ignore TeamMembersGetOptions
+type TeamMembersGetOptions struct {
+ // Sort the team members. Accepts "Username", but defaults to "Id".
+ Sort string
+
+ // If true, exclude team members whose corresponding user is deleted.
+ ExcludeDeletedUsers bool
+
+ // Restrict to search in a list of teams and channels
+ ViewRestrictions *ViewUsersRestrictions
+}
+
+func EmailInviteWithErrorToEmails(o []*EmailInviteWithError) []string {
+ var ret []string
+ for _, o := range o {
+ if o.Error == nil {
+ ret = append(ret, o.Email)
+ }
+ }
+ return ret
+}
+
+func EmailInviteWithErrorToString(o *EmailInviteWithError) string {
+ return fmt.Sprintf("%s:%s", o.Email, o.Error.Error())
+}
+
+func TeamMembersWithErrorToTeamMembers(o []*TeamMemberWithError) []*TeamMember {
+ var ret []*TeamMember
+ for _, o := range o {
+ if o.Error == nil {
+ ret = append(ret, o.Member)
+ }
+ }
+ return ret
+}
+
+func TeamMemberWithErrorToString(o *TeamMemberWithError) string {
+ return fmt.Sprintf("%s:%s", o.UserId, o.Error.Error())
+}
+
+func (o *TeamMember) IsValid() *AppError {
+
+ if !IsValidId(o.TeamId) {
+ return NewAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !IsValidId(o.UserId) {
+ 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/v6/model/team_member_serial_gen.go b/vendor/github.com/mattermost/mattermost-server/v6/model/team_member_serial_gen.go
new file mode 100644
index 00000000..044a608a
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/team_member_serial_gen.go
@@ -0,0 +1,193 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+// Code generated by github.com/tinylib/msgp DO NOT EDIT.
+
+import (
+ "github.com/tinylib/msgp/msgp"
+)
+
+// DecodeMsg implements msgp.Decodable
+func (z *TeamMember) DecodeMsg(dc *msgp.Reader) (err error) {
+ var zb0001 uint32
+ zb0001, err = dc.ReadArrayHeader()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0001 != 8 {
+ err = msgp.ArrayError{Wanted: 8, Got: zb0001}
+ return
+ }
+ z.TeamId, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "TeamId")
+ return
+ }
+ z.UserId, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "UserId")
+ return
+ }
+ z.Roles, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ z.DeleteAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "DeleteAt")
+ return
+ }
+ z.SchemeGuest, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeGuest")
+ return
+ }
+ z.SchemeUser, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeUser")
+ return
+ }
+ z.SchemeAdmin, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeAdmin")
+ return
+ }
+ z.ExplicitRoles, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "ExplicitRoles")
+ return
+ }
+ return
+}
+
+// EncodeMsg implements msgp.Encodable
+func (z *TeamMember) EncodeMsg(en *msgp.Writer) (err error) {
+ // array header, size 8
+ err = en.Append(0x98)
+ if err != nil {
+ return
+ }
+ err = en.WriteString(z.TeamId)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamId")
+ return
+ }
+ err = en.WriteString(z.UserId)
+ if err != nil {
+ err = msgp.WrapError(err, "UserId")
+ return
+ }
+ err = en.WriteString(z.Roles)
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ err = en.WriteInt64(z.DeleteAt)
+ if err != nil {
+ err = msgp.WrapError(err, "DeleteAt")
+ return
+ }
+ err = en.WriteBool(z.SchemeGuest)
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeGuest")
+ return
+ }
+ err = en.WriteBool(z.SchemeUser)
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeUser")
+ return
+ }
+ err = en.WriteBool(z.SchemeAdmin)
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeAdmin")
+ return
+ }
+ err = en.WriteString(z.ExplicitRoles)
+ if err != nil {
+ err = msgp.WrapError(err, "ExplicitRoles")
+ return
+ }
+ return
+}
+
+// MarshalMsg implements msgp.Marshaler
+func (z *TeamMember) MarshalMsg(b []byte) (o []byte, err error) {
+ o = msgp.Require(b, z.Msgsize())
+ // array header, size 8
+ o = append(o, 0x98)
+ o = msgp.AppendString(o, z.TeamId)
+ o = msgp.AppendString(o, z.UserId)
+ o = msgp.AppendString(o, z.Roles)
+ o = msgp.AppendInt64(o, z.DeleteAt)
+ o = msgp.AppendBool(o, z.SchemeGuest)
+ o = msgp.AppendBool(o, z.SchemeUser)
+ o = msgp.AppendBool(o, z.SchemeAdmin)
+ o = msgp.AppendString(o, z.ExplicitRoles)
+ return
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *TeamMember) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ var zb0001 uint32
+ zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0001 != 8 {
+ err = msgp.ArrayError{Wanted: 8, Got: zb0001}
+ return
+ }
+ z.TeamId, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "TeamId")
+ return
+ }
+ z.UserId, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "UserId")
+ return
+ }
+ z.Roles, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ z.DeleteAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "DeleteAt")
+ return
+ }
+ z.SchemeGuest, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeGuest")
+ return
+ }
+ z.SchemeUser, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeUser")
+ return
+ }
+ z.SchemeAdmin, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "SchemeAdmin")
+ return
+ }
+ z.ExplicitRoles, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "ExplicitRoles")
+ return
+ }
+ o = bts
+ return
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z *TeamMember) Msgsize() (s int) {
+ s = 1 + msgp.StringPrefixSize + len(z.TeamId) + msgp.StringPrefixSize + len(z.UserId) + msgp.StringPrefixSize + len(z.Roles) + msgp.Int64Size + msgp.BoolSize + msgp.BoolSize + msgp.BoolSize + msgp.StringPrefixSize + len(z.ExplicitRoles)
+ return
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/team_search.go b/vendor/github.com/mattermost/mattermost-server/v6/model/team_search.go
new file mode 100644
index 00000000..c4a39275
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/team_search.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type TeamSearch struct {
+ Term string `json:"term"`
+ Page *int `json:"page,omitempty"`
+ PerPage *int `json:"per_page,omitempty"`
+ AllowOpenInvite *bool `json:"allow_open_invite,omitempty"`
+ GroupConstrained *bool `json:"group_constrained,omitempty"`
+ IncludeGroupConstrained *bool `json:"include_group_constrained,omitempty"`
+ PolicyID *string `json:"policy_id,omitempty"`
+ ExcludePolicyConstrained *bool `json:"exclude_policy_constrained,omitempty"`
+ IncludePolicyID *bool `json:"-"`
+ IncludeDeleted *bool `json:"-"`
+ TeamType *string `json:"-"`
+}
+
+func (t *TeamSearch) IsPaginated() bool {
+ return t.Page != nil && t.PerPage != nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/team_stats.go b/vendor/github.com/mattermost/mattermost-server/v6/model/team_stats.go
new file mode 100644
index 00000000..0a3a7387
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/team_stats.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type TeamStats struct {
+ TeamId string `json:"team_id"`
+ TotalMemberCount int64 `json:"total_member_count"`
+ ActiveMemberCount int64 `json:"active_member_count"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/terms_of_service.go b/vendor/github.com/mattermost/mattermost-server/v6/model/terms_of_service.go
new file mode 100644
index 00000000..b6e531a2
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/terms_of_service.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+ "unicode/utf8"
+)
+
+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 !IsValidId(t.Id) {
+ return InvalidTermsOfServiceError("id", "")
+ }
+
+ if t.CreateAt == 0 {
+ return InvalidTermsOfServiceError("create_at", t.Id)
+ }
+
+ if !IsValidId(t.UserId) {
+ return InvalidTermsOfServiceError("user_id", t.Id)
+ }
+
+ if utf8.RuneCountInString(t.Text) > PostMessageMaxRunesV2 {
+ return InvalidTermsOfServiceError("text", t.Id)
+ }
+
+ return nil
+}
+
+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("TermsOfService.IsValid", id, map[string]interface{}{"MaxLength": PostMessageMaxRunesV2}, 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/v6/model/thread.go b/vendor/github.com/mattermost/mattermost-server/v6/model/thread.go
new file mode 100644
index 00000000..e774e87a
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/thread.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type Thread struct {
+ PostId string `json:"id"`
+ ChannelId string `json:"channel_id"`
+ ReplyCount int64 `json:"reply_count"`
+ LastReplyAt int64 `json:"last_reply_at"`
+ Participants StringArray `json:"participants"`
+}
+
+type ThreadResponse struct {
+ PostId string `json:"id"`
+ ReplyCount int64 `json:"reply_count"`
+ LastReplyAt int64 `json:"last_reply_at"`
+ LastViewedAt int64 `json:"last_viewed_at"`
+ Participants []*User `json:"participants"`
+ Post *Post `json:"post"`
+ UnreadReplies int64 `json:"unread_replies"`
+ UnreadMentions int64 `json:"unread_mentions"`
+}
+
+type Threads struct {
+ Total int64 `json:"total"`
+ TotalUnreadThreads int64 `json:"total_unread_threads"`
+ TotalUnreadMentions int64 `json:"total_unread_mentions"`
+ Threads []*ThreadResponse `json:"threads"`
+}
+
+type GetUserThreadsOpts struct {
+ // PageSize specifies the size of the returned chunk of results. Default = 30
+ PageSize uint64
+
+ // Extended will enrich the response with participant details. Default = false
+ Extended bool
+
+ // Deleted will specify that even deleted threads should be returned (For mobile sync). Default = false
+ Deleted bool
+
+ // Since filters the threads based on their LastUpdateAt timestamp.
+ Since uint64
+
+ // Before specifies thread id as a cursor for pagination and will return `PageSize` threads before the cursor
+ Before string
+
+ // After specifies thread id as a cursor for pagination and will return `PageSize` threads after the cursor
+ After string
+
+ // Unread will make sure that only threads with unread replies are returned
+ Unread bool
+
+ // TotalsOnly will not fetch any threads and just fetch the total counts
+ TotalsOnly bool
+
+ // TeamOnly will only fetch threads and unreads for the specified team and excludes DMs/GMs
+ TeamOnly bool
+}
+
+func (o *Thread) Etag() string {
+ return Etag(o.PostId, o.LastReplyAt)
+}
+
+type ThreadMembership struct {
+ PostId string `json:"post_id"`
+ UserId string `json:"user_id"`
+ Following bool `json:"following"`
+ LastViewed int64 `json:"last_view_at"`
+ LastUpdated int64 `json:"last_update_at"`
+ UnreadMentions int64 `json:"unread_mentions"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/token.go b/vendor/github.com/mattermost/mattermost-server/v6/model/token.go
new file mode 100644
index 00000000..90fc729f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/token.go
@@ -0,0 +1,42 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "net/http"
+)
+
+const (
+ TokenSize = 64
+ MaxTokenExipryTime = 1000 * 60 * 60 * 48 // 48 hour
+ TokenTypeOAuth = "oauth"
+)
+
+type Token struct {
+ Token string
+ CreateAt int64
+ Type string
+ Extra string
+}
+
+func NewToken(tokentype, extra string) *Token {
+ return &Token{
+ Token: NewRandomString(TokenSize),
+ CreateAt: GetMillis(),
+ Type: tokentype,
+ Extra: extra,
+ }
+}
+
+func (t *Token) IsValid() *AppError {
+ if len(t.Token) != TokenSize {
+ 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/v6/model/typing_request.go b/vendor/github.com/mattermost/mattermost-server/v6/model/typing_request.go
new file mode 100644
index 00000000..f7a34341
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/typing_request.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type TypingRequest struct {
+ ChannelId string `json:"channel_id"`
+ ParentId string `json:"parent_id"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/upload_session.go b/vendor/github.com/mattermost/mattermost-server/v6/model/upload_session.go
new file mode 100644
index 00000000..994e7fb3
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/upload_session.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+)
+
+// UploadType defines the type of an upload.
+type UploadType string
+
+const (
+ UploadTypeAttachment UploadType = "attachment"
+ UploadTypeImport UploadType = "import"
+)
+
+// UploadNoUserID is a "fake" user id used by the API layer when in local mode.
+const UploadNoUserID = "nouser"
+
+// UploadSession contains information used to keep track of a file upload.
+type UploadSession struct {
+ // The unique identifier for the session.
+ Id string `json:"id"`
+ // The type of the upload.
+ Type UploadType `json:"type"`
+ // The timestamp of creation.
+ CreateAt int64 `json:"create_at"`
+ // The id of the user performing the upload.
+ UserId string `json:"user_id"`
+ // The id of the channel to upload to.
+ ChannelId string `json:"channel_id,omitempty"`
+ // The name of the file to upload.
+ Filename string `json:"filename"`
+ // The path where the file is stored.
+ Path string `json:"-"`
+ // The size of the file to upload.
+ FileSize int64 `json:"file_size"`
+ // The amount of received data in bytes. If equal to FileSize it means the
+ // upload has finished.
+ FileOffset int64 `json:"file_offset"`
+ // Id of remote cluster if uploading for shared channel
+ RemoteId string `json:"remote_id"`
+ // Requested file id if uploading for shared channel
+ ReqFileId string `json:"req_file_id"`
+}
+
+// PreSave is a utility function used to fill required information.
+func (us *UploadSession) PreSave() {
+ if us.Id == "" {
+ us.Id = NewId()
+ }
+
+ if us.CreateAt == 0 {
+ us.CreateAt = GetMillis()
+ }
+}
+
+// IsValid validates an UploadType. It returns an error in case of
+// failure.
+func (t UploadType) IsValid() error {
+ switch t {
+ case UploadTypeAttachment:
+ return nil
+ case UploadTypeImport:
+ return nil
+ default:
+ }
+ return fmt.Errorf("invalid UploadType %s", t)
+}
+
+// IsValid validates an UploadSession. It returns an error in case of
+// failure.
+func (us *UploadSession) IsValid() *AppError {
+ if !IsValidId(us.Id) {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.id.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if err := us.Type.IsValid(); err != nil {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.type.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if !IsValidId(us.UserId) && us.UserId != UploadNoUserID {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.user_id.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ if us.Type == UploadTypeAttachment && !IsValidId(us.ChannelId) {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.channel_id.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ if us.CreateAt == 0 {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.create_at.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ if us.Filename == "" {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.filename.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ if us.FileSize <= 0 {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.file_size.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ if us.FileOffset < 0 || us.FileOffset > us.FileSize {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.file_offset.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ if us.Path == "" {
+ return NewAppError("UploadSession.IsValid", "model.upload_session.is_valid.path.app_error", nil, "id="+us.Id, http.StatusBadRequest)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user.go
new file mode 100644
index 00000000..2e843ea1
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user.go
@@ -0,0 +1,912 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "regexp"
+ "sort"
+ "strings"
+ "unicode/utf8"
+
+ "golang.org/x/crypto/bcrypt"
+ "golang.org/x/text/language"
+
+ "github.com/mattermost/mattermost-server/v6/services/timezones"
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+const (
+ Me = "me"
+ UserNotifyAll = "all"
+ UserNotifyHere = "here"
+ UserNotifyMention = "mention"
+ UserNotifyNone = "none"
+ DesktopNotifyProp = "desktop"
+ DesktopSoundNotifyProp = "desktop_sound"
+ MarkUnreadNotifyProp = "mark_unread"
+ PushNotifyProp = "push"
+ PushStatusNotifyProp = "push_status"
+ EmailNotifyProp = "email"
+ ChannelMentionsNotifyProp = "channel"
+ CommentsNotifyProp = "comments"
+ MentionKeysNotifyProp = "mention_keys"
+ CommentsNotifyNever = "never"
+ CommentsNotifyRoot = "root"
+ CommentsNotifyAny = "any"
+ CommentsNotifyCRT = "crt"
+ FirstNameNotifyProp = "first_name"
+ AutoResponderActiveNotifyProp = "auto_responder_active"
+ AutoResponderMessageNotifyProp = "auto_responder_message"
+ DesktopThreadsNotifyProp = "desktop_threads"
+ PushThreadsNotifyProp = "push_threads"
+ EmailThreadsNotifyProp = "email_threads"
+
+ DefaultLocale = "en"
+ UserAuthServiceEmail = "email"
+
+ UserEmailMaxLength = 128
+ UserNicknameMaxRunes = 64
+ UserPositionMaxRunes = 128
+ UserFirstNameMaxRunes = 64
+ UserLastNameMaxRunes = 64
+ UserAuthDataMaxLength = 128
+ UserNameMaxLength = 64
+ UserNameMinLength = 1
+ UserPasswordMaxLength = 72
+ UserLocaleMaxLength = 5
+ UserTimezoneMaxRunes = 256
+)
+
+//msgp:tuple User
+
+// User contains the details about the user.
+// This struct's serializer methods are auto-generated. If a new field is added/removed,
+// please run make gen-serialized.
+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"`
+ RemoteId *string `json:"remote_id,omitempty"`
+ LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
+ IsBot bool `db:"-" json:"is_bot,omitempty"`
+ BotDescription string `db:"-" json:"bot_description,omitempty"`
+ BotLastIconUpdate int64 `db:"-" json:"bot_last_icon_update,omitempty"`
+ TermsOfServiceId string `db:"-" json:"terms_of_service_id,omitempty"`
+ TermsOfServiceCreateAt int64 `db:"-" json:"terms_of_service_create_at,omitempty"`
+ DisableWelcomeEmail bool `db:"-" json:"disable_welcome_email"`
+}
+
+//msgp UserMap
+
+// UserMap is a map from a userId to a user object.
+// It is used to generate methods which can be used for fast serialization/de-serialization.
+type UserMap map[string]*User
+
+//msgp:ignore UserUpdate
+type UserUpdate struct {
+ Old *User
+ New *User
+}
+
+//msgp:ignore UserPatch
+type UserPatch struct {
+ Username *string `json:"username"`
+ Password *string `json:"password,omitempty"`
+ 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"`
+ RemoteId *string `json:"remote_id"`
+}
+
+//msgp:ignore UserAuth
+type UserAuth struct {
+ Password string `json:"password,omitempty"` // DEPRECATED: It is not used.
+ AuthData *string `json:"auth_data,omitempty"`
+ AuthService string `json:"auth_service,omitempty"`
+}
+
+//msgp:ignore UserForIndexing
+type UserForIndexing struct {
+ Id string `json:"id"`
+ Username string `json:"username"`
+ Nickname string `json:"nickname"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ Roles string `json:"roles"`
+ CreateAt int64 `json:"create_at"`
+ DeleteAt int64 `json:"delete_at"`
+ TeamsIds []string `json:"team_id"`
+ ChannelsIds []string `json:"channel_id"`
+}
+
+//msgp:ignore ViewUsersRestrictions
+type ViewUsersRestrictions struct {
+ Teams []string
+ Channels []string
+}
+
+func (r *ViewUsersRestrictions) Hash() string {
+ if r == nil {
+ return ""
+ }
+ ids := append(r.Teams, r.Channels...)
+ sort.Strings(ids)
+ hash := sha256.New()
+ hash.Write([]byte(strings.Join(ids, "")))
+ return fmt.Sprintf("%x", hash.Sum(nil))
+}
+
+//msgp:ignore UserSlice
+type UserSlice []*User
+
+func (u UserSlice) Usernames() []string {
+ usernames := []string{}
+ for _, user := range u {
+ usernames = append(usernames, user.Username)
+ }
+ sort.Strings(usernames)
+ return usernames
+}
+
+func (u UserSlice) IDs() []string {
+ ids := []string{}
+ for _, user := range u {
+ ids = append(ids, user.Id)
+ }
+ return ids
+}
+
+func (u UserSlice) FilterWithoutBots() UserSlice {
+ var matches []*User
+
+ for _, user := range u {
+ if !user.IsBot {
+ matches = append(matches, user)
+ }
+ }
+ return UserSlice(matches)
+}
+
+func (u UserSlice) FilterByActive(active bool) UserSlice {
+ var matches []*User
+
+ for _, user := range u {
+ if user.DeleteAt == 0 && active {
+ matches = append(matches, user)
+ } else if user.DeleteAt != 0 && !active {
+ matches = append(matches, user)
+ }
+ }
+ return UserSlice(matches)
+}
+
+func (u UserSlice) FilterByID(ids []string) UserSlice {
+ var matches []*User
+ for _, user := range u {
+ for _, id := range ids {
+ if id == user.Id {
+ matches = append(matches, user)
+ }
+ }
+ }
+ return UserSlice(matches)
+}
+
+func (u UserSlice) FilterWithoutID(ids []string) UserSlice {
+ var keep []*User
+ for _, user := range u {
+ present := false
+ for _, id := range ids {
+ if id == user.Id {
+ present = true
+ }
+ }
+ if !present {
+ keep = append(keep, user)
+ }
+ }
+ return UserSlice(keep)
+}
+
+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 &copyUser
+}
+
+// IsValid validates the user and returns an error if it isn't configured
+// correctly.
+func (u *User) IsValid() *AppError {
+
+ if !IsValidId(u.Id) {
+ return InvalidUserError("id", "")
+ }
+
+ if u.CreateAt == 0 {
+ return InvalidUserError("create_at", u.Id)
+ }
+
+ if u.UpdateAt == 0 {
+ return InvalidUserError("update_at", u.Id)
+ }
+
+ if u.IsRemote() {
+ if !IsValidUsernameAllowRemote(u.Username) {
+ return InvalidUserError("username", u.Id)
+ }
+ } else {
+ if !IsValidUsername(u.Username) {
+ return InvalidUserError("username", u.Id)
+ }
+ }
+
+ if len(u.Email) > UserEmailMaxLength || u.Email == "" || !IsValidEmail(u.Email) {
+ return InvalidUserError("email", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.Nickname) > UserNicknameMaxRunes {
+ return InvalidUserError("nickname", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.Position) > UserPositionMaxRunes {
+ return InvalidUserError("position", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.FirstName) > UserFirstNameMaxRunes {
+ return InvalidUserError("first_name", u.Id)
+ }
+
+ if utf8.RuneCountInString(u.LastName) > UserLastNameMaxRunes {
+ return InvalidUserError("last_name", u.Id)
+ }
+
+ if u.AuthData != nil && len(*u.AuthData) > UserAuthDataMaxLength {
+ return InvalidUserError("auth_data", u.Id)
+ }
+
+ if u.AuthData != nil && *u.AuthData != "" && u.AuthService == "" {
+ return InvalidUserError("auth_data_type", u.Id)
+ }
+
+ if u.Password != "" && u.AuthData != nil && *u.AuthData != "" {
+ return InvalidUserError("auth_data_pwd", u.Id)
+ }
+
+ if len(u.Password) > UserPasswordMaxLength {
+ return InvalidUserError("password_limit", u.Id)
+ }
+
+ if !IsValidLocale(u.Locale) {
+ return InvalidUserError("locale", u.Id)
+ }
+
+ if len(u.Timezone) > 0 {
+ if tzJSON, err := json.Marshal(u.Timezone); err != nil {
+ return NewAppError("User.IsValid", "model.user.is_valid.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
+ } else if utf8.RuneCount(tzJSON) > UserTimezoneMaxRunes {
+ return InvalidUserError("timezone_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 = SanitizeUnicode(u.Username)
+ u.FirstName = SanitizeUnicode(u.FirstName)
+ u.LastName = SanitizeUnicode(u.LastName)
+ u.Nickname = SanitizeUnicode(u.Nickname)
+
+ 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 = DefaultLocale
+ }
+
+ 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 = timezones.DefaultUserTimezone()
+ }
+
+ if u.Password != "" {
+ u.Password = HashPassword(u.Password)
+ }
+}
+
+// PreUpdate should be run before updating the user in the db.
+func (u *User) PreUpdate() {
+ u.Username = SanitizeUnicode(u.Username)
+ u.FirstName = SanitizeUnicode(u.FirstName)
+ u.LastName = SanitizeUnicode(u.LastName)
+ u.Nickname = SanitizeUnicode(u.Nickname)
+ u.BotDescription = SanitizeUnicode(u.BotDescription)
+
+ u.Username = NormalizeUsername(u.Username)
+ u.Email = NormalizeEmail(u.Email)
+ u.UpdateAt = GetMillis()
+
+ u.FirstName = SanitizeUnicode(u.FirstName)
+ u.LastName = SanitizeUnicode(u.LastName)
+ u.Nickname = SanitizeUnicode(u.Nickname)
+ u.BotDescription = SanitizeUnicode(u.BotDescription)
+
+ if u.AuthData != nil && *u.AuthData == "" {
+ u.AuthData = nil
+ }
+
+ if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
+ u.SetDefaultNotifications()
+ } else if _, ok := u.NotifyProps[MentionKeysNotifyProp]; ok {
+ // Remove any blank mention keys
+ splitKeys := strings.Split(u.NotifyProps[MentionKeysNotifyProp], ",")
+ goodKeys := []string{}
+ for _, key := range splitKeys {
+ if key != "" {
+ goodKeys = append(goodKeys, strings.ToLower(key))
+ }
+ }
+ u.NotifyProps[MentionKeysNotifyProp] = strings.Join(goodKeys, ",")
+ }
+}
+
+func (u *User) SetDefaultNotifications() {
+ u.NotifyProps = make(map[string]string)
+ u.NotifyProps[EmailNotifyProp] = "true"
+ u.NotifyProps[PushNotifyProp] = UserNotifyMention
+ u.NotifyProps[DesktopNotifyProp] = UserNotifyMention
+ u.NotifyProps[DesktopSoundNotifyProp] = "true"
+ u.NotifyProps[MentionKeysNotifyProp] = ""
+ u.NotifyProps[ChannelMentionsNotifyProp] = "true"
+ u.NotifyProps[PushStatusNotifyProp] = StatusAway
+ u.NotifyProps[CommentsNotifyProp] = CommentsNotifyNever
+ u.NotifyProps[FirstNameNotifyProp] = "false"
+ u.NotifyProps[DesktopThreadsNotifyProp] = UserNotifyAll
+ u.NotifyProps[EmailThreadsNotifyProp] = UserNotifyAll
+ u.NotifyProps[PushThreadsNotifyProp] = UserNotifyAll
+}
+
+func (u *User) UpdateMentionKeysFromUsername(oldUsername string) {
+ nonUsernameKeys := []string{}
+ for _, key := range u.GetMentionKeys() {
+ if key != oldUsername && key != "@"+oldUsername {
+ nonUsernameKeys = append(nonUsernameKeys, key)
+ }
+ }
+
+ u.NotifyProps[MentionKeysNotifyProp] = ""
+ if len(nonUsernameKeys) > 0 {
+ u.NotifyProps[MentionKeysNotifyProp] += "," + strings.Join(nonUsernameKeys, ",")
+ }
+}
+
+func (u *User) GetMentionKeys() []string {
+ var keys []string
+
+ for _, key := range strings.Split(u.NotifyProps[MentionKeysNotifyProp], ",") {
+ trimmedKey := strings.TrimSpace(key)
+
+ if trimmedKey == "" {
+ continue
+ }
+
+ keys = append(keys, trimmedKey)
+ }
+
+ return keys
+}
+
+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
+ }
+
+ if patch.RemoteId != nil {
+ u.RemoteId = patch.RemoteId
+ }
+}
+
+// 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, u.TermsOfServiceId, u.TermsOfServiceCreateAt, showFullName, showEmail, u.BotLastIconUpdate)
+}
+
+// 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 = ""
+ }
+}
+
+// Remove any input data from the user object that is not user controlled
+func (u *User) SanitizeInput(isAdmin bool) {
+ if !isAdmin {
+ u.AuthData = NewString("")
+ u.AuthService = ""
+ u.EmailVerified = false
+ }
+ u.LastPasswordUpdate = 0
+ u.LastPictureUpdate = 0
+ u.FailedAttempts = 0
+ u.MfaActive = false
+ u.MfaSecret = ""
+ u.Email = strings.TrimSpace(u.Email)
+}
+
+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) SetCustomStatus(cs *CustomStatus) error {
+ u.MakeNonNil()
+ statusJSON, jsonErr := json.Marshal(cs)
+ if jsonErr != nil {
+ return jsonErr
+ }
+ u.Props[UserPropsKeyCustomStatus] = string(statusJSON)
+ return nil
+}
+
+func (u *User) ClearCustomStatus() {
+ u.MakeNonNil()
+ u.Props[UserPropsKeyCustomStatus] = ""
+}
+
+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(baseName, nameFormat string) string {
+ displayName := baseName
+
+ if nameFormat == ShowNicknameFullName {
+ if u.Nickname != "" {
+ displayName = u.Nickname
+ } else if fullName := u.GetFullName(); fullName != "" {
+ displayName = fullName
+ }
+ } else if nameFormat == ShowFullName {
+ if fullName := u.GetFullName(); fullName != "" {
+ displayName = fullName
+ }
+ }
+
+ return displayName
+}
+
+func (u *User) GetDisplayName(nameFormat string) string {
+ displayName := u.Username
+
+ return u.getDisplayName(displayName, nameFormat)
+}
+
+func (u *User) GetDisplayNameWithPrefix(nameFormat, prefix string) string {
+ displayName := prefix + u.Username
+
+ return u.getDisplayName(displayName, nameFormat)
+}
+
+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) IsGuest() bool {
+ return IsInRole(u.Roles, SystemGuestRoleId)
+}
+
+func (u *User) IsSystemAdmin() bool {
+ return IsInRole(u.Roles, SystemAdminRoleId)
+}
+
+// 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 != UserAuthServiceEmail
+}
+
+func (u *User) IsOAuthUser() bool {
+ return u.AuthService == ServiceGitlab ||
+ u.AuthService == ServiceGoogle ||
+ u.AuthService == ServiceOffice365 ||
+ u.AuthService == ServiceOpenid
+}
+
+func (u *User) IsLDAPUser() bool {
+ return u.AuthService == UserAuthServiceLdap
+}
+
+func (u *User) IsSAMLUser() bool {
+ return u.AuthService == UserAuthServiceSaml
+}
+
+func (u *User) GetPreferredTimezone() string {
+ return GetPreferredTimezone(u.Timezone)
+}
+
+// IsRemote returns true if the user belongs to a remote cluster (has RemoteId).
+func (u *User) IsRemote() bool {
+ return u.RemoteId != nil && *u.RemoteId != ""
+}
+
+// GetRemoteID returns the remote id for this user or "" if not a remote user.
+func (u *User) GetRemoteID() string {
+ if u.RemoteId != nil {
+ return *u.RemoteId
+ }
+ return ""
+}
+
+// GetProp fetches a prop value by name.
+func (u *User) GetProp(name string) (string, bool) {
+ val, ok := u.Props[name]
+ return val, ok
+}
+
+// SetProp sets a prop value by name, creating the map if nil.
+// Not thread safe.
+func (u *User) SetProp(name string, value string) {
+ if u.Props == nil {
+ u.Props = make(map[string]string)
+ }
+ u.Props[name] = value
+}
+
+func (u *User) ToPatch() *UserPatch {
+ return &UserPatch{
+ Username: &u.Username, Password: &u.Password,
+ Nickname: &u.Nickname, FirstName: &u.FirstName, LastName: &u.LastName,
+ Position: &u.Position, Email: &u.Email,
+ Props: u.Props, NotifyProps: u.NotifyProps,
+ Locale: &u.Locale, Timezone: u.Timezone,
+ }
+}
+
+func (u *UserPatch) SetField(fieldName string, fieldValue string) {
+ switch fieldName {
+ case "FirstName":
+ u.FirstName = &fieldValue
+ case "LastName":
+ u.LastName = &fieldValue
+ case "Nickname":
+ u.Nickname = &fieldValue
+ case "Email":
+ u.Email = &fieldValue
+ case "Position":
+ u.Position = &fieldValue
+ case "Username":
+ u.Username = &fieldValue
+ }
+}
+
+// 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)
+}
+
+var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
+var validUsernameCharsForRemote = regexp.MustCompile(`^[a-z0-9\.\-_:]+$`)
+
+var restrictedUsernames = map[string]struct{}{
+ "all": {},
+ "channel": {},
+ "matterbot": {},
+ "system": {},
+}
+
+func IsValidUsername(s string) bool {
+ if len(s) < UserNameMinLength || len(s) > UserNameMaxLength {
+ return false
+ }
+
+ if !validUsernameChars.MatchString(s) {
+ return false
+ }
+
+ _, found := restrictedUsernames[s]
+ return !found
+}
+
+func IsValidUsernameAllowRemote(s string) bool {
+ if len(s) < UserNameMinLength || len(s) > UserNameMaxLength {
+ return false
+ }
+
+ if !validUsernameCharsForRemote.MatchString(s) {
+ return false
+ }
+
+ _, found := restrictedUsernames[s]
+ return !found
+}
+
+func CleanUsername(username string) string {
+ s := NormalizeUsername(strings.Replace(username, " ", "-", -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()
+ mlog.Warn("Generating new username since provided username was invalid",
+ mlog.String("provided_username", username), mlog.String("new_username", s))
+ }
+
+ return s
+}
+
+func IsValidLocale(locale string) bool {
+ if locale != "" {
+ if len(locale) > UserLocaleMaxLength {
+ return false
+ } else if _, err := language.Parse(locale); err != nil {
+ return false
+ }
+ }
+
+ return true
+}
+
+//msgp:ignore UserWithGroups
+type UserWithGroups struct {
+ User
+ GroupIDs *string `json:"-"`
+ Groups []*Group `json:"groups"`
+ SchemeGuest bool `json:"scheme_guest"`
+ SchemeUser bool `json:"scheme_user"`
+ SchemeAdmin bool `json:"scheme_admin"`
+}
+
+func (u *UserWithGroups) GetGroupIDs() []string {
+ if u.GroupIDs == nil {
+ return nil
+ }
+ trimmed := strings.TrimSpace(*u.GroupIDs)
+ if trimmed == "" {
+ return nil
+ }
+ return strings.Split(trimmed, ",")
+}
+
+//msgp:ignore UsersWithGroupsAndCount
+type UsersWithGroupsAndCount struct {
+ Users []*UserWithGroups `json:"users"`
+ Count int64 `json:"total_count"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token.go
new file mode 100644
index 00000000..dee31f18
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token.go
@@ -0,0 +1,41 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "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 !IsValidId(t.Id) {
+ 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 !IsValidId(t.UserId) {
+ 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
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token_search.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token_search.go
new file mode 100644
index 00000000..97fcde12
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_access_token_search.go
@@ -0,0 +1,8 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type UserAccessTokenSearch struct {
+ Term string `json:"term"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_autocomplete.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_autocomplete.go
new file mode 100644
index 00000000..b07131b3
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_autocomplete.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+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"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_count.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_count.go
new file mode 100644
index 00000000..ee474883
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_count.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+// Options for counting users
+type UserCountOptions struct {
+ // Should include users that are bots
+ IncludeBotAccounts bool
+ // Should include deleted users (of any type)
+ IncludeDeleted bool
+ // Exclude regular users
+ ExcludeRegularUsers bool
+ // Only include users on a specific team. "" for any team.
+ TeamId string
+ // Only include users on a specific channel. "" for any channel.
+ ChannelId string
+ // Restrict to search in a list of teams and channels
+ ViewRestrictions *ViewUsersRestrictions
+ // Only include users matching any of the given system wide roles.
+ Roles []string
+ // Only include users matching any of the given channel roles, must be used with ChannelId.
+ ChannelRoles []string
+ // Only include users matching any of the given team roles, must be used with TeamId.
+ TeamRoles []string
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_get.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_get.go
new file mode 100644
index 00000000..2748d735
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_get.go
@@ -0,0 +1,46 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type UserGetOptions struct {
+ // Filters the users in the team
+ InTeamId string
+ // Filters the users not in the team
+ NotInTeamId string
+ // Filters the users in the channel
+ InChannelId string
+ // Filters the users not in the channel
+ NotInChannelId string
+ // Filters the users in the group
+ InGroupId string
+ // Filters the users group constrained
+ GroupConstrained bool
+ // Filters the users without a team
+ WithoutTeam bool
+ // Filters the inactive users
+ Inactive bool
+ // Filters the active users
+ Active bool
+ // Filters for the given role
+ Role string
+ // Filters for users matching any of the given system wide roles
+ Roles []string
+ // Filters for users matching any of the given channel roles, must be used with InChannelId
+ ChannelRoles []string
+ // Filters for users matching any of the given team roles, must be used with InTeamId
+ TeamRoles []string
+ // Sorting option
+ Sort string
+ // Restrict to search in a list of teams and channels
+ ViewRestrictions *ViewUsersRestrictions
+ // Page
+ Page int
+ // Page size
+ PerPage int
+}
+
+type UserGetByIdsOptions struct {
+ // Since filters the users based on their UpdateAt timestamp.
+ Since int64
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_search.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_search.go
new file mode 100644
index 00000000..93bf6009
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_search.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+const UserSearchMaxLimit = 1000
+const UserSearchDefaultLimit = 100
+
+// UserSearch captures the parameters provided by a client for initiating a user search.
+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"`
+ InGroupId string `json:"in_group_id"`
+ GroupConstrained bool `json:"group_constrained"`
+ AllowInactive bool `json:"allow_inactive"`
+ WithoutTeam bool `json:"without_team"`
+ Limit int `json:"limit"`
+ Role string `json:"role"`
+ Roles []string `json:"roles"`
+ ChannelRoles []string `json:"channel_roles"`
+ TeamRoles []string `json:"team_roles"`
+}
+
+// UserSearchOptions captures internal parameters derived from the user's permissions and a
+// UserSearch request.
+type UserSearchOptions struct {
+ // IsAdmin tracks whether or not the search is being conducted by an administrator.
+ IsAdmin bool
+ // AllowEmails allows search to examine the emails of users.
+ AllowEmails bool
+ // AllowFullNames allows search to examine the full names of users, vs. just usernames and nicknames.
+ AllowFullNames bool
+ // AllowInactive configures whether or not to return inactive users in the search results.
+ AllowInactive bool
+ // Narrows the search to the group constrained users
+ GroupConstrained bool
+ // Limit limits the total number of results returned.
+ Limit int
+ // Filters for the given role
+ Role string
+ // Filters for users that have any of the given system roles
+ Roles []string
+ // Filters for users that have the given channel roles to be used when searching in a channel
+ ChannelRoles []string
+ // Filters for users that have the given team roles to be used when searching in a team
+ TeamRoles []string
+ // Restrict to search in a list of teams and channels
+ ViewRestrictions *ViewUsersRestrictions
+ // List of allowed channels
+ ListOfAllowedChannels []string
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_serial_gen.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_serial_gen.go
new file mode 100644
index 00000000..fb40b577
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_serial_gen.go
@@ -0,0 +1,826 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+// Code generated by github.com/tinylib/msgp DO NOT EDIT.
+
+import (
+ "github.com/tinylib/msgp/msgp"
+)
+
+// DecodeMsg implements msgp.Decodable
+func (z *User) DecodeMsg(dc *msgp.Reader) (err error) {
+ var zb0001 uint32
+ zb0001, err = dc.ReadArrayHeader()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0001 != 32 {
+ err = msgp.ArrayError{Wanted: 32, Got: zb0001}
+ return
+ }
+ z.Id, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Id")
+ return
+ }
+ z.CreateAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "CreateAt")
+ return
+ }
+ z.UpdateAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "UpdateAt")
+ return
+ }
+ z.DeleteAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "DeleteAt")
+ return
+ }
+ z.Username, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Username")
+ return
+ }
+ z.Password, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Password")
+ return
+ }
+ if dc.IsNil() {
+ err = dc.ReadNil()
+ if err != nil {
+ err = msgp.WrapError(err, "AuthData")
+ return
+ }
+ z.AuthData = nil
+ } else {
+ if z.AuthData == nil {
+ z.AuthData = new(string)
+ }
+ *z.AuthData, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "AuthData")
+ return
+ }
+ }
+ z.AuthService, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "AuthService")
+ return
+ }
+ z.Email, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Email")
+ return
+ }
+ z.EmailVerified, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "EmailVerified")
+ return
+ }
+ z.Nickname, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Nickname")
+ return
+ }
+ z.FirstName, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "FirstName")
+ return
+ }
+ z.LastName, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "LastName")
+ return
+ }
+ z.Position, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Position")
+ return
+ }
+ z.Roles, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ z.AllowMarketing, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "AllowMarketing")
+ return
+ }
+ err = z.Props.DecodeMsg(dc)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ err = z.NotifyProps.DecodeMsg(dc)
+ if err != nil {
+ err = msgp.WrapError(err, "NotifyProps")
+ return
+ }
+ z.LastPasswordUpdate, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "LastPasswordUpdate")
+ return
+ }
+ z.LastPictureUpdate, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "LastPictureUpdate")
+ return
+ }
+ z.FailedAttempts, err = dc.ReadInt()
+ if err != nil {
+ err = msgp.WrapError(err, "FailedAttempts")
+ return
+ }
+ z.Locale, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "Locale")
+ return
+ }
+ err = z.Timezone.DecodeMsg(dc)
+ if err != nil {
+ err = msgp.WrapError(err, "Timezone")
+ return
+ }
+ z.MfaActive, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "MfaActive")
+ return
+ }
+ z.MfaSecret, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "MfaSecret")
+ return
+ }
+ if dc.IsNil() {
+ err = dc.ReadNil()
+ if err != nil {
+ err = msgp.WrapError(err, "RemoteId")
+ return
+ }
+ z.RemoteId = nil
+ } else {
+ if z.RemoteId == nil {
+ z.RemoteId = new(string)
+ }
+ *z.RemoteId, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "RemoteId")
+ return
+ }
+ }
+ z.LastActivityAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "LastActivityAt")
+ return
+ }
+ z.IsBot, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "IsBot")
+ return
+ }
+ z.BotDescription, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "BotDescription")
+ return
+ }
+ z.BotLastIconUpdate, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "BotLastIconUpdate")
+ return
+ }
+ z.TermsOfServiceId, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err, "TermsOfServiceId")
+ return
+ }
+ z.TermsOfServiceCreateAt, err = dc.ReadInt64()
+ if err != nil {
+ err = msgp.WrapError(err, "TermsOfServiceCreateAt")
+ return
+ }
+ return
+}
+
+// EncodeMsg implements msgp.Encodable
+func (z *User) EncodeMsg(en *msgp.Writer) (err error) {
+ // array header, size 32
+ err = en.Append(0xdc, 0x0, 0x20)
+ if err != nil {
+ return
+ }
+ err = en.WriteString(z.Id)
+ if err != nil {
+ err = msgp.WrapError(err, "Id")
+ return
+ }
+ err = en.WriteInt64(z.CreateAt)
+ if err != nil {
+ err = msgp.WrapError(err, "CreateAt")
+ return
+ }
+ err = en.WriteInt64(z.UpdateAt)
+ if err != nil {
+ err = msgp.WrapError(err, "UpdateAt")
+ return
+ }
+ err = en.WriteInt64(z.DeleteAt)
+ if err != nil {
+ err = msgp.WrapError(err, "DeleteAt")
+ return
+ }
+ err = en.WriteString(z.Username)
+ if err != nil {
+ err = msgp.WrapError(err, "Username")
+ return
+ }
+ err = en.WriteString(z.Password)
+ if err != nil {
+ err = msgp.WrapError(err, "Password")
+ return
+ }
+ if z.AuthData == nil {
+ err = en.WriteNil()
+ if err != nil {
+ return
+ }
+ } else {
+ err = en.WriteString(*z.AuthData)
+ if err != nil {
+ err = msgp.WrapError(err, "AuthData")
+ return
+ }
+ }
+ err = en.WriteString(z.AuthService)
+ if err != nil {
+ err = msgp.WrapError(err, "AuthService")
+ return
+ }
+ err = en.WriteString(z.Email)
+ if err != nil {
+ err = msgp.WrapError(err, "Email")
+ return
+ }
+ err = en.WriteBool(z.EmailVerified)
+ if err != nil {
+ err = msgp.WrapError(err, "EmailVerified")
+ return
+ }
+ err = en.WriteString(z.Nickname)
+ if err != nil {
+ err = msgp.WrapError(err, "Nickname")
+ return
+ }
+ err = en.WriteString(z.FirstName)
+ if err != nil {
+ err = msgp.WrapError(err, "FirstName")
+ return
+ }
+ err = en.WriteString(z.LastName)
+ if err != nil {
+ err = msgp.WrapError(err, "LastName")
+ return
+ }
+ err = en.WriteString(z.Position)
+ if err != nil {
+ err = msgp.WrapError(err, "Position")
+ return
+ }
+ err = en.WriteString(z.Roles)
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ err = en.WriteBool(z.AllowMarketing)
+ if err != nil {
+ err = msgp.WrapError(err, "AllowMarketing")
+ return
+ }
+ err = z.Props.EncodeMsg(en)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ err = z.NotifyProps.EncodeMsg(en)
+ if err != nil {
+ err = msgp.WrapError(err, "NotifyProps")
+ return
+ }
+ err = en.WriteInt64(z.LastPasswordUpdate)
+ if err != nil {
+ err = msgp.WrapError(err, "LastPasswordUpdate")
+ return
+ }
+ err = en.WriteInt64(z.LastPictureUpdate)
+ if err != nil {
+ err = msgp.WrapError(err, "LastPictureUpdate")
+ return
+ }
+ err = en.WriteInt(z.FailedAttempts)
+ if err != nil {
+ err = msgp.WrapError(err, "FailedAttempts")
+ return
+ }
+ err = en.WriteString(z.Locale)
+ if err != nil {
+ err = msgp.WrapError(err, "Locale")
+ return
+ }
+ err = z.Timezone.EncodeMsg(en)
+ if err != nil {
+ err = msgp.WrapError(err, "Timezone")
+ return
+ }
+ err = en.WriteBool(z.MfaActive)
+ if err != nil {
+ err = msgp.WrapError(err, "MfaActive")
+ return
+ }
+ err = en.WriteString(z.MfaSecret)
+ if err != nil {
+ err = msgp.WrapError(err, "MfaSecret")
+ return
+ }
+ if z.RemoteId == nil {
+ err = en.WriteNil()
+ if err != nil {
+ return
+ }
+ } else {
+ err = en.WriteString(*z.RemoteId)
+ if err != nil {
+ err = msgp.WrapError(err, "RemoteId")
+ return
+ }
+ }
+ err = en.WriteInt64(z.LastActivityAt)
+ if err != nil {
+ err = msgp.WrapError(err, "LastActivityAt")
+ return
+ }
+ err = en.WriteBool(z.IsBot)
+ if err != nil {
+ err = msgp.WrapError(err, "IsBot")
+ return
+ }
+ err = en.WriteString(z.BotDescription)
+ if err != nil {
+ err = msgp.WrapError(err, "BotDescription")
+ return
+ }
+ err = en.WriteInt64(z.BotLastIconUpdate)
+ if err != nil {
+ err = msgp.WrapError(err, "BotLastIconUpdate")
+ return
+ }
+ err = en.WriteString(z.TermsOfServiceId)
+ if err != nil {
+ err = msgp.WrapError(err, "TermsOfServiceId")
+ return
+ }
+ err = en.WriteInt64(z.TermsOfServiceCreateAt)
+ if err != nil {
+ err = msgp.WrapError(err, "TermsOfServiceCreateAt")
+ return
+ }
+ return
+}
+
+// MarshalMsg implements msgp.Marshaler
+func (z *User) MarshalMsg(b []byte) (o []byte, err error) {
+ o = msgp.Require(b, z.Msgsize())
+ // array header, size 32
+ o = append(o, 0xdc, 0x0, 0x20)
+ o = msgp.AppendString(o, z.Id)
+ o = msgp.AppendInt64(o, z.CreateAt)
+ o = msgp.AppendInt64(o, z.UpdateAt)
+ o = msgp.AppendInt64(o, z.DeleteAt)
+ o = msgp.AppendString(o, z.Username)
+ o = msgp.AppendString(o, z.Password)
+ if z.AuthData == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o = msgp.AppendString(o, *z.AuthData)
+ }
+ o = msgp.AppendString(o, z.AuthService)
+ o = msgp.AppendString(o, z.Email)
+ o = msgp.AppendBool(o, z.EmailVerified)
+ o = msgp.AppendString(o, z.Nickname)
+ o = msgp.AppendString(o, z.FirstName)
+ o = msgp.AppendString(o, z.LastName)
+ o = msgp.AppendString(o, z.Position)
+ o = msgp.AppendString(o, z.Roles)
+ o = msgp.AppendBool(o, z.AllowMarketing)
+ o, err = z.Props.MarshalMsg(o)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ o, err = z.NotifyProps.MarshalMsg(o)
+ if err != nil {
+ err = msgp.WrapError(err, "NotifyProps")
+ return
+ }
+ o = msgp.AppendInt64(o, z.LastPasswordUpdate)
+ o = msgp.AppendInt64(o, z.LastPictureUpdate)
+ o = msgp.AppendInt(o, z.FailedAttempts)
+ o = msgp.AppendString(o, z.Locale)
+ o, err = z.Timezone.MarshalMsg(o)
+ if err != nil {
+ err = msgp.WrapError(err, "Timezone")
+ return
+ }
+ o = msgp.AppendBool(o, z.MfaActive)
+ o = msgp.AppendString(o, z.MfaSecret)
+ if z.RemoteId == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o = msgp.AppendString(o, *z.RemoteId)
+ }
+ o = msgp.AppendInt64(o, z.LastActivityAt)
+ o = msgp.AppendBool(o, z.IsBot)
+ o = msgp.AppendString(o, z.BotDescription)
+ o = msgp.AppendInt64(o, z.BotLastIconUpdate)
+ o = msgp.AppendString(o, z.TermsOfServiceId)
+ o = msgp.AppendInt64(o, z.TermsOfServiceCreateAt)
+ return
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *User) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ var zb0001 uint32
+ zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0001 != 32 {
+ err = msgp.ArrayError{Wanted: 32, Got: zb0001}
+ return
+ }
+ z.Id, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Id")
+ return
+ }
+ z.CreateAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "CreateAt")
+ return
+ }
+ z.UpdateAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "UpdateAt")
+ return
+ }
+ z.DeleteAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "DeleteAt")
+ return
+ }
+ z.Username, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Username")
+ return
+ }
+ z.Password, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Password")
+ return
+ }
+ if msgp.IsNil(bts) {
+ bts, err = msgp.ReadNilBytes(bts)
+ if err != nil {
+ return
+ }
+ z.AuthData = nil
+ } else {
+ if z.AuthData == nil {
+ z.AuthData = new(string)
+ }
+ *z.AuthData, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "AuthData")
+ return
+ }
+ }
+ z.AuthService, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "AuthService")
+ return
+ }
+ z.Email, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Email")
+ return
+ }
+ z.EmailVerified, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "EmailVerified")
+ return
+ }
+ z.Nickname, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Nickname")
+ return
+ }
+ z.FirstName, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "FirstName")
+ return
+ }
+ z.LastName, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "LastName")
+ return
+ }
+ z.Position, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Position")
+ return
+ }
+ z.Roles, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Roles")
+ return
+ }
+ z.AllowMarketing, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "AllowMarketing")
+ return
+ }
+ bts, err = z.Props.UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Props")
+ return
+ }
+ bts, err = z.NotifyProps.UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "NotifyProps")
+ return
+ }
+ z.LastPasswordUpdate, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "LastPasswordUpdate")
+ return
+ }
+ z.LastPictureUpdate, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "LastPictureUpdate")
+ return
+ }
+ z.FailedAttempts, bts, err = msgp.ReadIntBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "FailedAttempts")
+ return
+ }
+ z.Locale, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Locale")
+ return
+ }
+ bts, err = z.Timezone.UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Timezone")
+ return
+ }
+ z.MfaActive, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "MfaActive")
+ return
+ }
+ z.MfaSecret, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "MfaSecret")
+ return
+ }
+ if msgp.IsNil(bts) {
+ bts, err = msgp.ReadNilBytes(bts)
+ if err != nil {
+ return
+ }
+ z.RemoteId = nil
+ } else {
+ if z.RemoteId == nil {
+ z.RemoteId = new(string)
+ }
+ *z.RemoteId, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "RemoteId")
+ return
+ }
+ }
+ z.LastActivityAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "LastActivityAt")
+ return
+ }
+ z.IsBot, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "IsBot")
+ return
+ }
+ z.BotDescription, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "BotDescription")
+ return
+ }
+ z.BotLastIconUpdate, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "BotLastIconUpdate")
+ return
+ }
+ z.TermsOfServiceId, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "TermsOfServiceId")
+ return
+ }
+ z.TermsOfServiceCreateAt, bts, err = msgp.ReadInt64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "TermsOfServiceCreateAt")
+ return
+ }
+ o = bts
+ return
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z *User) Msgsize() (s int) {
+ s = 3 + msgp.StringPrefixSize + len(z.Id) + msgp.Int64Size + msgp.Int64Size + msgp.Int64Size + msgp.StringPrefixSize + len(z.Username) + msgp.StringPrefixSize + len(z.Password)
+ if z.AuthData == nil {
+ s += msgp.NilSize
+ } else {
+ s += msgp.StringPrefixSize + len(*z.AuthData)
+ }
+ s += msgp.StringPrefixSize + len(z.AuthService) + msgp.StringPrefixSize + len(z.Email) + msgp.BoolSize + msgp.StringPrefixSize + len(z.Nickname) + msgp.StringPrefixSize + len(z.FirstName) + msgp.StringPrefixSize + len(z.LastName) + msgp.StringPrefixSize + len(z.Position) + msgp.StringPrefixSize + len(z.Roles) + msgp.BoolSize + z.Props.Msgsize() + z.NotifyProps.Msgsize() + msgp.Int64Size + msgp.Int64Size + msgp.IntSize + msgp.StringPrefixSize + len(z.Locale) + z.Timezone.Msgsize() + msgp.BoolSize + msgp.StringPrefixSize + len(z.MfaSecret)
+ if z.RemoteId == nil {
+ s += msgp.NilSize
+ } else {
+ s += msgp.StringPrefixSize + len(*z.RemoteId)
+ }
+ s += msgp.Int64Size + msgp.BoolSize + msgp.StringPrefixSize + len(z.BotDescription) + msgp.Int64Size + msgp.StringPrefixSize + len(z.TermsOfServiceId) + msgp.Int64Size
+ return
+}
+
+// DecodeMsg implements msgp.Decodable
+func (z *UserMap) DecodeMsg(dc *msgp.Reader) (err error) {
+ var zb0003 uint32
+ zb0003, err = dc.ReadMapHeader()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if (*z) == nil {
+ (*z) = make(UserMap, zb0003)
+ } else if len((*z)) > 0 {
+ for key := range *z {
+ delete((*z), key)
+ }
+ }
+ for zb0003 > 0 {
+ zb0003--
+ var zb0001 string
+ var zb0002 *User
+ zb0001, err = dc.ReadString()
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if dc.IsNil() {
+ err = dc.ReadNil()
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ zb0002 = nil
+ } else {
+ if zb0002 == nil {
+ zb0002 = new(User)
+ }
+ err = zb0002.DecodeMsg(dc)
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ }
+ (*z)[zb0001] = zb0002
+ }
+ return
+}
+
+// EncodeMsg implements msgp.Encodable
+func (z UserMap) EncodeMsg(en *msgp.Writer) (err error) {
+ err = en.WriteMapHeader(uint32(len(z)))
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ for zb0004, zb0005 := range z {
+ err = en.WriteString(zb0004)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0005 == nil {
+ err = en.WriteNil()
+ if err != nil {
+ return
+ }
+ } else {
+ err = zb0005.EncodeMsg(en)
+ if err != nil {
+ err = msgp.WrapError(err, zb0004)
+ return
+ }
+ }
+ }
+ return
+}
+
+// MarshalMsg implements msgp.Marshaler
+func (z UserMap) MarshalMsg(b []byte) (o []byte, err error) {
+ o = msgp.Require(b, z.Msgsize())
+ o = msgp.AppendMapHeader(o, uint32(len(z)))
+ for zb0004, zb0005 := range z {
+ o = msgp.AppendString(o, zb0004)
+ if zb0005 == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o, err = zb0005.MarshalMsg(o)
+ if err != nil {
+ err = msgp.WrapError(err, zb0004)
+ return
+ }
+ }
+ }
+ return
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *UserMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ var zb0003 uint32
+ zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if (*z) == nil {
+ (*z) = make(UserMap, zb0003)
+ } else if len((*z)) > 0 {
+ for key := range *z {
+ delete((*z), key)
+ }
+ }
+ for zb0003 > 0 {
+ var zb0001 string
+ var zb0002 *User
+ zb0003--
+ zb0001, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if msgp.IsNil(bts) {
+ bts, err = msgp.ReadNilBytes(bts)
+ if err != nil {
+ return
+ }
+ zb0002 = nil
+ } else {
+ if zb0002 == nil {
+ zb0002 = new(User)
+ }
+ bts, err = zb0002.UnmarshalMsg(bts)
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ }
+ (*z)[zb0001] = zb0002
+ }
+ o = bts
+ return
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z UserMap) Msgsize() (s int) {
+ s = msgp.MapHeaderSize
+ if z != nil {
+ for zb0004, zb0005 := range z {
+ _ = zb0005
+ s += msgp.StringPrefixSize + len(zb0004)
+ if zb0005 == nil {
+ s += msgp.NilSize
+ } else {
+ s += zb0005.Msgsize()
+ }
+ }
+ }
+ return
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/user_terms_of_service.go b/vendor/github.com/mattermost/mattermost-server/v6/model/user_terms_of_service.go
new file mode 100644
index 00000000..880c0786
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/user_terms_of_service.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "fmt"
+ "net/http"
+)
+
+type UserTermsOfService struct {
+ UserId string `json:"user_id"`
+ TermsOfServiceId string `json:"terms_of_service_id"`
+ CreateAt int64 `json:"create_at"`
+}
+
+func (ut *UserTermsOfService) IsValid() *AppError {
+ if !IsValidId(ut.UserId) {
+ return InvalidUserTermsOfServiceError("user_id", ut.UserId)
+ }
+
+ if !IsValidId(ut.TermsOfServiceId) {
+ return InvalidUserTermsOfServiceError("terms_of_service_id", ut.UserId)
+ }
+
+ if ut.CreateAt == 0 {
+ return InvalidUserTermsOfServiceError("create_at", ut.UserId)
+ }
+
+ return nil
+}
+
+func (ut *UserTermsOfService) PreSave() {
+ if ut.UserId == "" {
+ ut.UserId = NewId()
+ }
+
+ ut.CreateAt = GetMillis()
+}
+
+func InvalidUserTermsOfServiceError(fieldName string, userTermsOfServiceId string) *AppError {
+ id := fmt.Sprintf("model.user_terms_of_service.is_valid.%s.app_error", fieldName)
+ details := ""
+ if userTermsOfServiceId != "" {
+ details = "user_terms_of_service_user_id=" + userTermsOfServiceId
+ }
+ return NewAppError("UserTermsOfService.IsValid", id, nil, details, http.StatusBadRequest)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/users_stats.go b/vendor/github.com/mattermost/mattermost-server/v6/model/users_stats.go
new file mode 100644
index 00000000..1a7becf2
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/users_stats.go
@@ -0,0 +1,8 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+type UsersStats struct {
+ TotalUsersCount int64 `json:"total_users_count"`
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/utils.go b/vendor/github.com/mattermost/mattermost-server/v6/model/utils.go
new file mode 100644
index 00000000..fab3f494
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/utils.go
@@ -0,0 +1,587 @@
+// 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"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+ "unicode"
+
+ "github.com/mattermost/mattermost-server/v6/shared/i18n"
+ "github.com/pborman/uuid"
+)
+
+const (
+ LowercaseLetters = "abcdefghijklmnopqrstuvwxyz"
+ UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ NUMBERS = "0123456789"
+ SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
+)
+
+type StringInterface map[string]interface{}
+type StringArray []string
+
+func (sa StringArray) Remove(input string) StringArray {
+ for index := range sa {
+ if sa[index] == input {
+ ret := make(StringArray, 0, len(sa)-1)
+ ret = append(ret, sa[:index]...)
+ return append(ret, sa[index+1:]...)
+ }
+ }
+ return sa
+}
+
+func (sa StringArray) Contains(input string) bool {
+ for index := range sa {
+ if sa[index] == input {
+ return true
+ }
+ }
+
+ return false
+}
+func (sa StringArray) Equals(input StringArray) bool {
+
+ if len(sa) != len(input) {
+ return false
+ }
+
+ for index := range sa {
+
+ if sa[index] != input[index] {
+ return false
+ }
+ }
+
+ return true
+}
+
+var translateFunc i18n.TranslateFunc
+var translateFuncOnce sync.Once
+
+func AppErrorInit(t i18n.TranslateFunc) {
+ translateFuncOnce.Do(func() {
+ 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 i18n.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 i18n.TranslateFunc) string {
+ if er.params == nil {
+ return T(er.Id)
+ }
+ 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 NewAppError("AppErrorFromJSON", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError)
+ }
+ return &er
+}
+
+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()
+}
+
+// NewRandomTeamName is a NewId that will be a valid team name.
+func NewRandomTeamName() string {
+ teamName := NewId()
+ for IsReservedTeamName(teamName) {
+ teamName = NewId()
+ }
+ return teamName
+}
+
+// NewRandomString returns a random string of the given length.
+// The resulting entropy will be (5 * length) bits.
+func NewRandomString(length int) string {
+ data := make([]byte, 1+(length*5/8))
+ rand.Read(data)
+ return encoding.EncodeToString(data)[:length]
+}
+
+// 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)
+}
+
+// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.
+func GetTimeForMillis(millis int64) time.Time {
+ return time.Unix(0, millis*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, len(originalMap))
+ 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)
+}
+
+// MapBoolToJSON 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)
+ }
+ 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)
+ }
+ 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)
+ }
+ 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{})
+ }
+ return objmap
+}
+
+// ToJSON serializes an arbitrary data type to JSON, discarding the error.
+func ToJSON(v interface{}) []byte {
+ b, _ := json.Marshal(v)
+ return b
+}
+
+func GetServerIPAddress(iface string) string {
+ var addrs []net.Addr
+ if iface == "" {
+ var err error
+ addrs, err = net.InterfaceAddrs()
+ if err != nil {
+ return ""
+ }
+ } else {
+ interfaces, err := net.Interfaces()
+ if err != nil {
+ return ""
+ }
+ for _, i := range interfaces {
+ if i.Name == iface {
+ addrs, err = i.Addrs()
+ if err != nil {
+ return ""
+ }
+ break
+ }
+ }
+ }
+
+ 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 <billy@example.com>" which we don't allow
+ return false
+ }
+
+ return true
+}
+
+var reservedName = []string{
+ "admin",
+ "api",
+ "channel",
+ "claim",
+ "error",
+ "files",
+ "help",
+ "landing",
+ "login",
+ "mfa",
+ "oauth",
+ "plug",
+ "plugins",
+ "post",
+ "signup",
+ "boards",
+ "playbooks",
+}
+
+func IsValidChannelIdentifier(s string) bool {
+
+ if !IsValidAlphaNumHyphenUnderscore(s, true) {
+ return false
+ }
+
+ if len(s) < ChannelNameMinLength {
+ return false
+ }
+
+ return true
+}
+
+var (
+ validAlphaNum = regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
+ validAlphaNumHyphenUnderscore = regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
+ validSimpleAlphaNumHyphenUnderscore = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
+ validSimpleAlphaNumHyphenUnderscorePlus = regexp.MustCompile(`^[a-zA-Z0-9+_-]+$`)
+)
+
+func isValidAlphaNum(s string) bool {
+ return validAlphaNum.MatchString(s)
+}
+
+func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
+ if withFormat {
+ return validAlphaNumHyphenUnderscore.MatchString(s)
+ }
+ return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
+}
+
+func IsValidAlphaNumHyphenUnderscorePlus(s string) bool {
+ return validSimpleAlphaNumHyphenUnderscorePlus.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])$`)
+ puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
+ hashtagStart = regexp.MustCompile(`^#{2,}`)
+ 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 ClearMentionTags(post string) string {
+ post = strings.Replace(post, "<mention>", "", -1)
+ post = strings.Replace(post, "</mention>", "", -1)
+ return post
+}
+
+func IsValidHTTPURL(rawURL string) bool {
+ if strings.Index(rawURL, "http://") != 0 && strings.Index(rawURL, "https://") != 0 {
+ return false
+ }
+
+ if u, err := url.ParseRequestURI(rawURL); err != nil || u.Scheme == "" || u.Host == "" {
+ 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
+}
+
+// RemoveDuplicateStrings does an in-place removal of duplicate strings
+// from the input slice. The original slice gets modified.
+func RemoveDuplicateStrings(in []string) []string {
+ // In-place de-dup.
+ // Copied from https://github.com/golang/go/wiki/SliceTricks#in-place-deduplicate-comparable
+ if len(in) == 0 {
+ return in
+ }
+ sort.Strings(in)
+ j := 0
+ for i := 1; i < len(in); i++ {
+ if in[j] == in[i] {
+ continue
+ }
+ j++
+ in[j] = in[i]
+ }
+ return in[:j+1]
+}
+
+func GetPreferredTimezone(timezone StringMap) string {
+ if timezone["useAutomaticTimezone"] == "true" {
+ return timezone["automaticTimezone"]
+ }
+
+ return timezone["manualTimezone"]
+}
+
+// SanitizeUnicode will remove undesirable Unicode characters from a string.
+func SanitizeUnicode(s string) string {
+ return strings.Map(filterBlocklist, s)
+}
+
+// filterBlocklist returns `r` if it is not in the blocklist, otherwise drop (-1).
+// Blocklist is taken from https://www.w3.org/TR/unicode-xml/#Charlist
+func filterBlocklist(r rune) rune {
+ const drop = -1
+ switch r {
+ case '\u0340', '\u0341': // clones of grave and acute; deprecated in Unicode
+ return drop
+ case '\u17A3', '\u17D3': // obsolete characters for Khmer; deprecated in Unicode
+ return drop
+ case '\u2028', '\u2029': // line and paragraph separator
+ return drop
+ case '\u202A', '\u202B', '\u202C', '\u202D', '\u202E': // BIDI embedding controls
+ return drop
+ case '\u206A', '\u206B': // activate/inhibit symmetric swapping; deprecated in Unicode
+ return drop
+ case '\u206C', '\u206D': // activate/inhibit Arabic form shaping; deprecated in Unicode
+ return drop
+ case '\u206E', '\u206F': // activate/inhibit national digit shapes; deprecated in Unicode
+ return drop
+ case '\uFFF9', '\uFFFA', '\uFFFB': // interlinear annotation characters
+ return drop
+ case '\uFEFF': // byte order mark
+ return drop
+ case '\uFFFC': // object replacement character
+ return drop
+ }
+
+ // Scoping for musical notation
+ if r >= 0x0001D173 && r <= 0x0001D17A {
+ return drop
+ }
+
+ // Language tag code points
+ if r >= 0x000E0000 && r <= 0x000E007F {
+ return drop
+ }
+
+ return r
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/version.go b/vendor/github.com/mattermost/mattermost-server/v6/model/version.go
new file mode 100644
index 00000000..b75a4e9c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/version.go
@@ -0,0 +1,190 @@
+// 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{
+ "6.0.0",
+ "5.39.0",
+ "5.38.0",
+ "5.37.0",
+ "5.36.0",
+ "5.35.0",
+ "5.34.0",
+ "5.33.0",
+ "5.32.0",
+ "5.31.0",
+ "5.30.0",
+ "5.29.0",
+ "5.28.0",
+ "5.27.0",
+ "5.26.0",
+ "5.25.0",
+ "5.24.0",
+ "5.23.0",
+ "5.22.0",
+ "5.21.0",
+ "5.20.0",
+ "5.19.0",
+ "5.18.0",
+ "5.17.0",
+ "5.16.0",
+ "5.15.0",
+ "5.14.0",
+ "5.13.0",
+ "5.12.0",
+ "5.11.0",
+ "5.10.0",
+ "5.9.0",
+ "5.8.0",
+ "5.7.0",
+ "5.6.0",
+ "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
+ }
+ 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/v6/model/websocket_client.go b/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_client.go
new file mode 100644
index 00000000..2bd8a3b7
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_client.go
@@ -0,0 +1,324 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "sync/atomic"
+ "time"
+
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+
+ "github.com/gorilla/websocket"
+)
+
+const (
+ SocketMaxMessageSizeKb = 8 * 1024 // 8KB
+ PingTimeoutBufferSeconds = 5
+)
+
+type msgType int
+
+const (
+ msgTypeJSON msgType = iota + 1
+ msgTypePong
+)
+
+type writeMessage struct {
+ msgType msgType
+ data interface{}
+}
+
+const avgReadMsgSizeBytes = 1024
+
+// WebSocketClient stores the necessary information required to
+// communicate with a WebSocket endpoint.
+// A client must read from PingTimeoutChannel, EventChannel and ResponseChannel to prevent
+// deadlocks from occurring in the program.
+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 connection
+ Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
+ PingTimeoutChannel chan bool // The channel used to signal ping timeouts
+ EventChannel chan *WebSocketEvent // The channel used to receive various events pushed from the server. For example: typing, posted
+ ResponseChannel chan *WebSocketResponse // The channel used to receive responses for requests made to the server
+ ListenError *AppError // A field that is set if there was an abnormal closure of the WebSocket connection
+ writeChan chan writeMessage
+
+ pingTimeoutTimer *time.Timer
+ quitPingWatchdog chan struct{}
+
+ quitWriterChan chan struct{}
+ resetTimerChan chan struct{}
+ closed int32
+}
+
+// NewWebSocketClient constructs a new WebSocket client with convenience
+// methods for talking to the server.
+func NewWebSocketClient(url, authToken string) (*WebSocketClient, error) {
+ 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, error) {
+ conn, _, err := dialer.Dial(url+APIURLSuffix+"/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,
+ APIURL: url + APIURLSuffix,
+ ConnectURL: url + APIURLSuffix + "/websocket",
+ Conn: conn,
+ AuthToken: authToken,
+ Sequence: 1,
+ PingTimeoutChannel: make(chan bool, 1),
+ EventChannel: make(chan *WebSocketEvent, 100),
+ ResponseChannel: make(chan *WebSocketResponse, 100),
+ writeChan: make(chan writeMessage),
+ quitPingWatchdog: make(chan struct{}),
+ quitWriterChan: make(chan struct{}),
+ resetTimerChan: make(chan struct{}),
+ }
+
+ client.configurePingHandling()
+ go client.writer()
+
+ client.SendMessage(WebsocketAuthenticationChallenge, 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, error) {
+ 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, error) {
+ return NewWebSocketClientWithDialer(dialer, url, authToken)
+}
+
+// Connect creates a websocket connection with the given ConnectURL.
+// This is racy and error-prone should not be used. Use any of the New* functions to create a websocket.
+func (wsc *WebSocketClient) Connect() *AppError {
+ return wsc.ConnectWithDialer(websocket.DefaultDialer)
+}
+
+// ConnectWithDialer creates a websocket connection with the given ConnectURL using the dialer.
+// This is racy and error-prone and should not be used. Use any of the New* functions to create a websocket.
+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)
+ }
+ // Super racy and should not be done anyways.
+ // All of this needs to be redesigned for v6.
+ wsc.configurePingHandling()
+ // If it has been closed before, we just restart the writer.
+ if atomic.CompareAndSwapInt32(&wsc.closed, 1, 0) {
+ wsc.writeChan = make(chan writeMessage)
+ wsc.quitWriterChan = make(chan struct{})
+ go wsc.writer()
+ wsc.resetTimerChan = make(chan struct{})
+ wsc.quitPingWatchdog = make(chan struct{})
+ }
+
+ wsc.EventChannel = make(chan *WebSocketEvent, 100)
+ wsc.ResponseChannel = make(chan *WebSocketResponse, 100)
+
+ wsc.SendMessage(WebsocketAuthenticationChallenge, map[string]interface{}{"token": wsc.AuthToken})
+
+ return nil
+}
+
+// Close closes the websocket client. It is recommended that a closed client should not be
+// reused again. Rather a new client should be created anew.
+func (wsc *WebSocketClient) Close() {
+ // CAS to 1 and proceed. Return if already 1.
+ if !atomic.CompareAndSwapInt32(&wsc.closed, 0, 1) {
+ return
+ }
+ wsc.quitWriterChan <- struct{}{}
+ close(wsc.writeChan)
+ // We close the connection, which breaks the reader loop.
+ // Then we let the defer block in the reader do further cleanup.
+ wsc.Conn.Close()
+}
+
+// TODO: un-export the Conn so that Write methods go through the writer
+func (wsc *WebSocketClient) writer() {
+ for {
+ select {
+ case msg := <-wsc.writeChan:
+ switch msg.msgType {
+ case msgTypeJSON:
+ wsc.Conn.WriteJSON(msg.data)
+ case msgTypePong:
+ wsc.Conn.WriteMessage(websocket.PongMessage, []byte{})
+ }
+ case <-wsc.quitWriterChan:
+ return
+ }
+ }
+}
+
+// Listen starts the read loop of the websocket client.
+func (wsc *WebSocketClient) Listen() {
+ // This loop can exit in 2 conditions:
+ // 1. Either the connection breaks naturally.
+ // 2. Close was explicitly called, which closes the connection manually.
+ //
+ // Due to the way the API is written, there is a requirement that a client may NOT
+ // call Listen at all and can still call Close and Connect.
+ // Therefore, we let the cleanup of the reader stuff rely on closing the connection
+ // and then we do the cleanup in the defer block.
+ //
+ // First, we close some channels and then CAS to 1 and proceed to close the writer chan also.
+ // This is needed because then the defer clause does not double-close the writer when (2) happens.
+ // But if (1) happens, we set the closed bit, and close the rest of the stuff.
+ go func() {
+ defer func() {
+ close(wsc.EventChannel)
+ close(wsc.ResponseChannel)
+ close(wsc.quitPingWatchdog)
+ close(wsc.resetTimerChan)
+ // We CAS to 1 and proceed.
+ if !atomic.CompareAndSwapInt32(&wsc.closed, 0, 1) {
+ return
+ }
+ wsc.quitWriterChan <- struct{}{}
+ close(wsc.writeChan)
+ wsc.Conn.Close() // This can most likely be removed. Needs to be checked.
+ }()
+
+ var buf bytes.Buffer
+ buf.Grow(avgReadMsgSizeBytes)
+
+ for {
+ // Reset buffer.
+ buf.Reset()
+ _, r, err := wsc.Conn.NextReader()
+ if 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
+ }
+ // Use pre-allocated buffer.
+ _, err = buf.ReadFrom(r)
+ if err != nil {
+ // This should use a different error ID, but en.json is not imported anyways.
+ // It's a different bug altogether but we let it be for now.
+ // See MM-24520.
+ wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ event, jsonErr := WebSocketEventFromJSON(bytes.NewReader(buf.Bytes()))
+ if jsonErr != nil {
+ mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr))
+ continue
+ }
+ if event.IsValid() {
+ wsc.EventChannel <- event
+ continue
+ }
+
+ var response WebSocketResponse
+ if err := json.Unmarshal(buf.Bytes(), &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.writeChan <- writeMessage{
+ msgType: msgTypeJSON,
+ data: 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 + PingTimeoutBufferSeconds))
+ go wsc.pingWatchdog()
+}
+
+func (wsc *WebSocketClient) pingHandler(appData string) error {
+ if atomic.LoadInt32(&wsc.closed) == 1 {
+ return nil
+ }
+ wsc.resetTimerChan <- struct{}{}
+ wsc.writeChan <- writeMessage{
+ msgType: msgTypePong,
+ }
+ return nil
+}
+
+// pingWatchdog is used to send values to the PingTimeoutChannel whenever a timeout occurs.
+// We use the resetTimerChan from the pingHandler to pass the signal, and then reset the timer
+// after draining it. And if the timer naturally expires, we also extend it to prevent it from
+// being deadlocked when the resetTimerChan case runs. Because timer.Stop would return false,
+// and the code would be forever stuck trying to read from C.
+func (wsc *WebSocketClient) pingWatchdog() {
+ for {
+ select {
+ case <-wsc.resetTimerChan:
+ if !wsc.pingTimeoutTimer.Stop() {
+ <-wsc.pingTimeoutTimer.C
+ }
+ wsc.pingTimeoutTimer.Reset(time.Second * (60 + PingTimeoutBufferSeconds))
+
+ case <-wsc.pingTimeoutTimer.C:
+ wsc.PingTimeoutChannel <- true
+ wsc.pingTimeoutTimer.Reset(time.Second * (60 + PingTimeoutBufferSeconds))
+ case <-wsc.quitPingWatchdog:
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_message.go b/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_message.go
new file mode 100644
index 00000000..005eed33
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_message.go
@@ -0,0 +1,293 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+)
+
+const (
+ WebsocketEventTyping = "typing"
+ WebsocketEventPosted = "posted"
+ WebsocketEventPostEdited = "post_edited"
+ WebsocketEventPostDeleted = "post_deleted"
+ WebsocketEventPostUnread = "post_unread"
+ WebsocketEventChannelConverted = "channel_converted"
+ WebsocketEventChannelCreated = "channel_created"
+ WebsocketEventChannelDeleted = "channel_deleted"
+ WebsocketEventChannelRestored = "channel_restored"
+ WebsocketEventChannelUpdated = "channel_updated"
+ WebsocketEventChannelMemberUpdated = "channel_member_updated"
+ WebsocketEventChannelSchemeUpdated = "channel_scheme_updated"
+ WebsocketEventDirectAdded = "direct_added"
+ WebsocketEventGroupAdded = "group_added"
+ WebsocketEventNewUser = "new_user"
+ WebsocketEventAddedToTeam = "added_to_team"
+ WebsocketEventLeaveTeam = "leave_team"
+ WebsocketEventUpdateTeam = "update_team"
+ WebsocketEventDeleteTeam = "delete_team"
+ WebsocketEventRestoreTeam = "restore_team"
+ WebsocketEventUpdateTeamScheme = "update_team_scheme"
+ WebsocketEventUserAdded = "user_added"
+ WebsocketEventUserUpdated = "user_updated"
+ WebsocketEventUserRoleUpdated = "user_role_updated"
+ WebsocketEventMemberroleUpdated = "memberrole_updated"
+ WebsocketEventUserRemoved = "user_removed"
+ WebsocketEventPreferenceChanged = "preference_changed"
+ WebsocketEventPreferencesChanged = "preferences_changed"
+ WebsocketEventPreferencesDeleted = "preferences_deleted"
+ WebsocketEventEphemeralMessage = "ephemeral_message"
+ WebsocketEventStatusChange = "status_change"
+ WebsocketEventHello = "hello"
+ WebsocketAuthenticationChallenge = "authentication_challenge"
+ WebsocketEventReactionAdded = "reaction_added"
+ WebsocketEventReactionRemoved = "reaction_removed"
+ WebsocketEventResponse = "response"
+ WebsocketEventEmojiAdded = "emoji_added"
+ WebsocketEventChannelViewed = "channel_viewed"
+ WebsocketEventPluginStatusesChanged = "plugin_statuses_changed"
+ WebsocketEventPluginEnabled = "plugin_enabled"
+ WebsocketEventPluginDisabled = "plugin_disabled"
+ WebsocketEventRoleUpdated = "role_updated"
+ WebsocketEventLicenseChanged = "license_changed"
+ WebsocketEventConfigChanged = "config_changed"
+ WebsocketEventOpenDialog = "open_dialog"
+ WebsocketEventGuestsDeactivated = "guests_deactivated"
+ WebsocketEventUserActivationStatusChange = "user_activation_status_change"
+ WebsocketEventReceivedGroup = "received_group"
+ WebsocketEventReceivedGroupAssociatedToTeam = "received_group_associated_to_team"
+ WebsocketEventReceivedGroupNotAssociatedToTeam = "received_group_not_associated_to_team"
+ WebsocketEventReceivedGroupAssociatedToChannel = "received_group_associated_to_channel"
+ WebsocketEventReceivedGroupNotAssociatedToChannel = "received_group_not_associated_to_channel"
+ WebsocketEventSidebarCategoryCreated = "sidebar_category_created"
+ WebsocketEventSidebarCategoryUpdated = "sidebar_category_updated"
+ WebsocketEventSidebarCategoryDeleted = "sidebar_category_deleted"
+ WebsocketEventSidebarCategoryOrderUpdated = "sidebar_category_order_updated"
+ WebsocketWarnMetricStatusReceived = "warn_metric_status_received"
+ WebsocketWarnMetricStatusRemoved = "warn_metric_status_removed"
+ WebsocketEventCloudPaymentStatusUpdated = "cloud_payment_status_updated"
+ WebsocketEventThreadUpdated = "thread_updated"
+ WebsocketEventThreadFollowChanged = "thread_follow_changed"
+ WebsocketEventThreadReadChanged = "thread_read_changed"
+ WebsocketFirstAdminVisitMarketplaceStatusReceived = "first_admin_visit_marketplace_status_received"
+)
+
+type WebSocketMessage interface {
+ ToJSON() ([]byte, error)
+ 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
+}
+
+// webSocketEventJSON mirrors WebSocketEvent to make some of its unexported fields serializable
+type webSocketEventJSON struct {
+ Event string `json:"event"`
+ Data map[string]interface{} `json:"data"`
+ Broadcast *WebsocketBroadcast `json:"broadcast"`
+ Sequence int64 `json:"seq"`
+}
+
+type WebSocketEvent struct {
+ event string
+ data map[string]interface{}
+ broadcast *WebsocketBroadcast
+ sequence int64
+ 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 (ev *WebSocketEvent) PrecomputeJSON() *WebSocketEvent {
+ copy := ev.Copy()
+ event, _ := json.Marshal(copy.event)
+ data, _ := json.Marshal(copy.data)
+ broadcast, _ := json.Marshal(copy.broadcast)
+ copy.precomputedJSON = &precomputedWebSocketEventJSON{
+ Event: json.RawMessage(event),
+ Data: json.RawMessage(data),
+ Broadcast: json.RawMessage(broadcast),
+ }
+ return copy
+}
+
+func (ev *WebSocketEvent) Add(key string, value interface{}) {
+ ev.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 (ev *WebSocketEvent) Copy() *WebSocketEvent {
+ copy := &WebSocketEvent{
+ event: ev.event,
+ data: ev.data,
+ broadcast: ev.broadcast,
+ sequence: ev.sequence,
+ precomputedJSON: ev.precomputedJSON,
+ }
+ return copy
+}
+
+func (ev *WebSocketEvent) GetData() map[string]interface{} {
+ return ev.data
+}
+
+func (ev *WebSocketEvent) GetBroadcast() *WebsocketBroadcast {
+ return ev.broadcast
+}
+
+func (ev *WebSocketEvent) GetSequence() int64 {
+ return ev.sequence
+}
+
+func (ev *WebSocketEvent) SetEvent(event string) *WebSocketEvent {
+ copy := ev.Copy()
+ copy.event = event
+ return copy
+}
+
+func (ev *WebSocketEvent) SetData(data map[string]interface{}) *WebSocketEvent {
+ copy := ev.Copy()
+ copy.data = data
+ return copy
+}
+
+func (ev *WebSocketEvent) SetBroadcast(broadcast *WebsocketBroadcast) *WebSocketEvent {
+ copy := ev.Copy()
+ copy.broadcast = broadcast
+ return copy
+}
+
+func (ev *WebSocketEvent) SetSequence(seq int64) *WebSocketEvent {
+ copy := ev.Copy()
+ copy.sequence = seq
+ return copy
+}
+
+func (ev *WebSocketEvent) IsValid() bool {
+ return ev.event != ""
+}
+
+func (ev *WebSocketEvent) EventType() string {
+ return ev.event
+}
+
+func (ev *WebSocketEvent) ToJSON() ([]byte, error) {
+ if ev.precomputedJSON != nil {
+ return []byte(fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.GetSequence())), nil
+ }
+ return json.Marshal(webSocketEventJSON{
+ ev.event,
+ ev.data,
+ ev.broadcast,
+ ev.sequence,
+ })
+}
+
+// Encode encodes the event to the given encoder.
+func (ev *WebSocketEvent) Encode(enc *json.Encoder) error {
+ if ev.precomputedJSON != nil {
+ return enc.Encode(json.RawMessage(
+ fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.sequence),
+ ))
+ }
+
+ return enc.Encode(webSocketEventJSON{
+ ev.event,
+ ev.data,
+ ev.broadcast,
+ ev.sequence,
+ })
+}
+
+func WebSocketEventFromJSON(data io.Reader) (*WebSocketEvent, error) {
+ var ev WebSocketEvent
+ var o webSocketEventJSON
+ if err := json.NewDecoder(data).Decode(&o); err != nil {
+ return nil, err
+ }
+ ev.event = o.Event
+ if u, ok := o.Data["user"]; ok {
+ // We need to convert to and from JSON again
+ // because the user is in the form of a map[string]interface{}.
+ buf, err := json.Marshal(u)
+ if err != nil {
+ return nil, err
+ }
+
+ var user User
+ if err = json.Unmarshal(buf, &user); err != nil {
+ return nil, err
+ }
+ o.Data["user"] = &user
+ }
+ ev.data = o.Data
+ ev.broadcast = o.Broadcast
+ ev.sequence = o.Sequence
+ return &ev, nil
+}
+
+// WebSocketResponse represents a response received through the WebSocket
+// for a request made to the server. This is available through the ResponseChannel
+// channel in WebSocketClient.
+type WebSocketResponse struct {
+ Status string `json:"status"` // The status of the response. For example: OK, FAIL.
+ SeqReply int64 `json:"seq_reply,omitempty"` // A counter which is incremented for every response sent.
+ Data map[string]interface{} `json:"data,omitempty"` // The data contained in the response.
+ Error *AppError `json:"error,omitempty"` // A field that is set if any error has occurred.
+}
+
+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: StatusFail, SeqReply: seqReply, Error: err}
+}
+
+func (m *WebSocketResponse) IsValid() bool {
+ return m.Status != ""
+}
+
+func (m *WebSocketResponse) EventType() string {
+ return WebsocketEventResponse
+}
+
+func (m *WebSocketResponse) ToJSON() ([]byte, error) {
+ return json.Marshal(m)
+}
+
+func WebSocketResponseFromJSON(data io.Reader) (*WebSocketResponse, error) {
+ var o *WebSocketResponse
+ return o, json.NewDecoder(data).Decode(&o)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_request.go b/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_request.go
new file mode 100644
index 00000000..9e863978
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/model/websocket_request.go
@@ -0,0 +1,36 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+
+ "github.com/mattermost/mattermost-server/v6/shared/i18n"
+)
+
+// WebSocketRequest represents a request made to the server through a websocket.
+type WebSocketRequest struct {
+ // Client-provided fields
+ Seq int64 `json:"seq"` // A counter which is incremented for every request made.
+ Action string `json:"action"` // The action to perform for a request. For example: get_statuses, user_typing.
+ Data map[string]interface{} `json:"data"` // The metadata for an action.
+
+ // Server-provided fields
+ Session Session `json:"-"`
+ T i18n.TranslateFunc `json:"-"`
+ Locale string `json:"-"`
+}
+
+func (o *WebSocketRequest) Clone() (*WebSocketRequest, error) {
+ buf, err := json.Marshal(o)
+ if err != nil {
+ return nil, err
+ }
+ var ret WebSocketRequest
+ err = json.Unmarshal(buf, &ret)
+ if err != nil {
+ return nil, err
+ }
+ return &ret, nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/services/timezones/default.go b/vendor/github.com/mattermost/mattermost-server/v6/services/timezones/default.go
new file mode 100644
index 00000000..3d835f7d
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/services/timezones/default.go
@@ -0,0 +1,599 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package timezones
+
+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/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",
+ "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/v6/services/timezones/timezones.go b/vendor/github.com/mattermost/mattermost-server/v6/services/timezones/timezones.go
new file mode 100644
index 00000000..e211a4cd
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/services/timezones/timezones.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package timezones
+
+type Timezones struct {
+ supportedZones []string
+}
+
+func New() *Timezones {
+ timezones := Timezones{}
+
+ timezones.supportedZones = DefaultSupportedTimezones
+
+ return &timezones
+}
+
+func (t *Timezones) GetSupported() []string {
+ return t.supportedZones
+}
+
+func DefaultUserTimezone() map[string]string {
+ defaultTimezone := make(map[string]string)
+ defaultTimezone["useAutomaticTimezone"] = "true"
+ defaultTimezone["automaticTimezone"] = ""
+ defaultTimezone["manualTimezone"] = ""
+
+ return defaultTimezone
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/filesstore.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/filesstore.go
new file mode 100644
index 00000000..ef02895d
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/filesstore.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package filestore
+
+import (
+ "io"
+ "time"
+
+ "github.com/pkg/errors"
+)
+
+const (
+ driverS3 = "amazons3"
+ driverLocal = "local"
+)
+
+type ReadCloseSeeker interface {
+ io.ReadCloser
+ io.Seeker
+}
+
+type FileBackend interface {
+ TestConnection() error
+
+ Reader(path string) (ReadCloseSeeker, error)
+ ReadFile(path string) ([]byte, error)
+ FileExists(path string) (bool, error)
+ FileSize(path string) (int64, error)
+ CopyFile(oldPath, newPath string) error
+ MoveFile(oldPath, newPath string) error
+ WriteFile(fr io.Reader, path string) (int64, error)
+ AppendFile(fr io.Reader, path string) (int64, error)
+ RemoveFile(path string) error
+ FileModTime(path string) (time.Time, error)
+
+ ListDirectory(path string) ([]string, error)
+ RemoveDirectory(path string) error
+}
+
+type FileBackendSettings struct {
+ DriverName string
+ Directory string
+ AmazonS3AccessKeyId string
+ AmazonS3SecretAccessKey string
+ AmazonS3Bucket string
+ AmazonS3PathPrefix string
+ AmazonS3Region string
+ AmazonS3Endpoint string
+ AmazonS3SSL bool
+ AmazonS3SignV2 bool
+ AmazonS3SSE bool
+ AmazonS3Trace bool
+}
+
+func (settings *FileBackendSettings) CheckMandatoryS3Fields() error {
+ if settings.AmazonS3Bucket == "" {
+ return errors.New("missing s3 bucket settings")
+ }
+
+ // if S3 endpoint is not set call the set defaults to set that
+ if settings.AmazonS3Endpoint == "" {
+ settings.AmazonS3Endpoint = "s3.amazonaws.com"
+ }
+
+ return nil
+}
+
+func NewFileBackend(settings FileBackendSettings) (FileBackend, error) {
+ switch settings.DriverName {
+ case driverS3:
+ backend, err := NewS3FileBackend(settings)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to connect to the s3 backend")
+ }
+ return backend, nil
+ case driverLocal:
+ return &LocalFileBackend{
+ directory: settings.Directory,
+ }, nil
+ }
+ return nil, errors.New("no valid filestorage driver found")
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/localstore.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/localstore.go
new file mode 100644
index 00000000..5ed882de
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/localstore.go
@@ -0,0 +1,211 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package filestore
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/pkg/errors"
+
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+const (
+ TestFilePath = "/testfile"
+)
+
+type LocalFileBackend struct {
+ directory string
+}
+
+// copyFile will copy a file from src path to dst path.
+// Overwrites any existing files at dst.
+// Permissions are copied from file at src to the new file at dst.
+func copyFile(src, dst string) (err error) {
+ in, err := os.Open(src)
+ if err != nil {
+ return
+ }
+ defer in.Close()
+
+ if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
+ return
+ }
+ out, err := os.Create(dst)
+ if err != nil {
+ return
+ }
+ defer func() {
+ if e := out.Close(); e != nil {
+ err = e
+ }
+ }()
+
+ _, err = io.Copy(out, in)
+ if err != nil {
+ return
+ }
+
+ err = out.Sync()
+ if err != nil {
+ return
+ }
+
+ stat, err := os.Stat(src)
+ if err != nil {
+ return
+ }
+ err = os.Chmod(dst, stat.Mode())
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (b *LocalFileBackend) TestConnection() error {
+ f := bytes.NewReader([]byte("testingwrite"))
+ if _, err := writeFileLocally(f, filepath.Join(b.directory, TestFilePath)); err != nil {
+ return errors.Wrap(err, "unable to write to the local filesystem storage")
+ }
+ os.Remove(filepath.Join(b.directory, TestFilePath))
+ mlog.Debug("Able to write files to local storage.")
+ return nil
+}
+
+func (b *LocalFileBackend) Reader(path string) (ReadCloseSeeker, error) {
+ f, err := os.Open(filepath.Join(b.directory, path))
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to open file %s", path)
+ }
+ return f, nil
+}
+
+func (b *LocalFileBackend) ReadFile(path string) ([]byte, error) {
+ f, err := ioutil.ReadFile(filepath.Join(b.directory, path))
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to read file %s", path)
+ }
+ return f, nil
+}
+
+func (b *LocalFileBackend) FileExists(path string) (bool, error) {
+ _, err := os.Stat(filepath.Join(b.directory, path))
+
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+
+ if err != nil {
+ return false, errors.Wrapf(err, "unable to know if file %s exists", path)
+ }
+ return true, nil
+}
+
+func (b *LocalFileBackend) FileSize(path string) (int64, error) {
+ info, err := os.Stat(filepath.Join(b.directory, path))
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to get file size for %s", path)
+ }
+ return info.Size(), nil
+}
+
+func (b *LocalFileBackend) FileModTime(path string) (time.Time, error) {
+ info, err := os.Stat(filepath.Join(b.directory, path))
+ if err != nil {
+ return time.Time{}, errors.Wrapf(err, "unable to get modification time for file %s", path)
+ }
+ return info.ModTime(), nil
+}
+
+func (b *LocalFileBackend) CopyFile(oldPath, newPath string) error {
+ if err := copyFile(filepath.Join(b.directory, oldPath), filepath.Join(b.directory, newPath)); err != nil {
+ return errors.Wrapf(err, "unable to copy file from %s to %s", oldPath, newPath)
+ }
+ return nil
+}
+
+func (b *LocalFileBackend) MoveFile(oldPath, newPath string) error {
+ if err := os.MkdirAll(filepath.Dir(filepath.Join(b.directory, newPath)), 0750); err != nil {
+ return errors.Wrapf(err, "unable to create the new destination directory %s", filepath.Dir(newPath))
+ }
+
+ if err := os.Rename(filepath.Join(b.directory, oldPath), filepath.Join(b.directory, newPath)); err != nil {
+ return errors.Wrapf(err, "unable to move the file to %s to the destination directory", newPath)
+ }
+
+ return nil
+}
+
+func (b *LocalFileBackend) WriteFile(fr io.Reader, path string) (int64, error) {
+ return writeFileLocally(fr, filepath.Join(b.directory, path))
+}
+
+func writeFileLocally(fr io.Reader, path string) (int64, error) {
+ if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
+ directory, _ := filepath.Abs(filepath.Dir(path))
+ return 0, errors.Wrapf(err, "unable to create the directory %s for the file %s", directory, path)
+ }
+ fw, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to open the file %s to write the data", path)
+ }
+ defer fw.Close()
+ written, err := io.Copy(fw, fr)
+ if err != nil {
+ return written, errors.Wrapf(err, "unable write the data in the file %s", path)
+ }
+ return written, nil
+}
+
+func (b *LocalFileBackend) AppendFile(fr io.Reader, path string) (int64, error) {
+ fp := filepath.Join(b.directory, path)
+ if _, err := os.Stat(fp); err != nil {
+ return 0, errors.Wrapf(err, "unable to find the file %s to append the data", path)
+ }
+ fw, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND, 0600)
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to open the file %s to append the data", path)
+ }
+ defer fw.Close()
+ written, err := io.Copy(fw, fr)
+ if err != nil {
+ return written, errors.Wrapf(err, "unable append the data in the file %s", path)
+ }
+ return written, nil
+}
+
+func (b *LocalFileBackend) RemoveFile(path string) error {
+ if err := os.Remove(filepath.Join(b.directory, path)); err != nil {
+ return errors.Wrapf(err, "unable to remove the file %s", path)
+ }
+ return nil
+}
+
+func (b *LocalFileBackend) ListDirectory(path string) ([]string, error) {
+ var paths []string
+ fileInfos, err := ioutil.ReadDir(filepath.Join(b.directory, path))
+ if err != nil {
+ if os.IsNotExist(err) {
+ return paths, nil
+ }
+ return nil, errors.Wrapf(err, "unable to list the directory %s", path)
+ }
+ for _, fileInfo := range fileInfos {
+ paths = append(paths, filepath.Join(path, fileInfo.Name()))
+ }
+ return paths, nil
+}
+
+func (b *LocalFileBackend) RemoveDirectory(path string) error {
+ if err := os.RemoveAll(filepath.Join(b.directory, path)); err != nil {
+ return errors.Wrapf(err, "unable to remove the directory %s", path)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3_overrides.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3_overrides.go
new file mode 100644
index 00000000..e7b29b98
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3_overrides.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package filestore
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+// customTransport is used to point the request to a different server.
+// This is helpful in situations where a different service is handling AWS S3 requests
+// from multiple Mattermost applications, and the Mattermost service itself does not
+// have any S3 credentials.
+type customTransport struct {
+ base http.RoundTripper
+ host string
+ scheme string
+ client http.Client
+}
+
+// RoundTrip implements the http.Roundtripper interface.
+func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ // Rountrippers should not modify the original request.
+ newReq := req.Clone(context.Background())
+ *newReq.URL = *req.URL
+ req.URL.Scheme = t.scheme
+ req.URL.Host = t.host
+ return t.client.Do(req)
+}
+
+// customProvider is a dummy credentials provider for the minio client to work
+// without actually providing credentials. This is needed with a custom transport
+// in cases where the minio client does not actually have credentials with itself,
+// rather needs responses from another entity.
+//
+// It satisfies the credentials.Provider interface.
+type customProvider struct {
+ isSignV2 bool
+}
+
+// Retrieve just returns empty credentials.
+func (cp customProvider) Retrieve() (credentials.Value, error) {
+ sign := credentials.SignatureV4
+ if cp.isSignV2 {
+ sign = credentials.SignatureV2
+ }
+ return credentials.Value{
+ SignerType: sign,
+ }, nil
+}
+
+// IsExpired always returns false.
+func (cp customProvider) IsExpired() bool { return false }
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3store.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3store.go
new file mode 100644
index 00000000..f938f4d5
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/filestore/s3store.go
@@ -0,0 +1,442 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package filestore
+
+import (
+ "context"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ s3 "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+ "github.com/pkg/errors"
+
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+// S3FileBackend contains all necessary information to communicate with
+// an AWS S3 compatible API backend.
+type S3FileBackend struct {
+ endpoint string
+ accessKey string
+ secretKey string
+ secure bool
+ signV2 bool
+ region string
+ bucket string
+ pathPrefix string
+ encrypt bool
+ trace bool
+ client *s3.Client
+}
+
+type S3FileBackendAuthError struct {
+ DetailedError string
+}
+
+// S3FileBackendNoBucketError is returned when testing a connection and no S3 bucket is found
+type S3FileBackendNoBucketError struct{}
+
+const (
+ // This is not exported by minio. See: https://github.com/minio/minio-go/issues/1339
+ bucketNotFound = "NoSuchBucket"
+)
+
+var (
+ imageExtensions = map[string]bool{".jpg": true, ".jpeg": true, ".gif": true, ".bmp": true, ".png": true, ".tiff": true, "tif": true}
+ imageMimeTypes = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff", ".tif": "image/tif"}
+)
+
+func isFileExtImage(ext string) bool {
+ ext = strings.ToLower(ext)
+ return imageExtensions[ext]
+}
+
+func getImageMimeType(ext string) string {
+ ext = strings.ToLower(ext)
+ if imageMimeTypes[ext] == "" {
+ return "image"
+ }
+ return imageMimeTypes[ext]
+}
+
+func (s *S3FileBackendAuthError) Error() string {
+ return s.DetailedError
+}
+
+func (s *S3FileBackendNoBucketError) Error() string {
+ return "no such bucket"
+}
+
+// NewS3FileBackend returns an instance of an S3FileBackend.
+func NewS3FileBackend(settings FileBackendSettings) (*S3FileBackend, error) {
+ backend := &S3FileBackend{
+ endpoint: settings.AmazonS3Endpoint,
+ accessKey: settings.AmazonS3AccessKeyId,
+ secretKey: settings.AmazonS3SecretAccessKey,
+ secure: settings.AmazonS3SSL,
+ signV2: settings.AmazonS3SignV2,
+ region: settings.AmazonS3Region,
+ bucket: settings.AmazonS3Bucket,
+ pathPrefix: settings.AmazonS3PathPrefix,
+ encrypt: settings.AmazonS3SSE,
+ trace: settings.AmazonS3Trace,
+ }
+ cli, err := backend.s3New()
+ if err != nil {
+ return nil, err
+ }
+ backend.client = cli
+ return backend, nil
+}
+
+// Similar to s3.New() but allows initialization of signature v2 or signature v4 client.
+// If signV2 input is false, function always returns signature v4.
+//
+// Additionally this function also takes a user defined region, if set
+// disables automatic region lookup.
+func (b *S3FileBackend) s3New() (*s3.Client, error) {
+ var creds *credentials.Credentials
+
+ isCloud := os.Getenv("MM_CLOUD_FILESTORE_BIFROST") != ""
+ if isCloud {
+ creds = credentials.New(customProvider{isSignV2: b.signV2})
+ } else if b.accessKey == "" && b.secretKey == "" {
+ creds = credentials.NewIAM("")
+ } else if b.signV2 {
+ creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV2)
+ } else {
+ creds = credentials.NewStatic(b.accessKey, b.secretKey, "", credentials.SignatureV4)
+ }
+
+ opts := s3.Options{
+ Creds: creds,
+ Secure: b.secure,
+ Region: b.region,
+ }
+
+ // If this is a cloud installation, we override the default transport.
+ if isCloud {
+ tr, err := s3.DefaultTransport(b.secure)
+ if err != nil {
+ return nil, err
+ }
+ scheme := "http"
+ if b.secure {
+ scheme = "https"
+ }
+ opts.Transport = &customTransport{
+ base: tr,
+ host: b.endpoint,
+ scheme: scheme,
+ }
+ }
+
+ s3Clnt, err := s3.New(b.endpoint, &opts)
+ if err != nil {
+ return nil, err
+ }
+
+ if b.trace {
+ s3Clnt.TraceOn(os.Stdout)
+ }
+
+ return s3Clnt, nil
+}
+
+func (b *S3FileBackend) TestConnection() error {
+ exists := true
+ var err error
+ // If a path prefix is present, we attempt to test the bucket by listing objects under the path
+ // and just checking the first response. This is because the BucketExists call is only at a bucket level
+ // and sometimes the user might only be allowed access to the specified path prefix.
+ if b.pathPrefix != "" {
+ obj := <-b.client.ListObjects(context.Background(), b.bucket, s3.ListObjectsOptions{Prefix: b.pathPrefix})
+ if obj.Err != nil {
+ typedErr := s3.ToErrorResponse(obj.Err)
+ if typedErr.Code != bucketNotFound {
+ return &S3FileBackendAuthError{DetailedError: "unable to list objects in the S3 bucket"}
+ }
+ exists = false
+ }
+ } else {
+ exists, err = b.client.BucketExists(context.Background(), b.bucket)
+ if err != nil {
+ return &S3FileBackendAuthError{DetailedError: "unable to check if the S3 bucket exists"}
+ }
+ }
+
+ if !exists {
+ return &S3FileBackendNoBucketError{}
+ }
+ mlog.Debug("Connection to S3 or minio is good. Bucket exists.")
+ return nil
+}
+
+func (b *S3FileBackend) MakeBucket() error {
+ err := b.client.MakeBucket(context.Background(), b.bucket, s3.MakeBucketOptions{Region: b.region})
+ if err != nil {
+ return errors.Wrap(err, "unable to create the s3 bucket")
+ }
+ return nil
+}
+
+// Caller must close the first return value
+func (b *S3FileBackend) Reader(path string) (ReadCloseSeeker, error) {
+ path = filepath.Join(b.pathPrefix, path)
+ minioObject, err := b.client.GetObject(context.Background(), b.bucket, path, s3.GetObjectOptions{})
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to open file %s", path)
+ }
+
+ return minioObject, nil
+}
+
+func (b *S3FileBackend) ReadFile(path string) ([]byte, error) {
+ path = filepath.Join(b.pathPrefix, path)
+ minioObject, err := b.client.GetObject(context.Background(), b.bucket, path, s3.GetObjectOptions{})
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to open file %s", path)
+ }
+
+ defer minioObject.Close()
+ f, err := ioutil.ReadAll(minioObject)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to read file %s", path)
+ }
+ return f, nil
+}
+
+func (b *S3FileBackend) FileExists(path string) (bool, error) {
+ path = filepath.Join(b.pathPrefix, path)
+
+ _, err := b.client.StatObject(context.Background(), b.bucket, path, s3.StatObjectOptions{})
+ if err == nil {
+ return true, nil
+ }
+
+ var s3Err s3.ErrorResponse
+ if errors.As(err, &s3Err); s3Err.Code == "NoSuchKey" {
+ return false, nil
+ }
+
+ return false, errors.Wrapf(err, "unable to know if file %s exists", path)
+}
+
+func (b *S3FileBackend) FileSize(path string) (int64, error) {
+ path = filepath.Join(b.pathPrefix, path)
+
+ info, err := b.client.StatObject(context.Background(), b.bucket, path, s3.StatObjectOptions{})
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to get file size for %s", path)
+ }
+
+ return info.Size, nil
+}
+
+func (b *S3FileBackend) FileModTime(path string) (time.Time, error) {
+ path = filepath.Join(b.pathPrefix, path)
+
+ info, err := b.client.StatObject(context.Background(), b.bucket, path, s3.StatObjectOptions{})
+ if err != nil {
+ return time.Time{}, errors.Wrapf(err, "unable to get modification time for file %s", path)
+ }
+
+ return info.LastModified, nil
+}
+
+func (b *S3FileBackend) CopyFile(oldPath, newPath string) error {
+ oldPath = filepath.Join(b.pathPrefix, oldPath)
+ newPath = filepath.Join(b.pathPrefix, newPath)
+ srcOpts := s3.CopySrcOptions{
+ Bucket: b.bucket,
+ Object: oldPath,
+ Encryption: encrypt.NewSSE(),
+ }
+ dstOpts := s3.CopyDestOptions{
+ Bucket: b.bucket,
+ Object: newPath,
+ Encryption: encrypt.NewSSE(),
+ }
+ if _, err := b.client.CopyObject(context.Background(), dstOpts, srcOpts); err != nil {
+ return errors.Wrapf(err, "unable to copy file from %s to %s", oldPath, newPath)
+ }
+ return nil
+}
+
+func (b *S3FileBackend) MoveFile(oldPath, newPath string) error {
+ oldPath = filepath.Join(b.pathPrefix, oldPath)
+ newPath = filepath.Join(b.pathPrefix, newPath)
+ srcOpts := s3.CopySrcOptions{
+ Bucket: b.bucket,
+ Object: oldPath,
+ Encryption: encrypt.NewSSE(),
+ }
+ dstOpts := s3.CopyDestOptions{
+ Bucket: b.bucket,
+ Object: newPath,
+ Encryption: encrypt.NewSSE(),
+ }
+
+ if _, err := b.client.CopyObject(context.Background(), dstOpts, srcOpts); err != nil {
+ return errors.Wrapf(err, "unable to copy the file to %s to the new destionation", newPath)
+ }
+
+ if err := b.client.RemoveObject(context.Background(), b.bucket, oldPath, s3.RemoveObjectOptions{}); err != nil {
+ return errors.Wrapf(err, "unable to remove the file old file %s", oldPath)
+ }
+
+ return nil
+}
+
+func (b *S3FileBackend) WriteFile(fr io.Reader, path string) (int64, error) {
+ var contentType string
+ path = filepath.Join(b.pathPrefix, path)
+ if ext := filepath.Ext(path); isFileExtImage(ext) {
+ contentType = getImageMimeType(ext)
+ } else {
+ contentType = "binary/octet-stream"
+ }
+
+ options := s3PutOptions(b.encrypt, contentType)
+ info, err := b.client.PutObject(context.Background(), b.bucket, path, fr, -1, options)
+ if err != nil {
+ return info.Size, errors.Wrapf(err, "unable write the data in the file %s", path)
+ }
+
+ return info.Size, nil
+}
+
+func (b *S3FileBackend) AppendFile(fr io.Reader, path string) (int64, error) {
+ fp := filepath.Join(b.pathPrefix, path)
+ if _, err := b.client.StatObject(context.Background(), b.bucket, fp, s3.StatObjectOptions{}); err != nil {
+ return 0, errors.Wrapf(err, "unable to find the file %s to append the data", path)
+ }
+
+ var contentType string
+ if ext := filepath.Ext(fp); isFileExtImage(ext) {
+ contentType = getImageMimeType(ext)
+ } else {
+ contentType = "binary/octet-stream"
+ }
+
+ options := s3PutOptions(b.encrypt, contentType)
+ sse := options.ServerSideEncryption
+ partName := fp + ".part"
+ info, err := b.client.PutObject(context.Background(), b.bucket, partName, fr, -1, options)
+ defer b.client.RemoveObject(context.Background(), b.bucket, partName, s3.RemoveObjectOptions{})
+ if info.Size > 0 {
+ src1Opts := s3.CopySrcOptions{
+ Bucket: b.bucket,
+ Object: fp,
+ }
+ src2Opts := s3.CopySrcOptions{
+ Bucket: b.bucket,
+ Object: partName,
+ }
+ dstOpts := s3.CopyDestOptions{
+ Bucket: b.bucket,
+ Object: fp,
+ Encryption: sse,
+ }
+ _, err = b.client.ComposeObject(context.Background(), dstOpts, src1Opts, src2Opts)
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable append the data in the file %s", path)
+ }
+ return info.Size, nil
+ }
+
+ return 0, errors.Wrapf(err, "unable append the data in the file %s", path)
+}
+
+func (b *S3FileBackend) RemoveFile(path string) error {
+ path = filepath.Join(b.pathPrefix, path)
+ if err := b.client.RemoveObject(context.Background(), b.bucket, path, s3.RemoveObjectOptions{}); err != nil {
+ return errors.Wrapf(err, "unable to remove the file %s", path)
+ }
+
+ return nil
+}
+
+func getPathsFromObjectInfos(in <-chan s3.ObjectInfo) <-chan s3.ObjectInfo {
+ out := make(chan s3.ObjectInfo, 1)
+
+ go func() {
+ defer close(out)
+
+ for {
+ info, done := <-in
+
+ if !done {
+ break
+ }
+
+ out <- info
+ }
+ }()
+
+ return out
+}
+
+func (b *S3FileBackend) ListDirectory(path string) ([]string, error) {
+ path = filepath.Join(b.pathPrefix, path)
+ if !strings.HasSuffix(path, "/") && path != "" {
+ // s3Clnt returns only the path itself when "/" is not present
+ // appending "/" to make it consistent across all filestores
+ path = path + "/"
+ }
+
+ opts := s3.ListObjectsOptions{
+ Prefix: path,
+ }
+ var paths []string
+ for object := range b.client.ListObjects(context.Background(), b.bucket, opts) {
+ if object.Err != nil {
+ return nil, errors.Wrapf(object.Err, "unable to list the directory %s", path)
+ }
+ // We strip the path prefix that gets applied,
+ // so that it remains transparent to the application.
+ object.Key = strings.TrimPrefix(object.Key, b.pathPrefix)
+ trimmed := strings.Trim(object.Key, "/")
+ if trimmed != "" {
+ paths = append(paths, trimmed)
+ }
+ }
+
+ return paths, nil
+}
+
+func (b *S3FileBackend) RemoveDirectory(path string) error {
+ opts := s3.ListObjectsOptions{
+ Prefix: filepath.Join(b.pathPrefix, path),
+ Recursive: true,
+ }
+ list := b.client.ListObjects(context.Background(), b.bucket, opts)
+ objectsCh := b.client.RemoveObjects(context.Background(), b.bucket, getPathsFromObjectInfos(list), s3.RemoveObjectsOptions{})
+ for err := range objectsCh {
+ if err.Err != nil {
+ return errors.Wrapf(err.Err, "unable to remove the directory %s", path)
+ }
+ }
+
+ return nil
+}
+
+func s3PutOptions(encrypted bool, contentType string) s3.PutObjectOptions {
+ options := s3.PutObjectOptions{}
+ if encrypted {
+ options.ServerSideEncryption = encrypt.NewSSE()
+ }
+ options.ContentType = contentType
+ // We set the part size to the minimum allowed value of 5MBs
+ // to avoid an excessive allocation in minio.PutObject implementation.
+ options.PartSize = 1024 * 1024 * 5
+
+ return options
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/i18n/i18n.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/i18n/i18n.go
new file mode 100644
index 00000000..0168b751
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/i18n/i18n.go
@@ -0,0 +1,185 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package i18n
+
+import (
+ "fmt"
+ "html/template"
+ "io/ioutil"
+ "net/http"
+ "path/filepath"
+ "reflect"
+ "strings"
+
+ "github.com/mattermost/go-i18n/i18n"
+
+ "github.com/mattermost/mattermost-server/v6/shared/mlog"
+)
+
+const defaultLocale = "en"
+
+// TranslateFunc is the type of the translate functions
+type TranslateFunc func(translationID string, args ...interface{}) string
+
+// T is the translate function using the default server language as fallback language
+var T TranslateFunc
+
+// TDefault is the translate function using english as fallback language
+var TDefault TranslateFunc
+
+var locales map[string]string = make(map[string]string)
+var defaultServerLocale string
+var defaultClientLocale string
+
+// TranslationsPreInit loads translations from filesystem if they are not
+// loaded already and assigns english while loading server config
+func TranslationsPreInit(translationsDir string) error {
+ if T != nil {
+ return nil
+ }
+
+ // Set T even if we fail to load the translations. Lots of shutdown handling code will
+ // segfault trying to handle the error, and the untranslated IDs are strictly better.
+ T = tfuncWithFallback(defaultLocale)
+ TDefault = tfuncWithFallback(defaultLocale)
+
+ return initTranslationsWithDir(translationsDir)
+}
+
+// InitTranslations set the defaults configured in the server and initialize
+// the T function using the server default as fallback language
+func InitTranslations(serverLocale, clientLocale string) error {
+ defaultServerLocale = serverLocale
+ defaultClientLocale = clientLocale
+
+ var err error
+ T, err = getTranslationsBySystemLocale()
+ return err
+}
+
+func initTranslationsWithDir(dir string) error {
+ files, _ := ioutil.ReadDir(dir)
+ for _, f := range files {
+ if filepath.Ext(f.Name()) == ".json" {
+ filename := f.Name()
+ locales[strings.Split(filename, ".")[0]] = filepath.Join(dir, filename)
+
+ if err := i18n.LoadTranslationFile(filepath.Join(dir, filename)); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func getTranslationsBySystemLocale() (TranslateFunc, error) {
+ locale := defaultServerLocale
+ if _, ok := locales[locale]; !ok {
+ mlog.Warn("Failed to load system translations for", mlog.String("locale", locale), mlog.String("attempting to fall back to default locale", defaultLocale))
+ locale = defaultLocale
+ }
+
+ if locales[locale] == "" {
+ return nil, fmt.Errorf("failed to load system translations for '%v'", defaultLocale)
+ }
+
+ translations := tfuncWithFallback(locale)
+ if translations == nil {
+ return nil, fmt.Errorf("failed to load system translations")
+ }
+
+ mlog.Info("Loaded system translations", mlog.String("for locale", locale), mlog.String("from locale", locales[locale]))
+ return translations, nil
+}
+
+// GetUserTranslations get the translation function for an specific locale
+func GetUserTranslations(locale string) TranslateFunc {
+ if _, ok := locales[locale]; !ok {
+ locale = defaultLocale
+ }
+
+ translations := tfuncWithFallback(locale)
+ return translations
+}
+
+// GetTranslationsAndLocaleFromRequest return the translation function and the
+// locale based on a request headers
+func GetTranslationsAndLocaleFromRequest(r *http.Request) (TranslateFunc, string) {
+ // This is for checking against locales like pt_BR or zn_CN
+ headerLocaleFull := strings.Split(r.Header.Get("Accept-Language"), ",")[0]
+ // This is for checking against locales like en, es
+ headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0]
+ defaultLocale := defaultClientLocale
+ if locales[headerLocaleFull] != "" {
+ translations := tfuncWithFallback(headerLocaleFull)
+ return translations, headerLocaleFull
+ } else if locales[headerLocale] != "" {
+ translations := tfuncWithFallback(headerLocale)
+ return translations, headerLocale
+ } else if locales[defaultLocale] != "" {
+ translations := tfuncWithFallback(defaultLocale)
+ return translations, headerLocale
+ }
+
+ translations := tfuncWithFallback(defaultLocale)
+ return translations, defaultLocale
+}
+
+// GetSupportedLocales return a map of locale code and the file path with the
+// translations
+func GetSupportedLocales() map[string]string {
+ return locales
+}
+
+func tfuncWithFallback(pref string) TranslateFunc {
+ t, _ := i18n.Tfunc(pref)
+ return func(translationID string, args ...interface{}) string {
+ if translated := t(translationID, args...); translated != translationID {
+ return translated
+ }
+
+ t, _ := i18n.Tfunc(defaultLocale)
+ return t(translationID, args...)
+ }
+}
+
+// TranslateAsHTML translates the translationID provided and return a
+// template.HTML object
+func TranslateAsHTML(t TranslateFunc, translationID string, args map[string]interface{}) template.HTML {
+ message := t(translationID, escapeForHTML(args))
+ message = strings.Replace(message, "[[", "<strong>", -1)
+ message = strings.Replace(message, "]]", "</strong>", -1)
+ return template.HTML(message)
+}
+
+func escapeForHTML(arg interface{}) interface{} {
+ switch typedArg := arg.(type) {
+ case string:
+ return template.HTMLEscapeString(typedArg)
+ case *string:
+ return template.HTMLEscapeString(*typedArg)
+ case map[string]interface{}:
+ safeArg := make(map[string]interface{}, len(typedArg))
+ for key, value := range typedArg {
+ safeArg[key] = escapeForHTML(value)
+ }
+ return safeArg
+ default:
+ mlog.Warn(
+ "Unable to escape value for HTML template",
+ mlog.Any("html_template", arg),
+ mlog.String("template_type", reflect.ValueOf(arg).Type().String()),
+ )
+ return ""
+ }
+}
+
+// IdentityTfunc returns a translation function that don't translate, only
+// returns the same id
+func IdentityTfunc() TranslateFunc {
+ return func(translationID string, args ...interface{}) string {
+ return translationID
+ }
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/autolink.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/autolink.go
new file mode 100644
index 00000000..2eb05d90
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/autolink.go
@@ -0,0 +1,255 @@
+// Copyright (c) 2015-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"}
+ wwwAutoLinkRegex = regexp.MustCompile(`^www\d{0,3}\.`)
+)
+
+// 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 || !wwwAutoLinkRegex.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
+ }
+ 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
+ }
+
+ if start < 0 || position >= len(data) {
+ return Range{}, false
+ }
+
+ // 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
+ }
+ 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
+ }
+ return true
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/block_quote.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/block_quote.go
new file mode 100644
index 00000000..5cf66d10
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/block_quote.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2015-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) []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/v6/shared/markdown/blocks.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/blocks.go
new file mode 100644
index 00000000..fe9e272f
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/blocks.go
@@ -0,0 +1,154 @@
+// Copyright (c) 2015-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) []*ReferenceDefinition {
+ for _, block := range blocks {
+ block.Close()
+ if p, ok := block.(*Paragraph); ok && len(p.ReferenceDefinitions) > 0 {
+ referenceDefinitions = append(referenceDefinitions, p.ReferenceDefinitions...)
+ }
+ }
+ return 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 addedBlocks := container.AddChild(newBlocks); addedBlocks != nil {
+ referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions)
+ openBlocks = openBlocks[:i+1]
+ openBlocks = append(openBlocks, addedBlocks...)
+ 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
+ }
+
+ referenceDefinitions = 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 {
+ referenceDefinitions = closeBlocks(openBlocks[i+1:], referenceDefinitions)
+ openBlocks = openBlocks[:i+1]
+ openBlocks = append(openBlocks, newBlocks...)
+ break
+ }
+ }
+ }
+ }
+ }
+
+ referenceDefinitions = 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); 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); 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/v6/shared/markdown/document.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/document.go
new file mode 100644
index 00000000..306b93da
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/document.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2015-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/v6/shared/markdown/fenced_code.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/fenced_code.go
new file mode 100644
index 00000000..c8caad55
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/fenced_code.go
@@ -0,0 +1,112 @@
+// Copyright (c) 2015-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) []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/v6/shared/markdown/html.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/html.go
new file mode 100644
index 00000000..52583074
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/html.go
@@ -0,0 +1,192 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package markdown
+
+import (
+ "fmt"
+ "strings"
+)
+
+var htmlEscaper = strings.NewReplacer(
+ `&`, "&amp;",
+ `<`, "&lt;",
+ `>`, "&gt;",
+ `"`, "&quot;",
+)
+
+// 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 += "<p>"
+ }
+ for _, inline := range v.ParseInlines(referenceDefinitions) {
+ result += RenderInlineHTML(inline)
+ }
+ if !isTightList {
+ result += "</p>"
+ }
+ case *List:
+ if v.IsOrdered {
+ if v.OrderedStart != 1 {
+ result += fmt.Sprintf(`<ol start="%v">`, v.OrderedStart)
+ } else {
+ result += "<ol>"
+ }
+ } else {
+ result += "<ul>"
+ }
+ for _, block := range v.Children {
+ result += renderBlockHTML(block, referenceDefinitions, !v.IsLoose)
+ }
+ if v.IsOrdered {
+ result += "</ol>"
+ } else {
+ result += "</ul>"
+ }
+ case *ListItem:
+ result += "<li>"
+ for _, block := range v.Children {
+ result += renderBlockHTML(block, referenceDefinitions, isTightList)
+ }
+ result += "</li>"
+ case *BlockQuote:
+ result += "<blockquote>"
+ for _, block := range v.Children {
+ result += RenderBlockHTML(block, referenceDefinitions)
+ }
+ result += "</blockquote>"
+ case *FencedCode:
+ if info := v.Info(); info != "" {
+ language := strings.Fields(info)[0]
+ result += `<pre><code class="language-` + htmlEscaper.Replace(language) + `">`
+ } else {
+ result += "<pre><code>"
+ }
+ result += htmlEscaper.Replace(v.Code()) + "</code></pre>"
+ case *IndentedCode:
+ result += "<pre><code>" + htmlEscaper.Replace(v.Code()) + "</code></pre>"
+ 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 "<br />"
+ case *SoftLineBreak:
+ return "\n"
+ case *CodeSpan:
+ return "<code>" + htmlEscaper.Replace(v.Code) + "</code>"
+ case *InlineImage:
+ result += `<img src="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `" alt="` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `"`
+ if title := v.Title(); title != "" {
+ result += ` title="` + htmlEscaper.Replace(title) + `"`
+ }
+ result += ` />`
+ case *ReferenceImage:
+ result += `<img src="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `" alt="` + htmlEscaper.Replace(renderImageAltText(v.Children)) + `"`
+ if title := v.Title(); title != "" {
+ result += ` title="` + htmlEscaper.Replace(title) + `"`
+ }
+ result += ` />`
+ case *InlineLink:
+ result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"`
+ if title := v.Title(); title != "" {
+ result += ` title="` + htmlEscaper.Replace(title) + `"`
+ }
+ result += `>`
+ for _, inline := range v.Children {
+ result += RenderInlineHTML(inline)
+ }
+ result += "</a>"
+ case *ReferenceLink:
+ result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `"`
+ if title := v.Title(); title != "" {
+ result += ` title="` + htmlEscaper.Replace(title) + `"`
+ }
+ result += `>`
+ for _, inline := range v.Children {
+ result += RenderInlineHTML(inline)
+ }
+ result += "</a>"
+ case *Autolink:
+ result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `">`
+ for _, inline := range v.Children {
+ result += RenderInlineHTML(inline)
+ }
+ result += "</a>"
+ 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/v6/shared/markdown/html_entities.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/html_entities.go
new file mode 100644
index 00000000..e94cebb9
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/html_entities.go
@@ -0,0 +1,2132 @@
+// Copyright (c) 2015-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/v6/shared/markdown/indented_code.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/indented_code.go
new file mode 100644
index 00000000..a89ee6c7
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/indented_code.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2015-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/v6/shared/markdown/inlines.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/inlines.go
new file mode 100644
index 00000000..43dee3bd
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/inlines.go
@@ -0,0 +1,663 @@
+// Copyright (c) 2015-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 inlineElement := element.Prev(); inlineElement != nil; inlineElement = inlineElement.Prev() {
+ if d := inlineElement.Value.(*delimiter); d.Type == linkOpeningDelimiter {
+ d.IsInactive = true
+ }
+ }
+ }
+ p.delimiterStack.Remove(element)
+ return
+ }
+ 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 {
+ ret := inlines[:0]
+ 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/v6/shared/markdown/inspect.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/inspect.go
new file mode 100644
index 00000000..3c7f2d1c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/inspect.go
@@ -0,0 +1,78 @@
+// Copyright (c) 2015-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/v6/shared/markdown/lines.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/lines.go
new file mode 100644
index 00000000..f59e5afe
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/lines.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package markdown
+
+import (
+ "strings"
+)
+
+type Line struct {
+ Range
+}
+
+func ParseLines(markdown string) []Line {
+ lineStartPosition := 0
+ isAfterCarriageReturn := false
+ lines := make([]Line, 0, strings.Count(markdown, "\n"))
+ 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 lines
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/links.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/links.go
new file mode 100644
index 00000000..6aa56f25
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/links.go
@@ -0,0 +1,184 @@
+// Copyright (c) 2015-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 position < len(markdown)-1 && 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') || position == len(markdown)-1 {
+ return
+ }
+ position += 1
+
+ // Read height
+ hasHeight := false
+ for position < len(markdown)-1 && 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/v6/shared/markdown/list.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/list.go
new file mode 100644
index 00000000..39039295
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/list.go
@@ -0,0 +1,220 @@
+// Copyright (c) 2015-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/v6/shared/markdown/markdown.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/markdown.go
new file mode 100644
index 00000000..5ccdad8c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/markdown.go
@@ -0,0 +1,147 @@
+// Copyright (c) 2015-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
+ }
+ 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/v6/shared/markdown/paragraph.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/paragraph.go
new file mode 100644
index 00000000..aef01b5e
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/paragraph.go
@@ -0,0 +1,71 @@
+// Copyright (c) 2015-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/v6/shared/markdown/reference_definition.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/reference_definition.go
new file mode 100644
index 00000000..69e8ed94
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/markdown/reference_definition.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2015-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/mattermost-server/v6/shared/mlog/default.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/default.go
new file mode 100644
index 00000000..0567c016
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/default.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package mlog
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "os"
+)
+
+// defaultLog manually encodes the log to STDERR, providing a basic, default logging implementation
+// before mlog is fully configured.
+func defaultLog(level Level, msg string, fields ...Field) {
+ mFields := make(map[string]string)
+ buf := &bytes.Buffer{}
+
+ for _, fld := range fields {
+ buf.Reset()
+ fld.ValueString(buf, shouldQuote)
+ mFields[fld.Key] = buf.String()
+ }
+
+ log := struct {
+ Level string `json:"level"`
+ Message string `json:"msg"`
+ Fields map[string]string `json:"fields,omitempty"`
+ }{
+ level.Name,
+ msg,
+ mFields,
+ }
+
+ if b, err := json.Marshal(log); err != nil {
+ fmt.Fprintf(os.Stderr, `{"level":"error","msg":"failed to encode log message"}%s`, "\n")
+ } else {
+ fmt.Fprintf(os.Stderr, "%s\n", b)
+ }
+}
+
+func defaultIsLevelEnabled(level Level) bool {
+ return true
+}
+
+func defaultCustomMultiLog(lvl []Level, msg string, fields ...Field) {
+ for _, level := range lvl {
+ defaultLog(level, msg, fields...)
+ }
+}
+
+// shouldQuote returns true if val contains any characters that require quotations.
+func shouldQuote(val string) bool {
+ for _, c := range val {
+ if !((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ c == '-' || c == '.' || c == '_' || c == '/' || c == '@' || c == '^' || c == '+') {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/global.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/global.go
new file mode 100644
index 00000000..de346123
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/global.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package mlog
+
+import (
+ "sync"
+)
+
+var (
+ globalLogger *Logger
+ muxGlobalLogger sync.RWMutex
+)
+
+func InitGlobalLogger(logger *Logger) {
+ muxGlobalLogger.Lock()
+ defer muxGlobalLogger.Unlock()
+
+ globalLogger = logger
+}
+
+func getGlobalLogger() *Logger {
+ muxGlobalLogger.RLock()
+ defer muxGlobalLogger.RUnlock()
+
+ return globalLogger
+}
+
+// IsLevelEnabled returns true only if at least one log target is
+// configured to emit the specified log level. Use this check when
+// gathering the log info may be expensive.
+//
+// Note, transformations and serializations done via fields are already
+// lazily evaluated and don't require this check beforehand.
+func IsLevelEnabled(level Level) bool {
+ logger := getGlobalLogger()
+ if logger == nil {
+ return defaultIsLevelEnabled(level)
+ }
+ return logger.IsLevelEnabled(level)
+}
+
+// Log emits the log record for any targets configured for the specified level.
+func Log(level Level, msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(level, msg, fields...)
+ return
+ }
+ logger.Log(level, msg, fields...)
+}
+
+// LogM emits the log record for any targets configured for the specified levels.
+// Equivalent to calling `Log` once for each level.
+func LogM(levels []Level, msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultCustomMultiLog(levels, msg, fields...)
+ return
+ }
+ logger.LogM(levels, msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Trace` level.
+func Trace(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlTrace, msg, fields...)
+ return
+ }
+ logger.Trace(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Debug` level.
+func Debug(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlDebug, msg, fields...)
+ return
+ }
+ logger.Debug(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Info` level.
+func Info(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlInfo, msg, fields...)
+ return
+ }
+ logger.Info(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Warn` level.
+func Warn(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlWarn, msg, fields...)
+ return
+ }
+ logger.Warn(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Error` level.
+func Error(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlError, msg, fields...)
+ return
+ }
+ logger.Error(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Critical` level.
+// DEPRECATED: Either use Error or Fatal.
+func Critical(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlCritical, msg, fields...)
+ return
+ }
+ logger.Critical(msg, fields...)
+}
+
+func Fatal(msg string, fields ...Field) {
+ logger := getGlobalLogger()
+ if logger == nil {
+ defaultLog(LvlFatal, msg, fields...)
+ return
+ }
+ logger.Fatal(msg, fields...)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/levels.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/levels.go
new file mode 100644
index 00000000..c0b30996
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/levels.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package mlog
+
+import "github.com/mattermost/logr/v2"
+
+// Standard levels.
+var (
+ LvlPanic = logr.Panic // ID = 0
+ LvlFatal = logr.Fatal // ID = 1
+ LvlError = logr.Error // ID = 2
+ LvlWarn = logr.Warn // ID = 3
+ LvlInfo = logr.Info // ID = 4
+ LvlDebug = logr.Debug // ID = 5
+ LvlTrace = logr.Trace // ID = 6
+ StdAll = []Level{LvlPanic, LvlFatal, LvlError, LvlWarn, LvlInfo, LvlDebug, LvlTrace}
+ // non-standard "critical" level
+ LvlCritical = Level{ID: 7, Name: "critical"}
+ // used by redirected standard logger
+ LvlStdLog = Level{ID: 10, Name: "stdlog"}
+ // used only by the logger
+ LvlLogError = Level{ID: 11, Name: "logerror", Stacktrace: true}
+)
+
+// Register custom (discrete) levels here.
+// !!!!! Custom ID's must be between 20 and 32,768 !!!!!!
+var (
+ // used by the audit system
+ LvlAuditAPI = Level{ID: 100, Name: "audit-api"}
+ LvlAuditContent = Level{ID: 101, Name: "audit-content"}
+ LvlAuditPerms = Level{ID: 102, Name: "audit-permissions"}
+ LvlAuditCLI = Level{ID: 103, Name: "audit-cli"}
+
+ // used by the TCP log target
+ LvlTCPLogTarget = Level{ID: 120, Name: "TcpLogTarget"}
+
+ // used by Remote Cluster Service
+ LvlRemoteClusterServiceDebug = Level{ID: 130, Name: "RemoteClusterServiceDebug"}
+ LvlRemoteClusterServiceError = Level{ID: 131, Name: "RemoteClusterServiceError"}
+ LvlRemoteClusterServiceWarn = Level{ID: 132, Name: "RemoteClusterServiceWarn"}
+
+ // used by Shared Channel Sync Service
+ LvlSharedChannelServiceDebug = Level{ID: 200, Name: "SharedChannelServiceDebug"}
+ LvlSharedChannelServiceError = Level{ID: 201, Name: "SharedChannelServiceError"}
+ LvlSharedChannelServiceWarn = Level{ID: 202, Name: "SharedChannelServiceWarn"}
+ LvlSharedChannelServiceMessagesInbound = Level{ID: 203, Name: "SharedChannelServiceMsgInbound"}
+ LvlSharedChannelServiceMessagesOutbound = Level{ID: 204, Name: "SharedChannelServiceMsgOutbound"}
+
+ // Focalboard
+ LvlFBTelemetry = Level{ID: 9000, Name: "telemetry"}
+ LvlFBMetrics = Level{ID: 9001, Name: "metrics"}
+)
+
+// Combinations for LogM (log multi).
+var (
+ MLvlAuditAll = []Level{LvlAuditAPI, LvlAuditContent, LvlAuditPerms, LvlAuditCLI}
+)
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/mlog.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/mlog.go
new file mode 100644
index 00000000..ac56362c
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/mlog.go
@@ -0,0 +1,419 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+// Package mlog provides a simple wrapper around Logr.
+package mlog
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/mattermost/logr/v2"
+ logrcfg "github.com/mattermost/logr/v2/config"
+)
+
+const (
+ ShutdownTimeout = time.Second * 15
+ FlushTimeout = time.Second * 15
+ DefaultMaxQueueSize = 1000
+ DefaultMetricsUpdateFreqMillis = 15000
+)
+
+type LoggerIFace interface {
+ IsLevelEnabled(Level) bool
+ Debug(string, ...Field)
+ Info(string, ...Field)
+ Warn(string, ...Field)
+ Error(string, ...Field)
+ Critical(string, ...Field)
+ Log(Level, string, ...Field)
+ LogM([]Level, string, ...Field)
+}
+
+// Type and function aliases from Logr to limit the spread of dependencies.
+type Field = logr.Field
+type Level = logr.Level
+type Option = logr.Option
+type Target = logr.Target
+type TargetInfo = logr.TargetInfo
+type LogRec = logr.LogRec
+type LogCloner = logr.LogCloner
+type MetricsCollector = logr.MetricsCollector
+type TargetCfg = logrcfg.TargetCfg
+type Sugar = logr.Sugar
+
+// LoggerConfiguration is a map of LogTarget configurations.
+type LoggerConfiguration map[string]TargetCfg
+
+func (lc LoggerConfiguration) Append(cfg LoggerConfiguration) {
+ for k, v := range cfg {
+ lc[k] = v
+ }
+}
+
+func (lc LoggerConfiguration) toTargetCfg() map[string]logrcfg.TargetCfg {
+ tcfg := make(map[string]logrcfg.TargetCfg)
+ for k, v := range lc {
+ tcfg[k] = v
+ }
+ return tcfg
+}
+
+// Any picks the best supported field type based on type of val.
+// For best performance when passing a struct (or struct pointer),
+// implement `logr.LogWriter` on the struct, otherwise reflection
+// will be used to generate a string representation.
+var Any = logr.Any
+
+// Int64 constructs a field containing a key and Int64 value.
+var Int64 = logr.Int64
+
+// Int32 constructs a field containing a key and Int32 value.
+var Int32 = logr.Int32
+
+// Int constructs a field containing a key and Int value.
+var Int = logr.Int
+
+// Uint64 constructs a field containing a key and Uint64 value.
+var Uint64 = logr.Uint64
+
+// Uint32 constructs a field containing a key and Uint32 value.
+var Uint32 = logr.Uint32
+
+// Uint constructs a field containing a key and Uint value.
+var Uint = logr.Uint
+
+// Float64 constructs a field containing a key and Float64 value.
+var Float64 = logr.Float64
+
+// Float32 constructs a field containing a key and Float32 value.
+var Float32 = logr.Float32
+
+// String constructs a field containing a key and String value.
+var String = logr.String
+
+// Stringer constructs a field containing a key and a fmt.Stringer value.
+// The fmt.Stringer's `String` method is called lazily.
+var Stringer = func(key string, s fmt.Stringer) logr.Field {
+ if s == nil {
+ return Field{Key: key, Type: logr.StringType, String: ""}
+ }
+ return Field{Key: key, Type: logr.StringType, String: s.String()}
+}
+
+// Err constructs a field containing a default key ("error") and error value.
+var Err = func(err error) logr.Field {
+ return NamedErr("error", err)
+}
+
+// NamedErr constructs a field containing a key and error value.
+var NamedErr = func(key string, err error) logr.Field {
+ if err == nil {
+ return Field{Key: key, Type: logr.StringType, String: ""}
+ }
+ return Field{Key: key, Type: logr.StringType, String: err.Error()}
+}
+
+// Bool constructs a field containing a key and bool value.
+var Bool = logr.Bool
+
+// Time constructs a field containing a key and time.Time value.
+var Time = logr.Time
+
+// Duration constructs a field containing a key and time.Duration value.
+var Duration = logr.Duration
+
+// Millis constructs a field containing a key and timestamp value.
+// The timestamp is expected to be milliseconds since Jan 1, 1970 UTC.
+var Millis = logr.Millis
+
+// Array constructs a field containing a key and array value.
+var Array = logr.Array
+
+// Map constructs a field containing a key and map value.
+var Map = logr.Map
+
+// Logger provides a thin wrapper around a Logr instance. This is a struct instead of an interface
+// so that there are no allocations on the heap each interface method invocation. Normally not
+// something to be concerned about, but logging calls for disabled levels should have as little CPU
+// and memory impact as possible. Most of these wrapper calls will be inlined as well.
+type Logger struct {
+ log *logr.Logger
+ lockConfig *int32
+}
+
+// NewLogger creates a new Logger instance which can be configured via `(*Logger).Configure`.
+// Some options with invalid values can cause an error to be returned, however `NewLogger()`
+// using just defaults never errors.
+func NewLogger(options ...Option) (*Logger, error) {
+ options = append(options, logr.StackFilter(logr.GetPackageName("NewLogger")))
+
+ lgr, err := logr.New(options...)
+ if err != nil {
+ return nil, err
+ }
+
+ log := lgr.NewLogger()
+ var lockConfig int32
+
+ return &Logger{
+ log: &log,
+ lockConfig: &lockConfig,
+ }, nil
+}
+
+// Configure provides a new configuration for this logger.
+// Zero or more sources of config can be provided:
+// cfgFile - path to file containing JSON
+// cfgEscaped - JSON string probably from ENV var
+//
+// For each case JSON containing log targets is provided. Target name collisions are resolved
+// using the following precedence:
+// cfgFile > cfgEscaped
+func (l *Logger) Configure(cfgFile string, cfgEscaped string) error {
+ if atomic.LoadInt32(l.lockConfig) != 0 {
+ return ErrConfigurationLock
+ }
+
+ cfgMap := make(LoggerConfiguration)
+
+ // Add config from file
+ if cfgFile != "" {
+ b, err := ioutil.ReadFile(cfgFile)
+ if err != nil {
+ return fmt.Errorf("error reading logger config file %s: %w", cfgFile, err)
+ }
+
+ var mapCfgFile LoggerConfiguration
+ if err := json.Unmarshal(b, &mapCfgFile); err != nil {
+ return fmt.Errorf("error decoding logger config file %s: %w", cfgFile, err)
+ }
+ cfgMap.Append(mapCfgFile)
+ }
+
+ // Add config from escaped json string
+ if cfgEscaped != "" {
+ var mapCfgEscaped LoggerConfiguration
+ if err := json.Unmarshal([]byte(cfgEscaped), &mapCfgEscaped); err != nil {
+ return fmt.Errorf("error decoding logger config as escaped json: %w", err)
+ }
+ cfgMap.Append(mapCfgEscaped)
+ }
+
+ if len(cfgMap) == 0 {
+ return nil
+ }
+
+ return logrcfg.ConfigureTargets(l.log.Logr(), cfgMap.toTargetCfg(), nil)
+}
+
+// ConfigureTargets provides a new configuration for this logger via a `LoggerConfig` map.
+// Typically `mlog.Configure` is used instead which accepts JSON formatted configuration.
+func (l *Logger) ConfigureTargets(cfg LoggerConfiguration) error {
+ if atomic.LoadInt32(l.lockConfig) != 0 {
+ return ErrConfigurationLock
+ }
+ return logrcfg.ConfigureTargets(l.log.Logr(), cfg.toTargetCfg(), nil)
+}
+
+// LockConfiguration disallows further configuration changes until `UnlockConfiguration`
+// is called. The previous locked stated is returned.
+func (l *Logger) LockConfiguration() bool {
+ old := atomic.SwapInt32(l.lockConfig, 1)
+ return old != 0
+}
+
+// UnlockConfiguration allows configuration changes. The previous locked stated is returned.
+func (l *Logger) UnlockConfiguration() bool {
+ old := atomic.SwapInt32(l.lockConfig, 0)
+ return old != 0
+}
+
+// IsConfigurationLocked returns the current state of the configuration lock.
+func (l *Logger) IsConfigurationLocked() bool {
+ return atomic.LoadInt32(l.lockConfig) != 0
+}
+
+// With creates a new Logger with the specified fields. This is a light-weight
+// operation and can be called on demand.
+func (l *Logger) With(fields ...Field) *Logger {
+ logWith := l.log.With(fields...)
+ return &Logger{
+ log: &logWith,
+ lockConfig: l.lockConfig,
+ }
+}
+
+// IsLevelEnabled returns true only if at least one log target is
+// configured to emit the specified log level. Use this check when
+// gathering the log info may be expensive.
+//
+// Note, transformations and serializations done via fields are already
+// lazily evaluated and don't require this check beforehand.
+func (l *Logger) IsLevelEnabled(level Level) bool {
+ return l.log.IsLevelEnabled(level)
+}
+
+// Log emits the log record for any targets configured for the specified level.
+func (l *Logger) Log(level Level, msg string, fields ...Field) {
+ l.log.Log(level, msg, fields...)
+}
+
+// LogM emits the log record for any targets configured for the specified levels.
+// Equivalent to calling `Log` once for each level.
+func (l *Logger) LogM(levels []Level, msg string, fields ...Field) {
+ l.log.LogM(levels, msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Trace` level.
+func (l *Logger) Trace(msg string, fields ...Field) {
+ l.log.Trace(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Debug` level.
+func (l *Logger) Debug(msg string, fields ...Field) {
+ l.log.Debug(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Info` level.
+func (l *Logger) Info(msg string, fields ...Field) {
+ l.log.Info(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Warn` level.
+func (l *Logger) Warn(msg string, fields ...Field) {
+ l.log.Warn(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Error` level.
+func (l *Logger) Error(msg string, fields ...Field) {
+ l.log.Error(msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Critical` level.
+func (l *Logger) Critical(msg string, fields ...Field) {
+ l.log.Log(LvlCritical, msg, fields...)
+}
+
+// Convenience method equivalent to calling `Log` with the `Fatal` level,
+// followed by `os.Exit(1)`.
+func (l *Logger) Fatal(msg string, fields ...Field) {
+ l.log.Log(logr.Fatal, msg, fields...)
+ _ = l.Shutdown()
+ os.Exit(1)
+}
+
+// HasTargets returns true if at least one log target has been added.
+func (l *Logger) HasTargets() bool {
+ return l.log.Logr().HasTargets()
+}
+
+// StdLogger creates a standard logger backed by this logger.
+// All log records are output with the specified level.
+func (l *Logger) StdLogger(level Level) *log.Logger {
+ return l.log.StdLogger(level)
+}
+
+// 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 and passed to this logger.
+func (l *Logger) StdLogWriter() io.Writer {
+ return &logWriter{
+ logger: l,
+ }
+}
+
+// RedirectStdLog redirects output from the standard library's package-global logger
+// to this logger at the specified level and with zero or more Field's. Since this logger already
+// handles caller annotations, timestamps, etc., it automatically disables the standard
+// library's annotations and prefixing.
+// A function is returned that restores the original prefix and flags and resets the standard
+// library's output to os.Stdout.
+func (l *Logger) RedirectStdLog(level Level, fields ...Field) func() {
+ return l.log.Logr().RedirectStdLog(level, fields...)
+}
+
+// RemoveTargets safely removes one or more targets based on the filtering method.
+// `f` should return true to delete the target, false to keep it.
+// When removing a target, best effort is made to write any queued log records before
+// closing, with cxt determining how much time can be spent in total.
+// Note, keep the timeout short since this method blocks certain logging operations.
+func (l *Logger) RemoveTargets(ctx context.Context, f func(ti TargetInfo) bool) error {
+ return l.log.Logr().RemoveTargets(ctx, f)
+}
+
+// SetMetricsCollector sets (or resets) the metrics collector to be used for gathering
+// metrics for all targets. Only targets added after this call will use the collector.
+//
+// To ensure all targets use a collector, use the `SetMetricsCollector` option when
+// creating the Logger instead, or configure/reconfigure the Logger after calling this method.
+func (l *Logger) SetMetricsCollector(collector MetricsCollector, updateFrequencyMillis int64) {
+ l.log.Logr().SetMetricsCollector(collector, updateFrequencyMillis)
+}
+
+// Sugar creates a new `Logger` with a less structured API. Any fields are preserved.
+func (l *Logger) Sugar(fields ...Field) Sugar {
+ return l.log.Sugar(fields...)
+}
+
+// Flush forces all targets to write out any queued log records with a default timeout.
+func (l *Logger) Flush() error {
+ ctx, cancel := context.WithTimeout(context.Background(), FlushTimeout)
+ defer cancel()
+ return l.log.Logr().FlushWithTimeout(ctx)
+}
+
+// Flush forces all targets to write out any queued log records with the specfified timeout.
+func (l *Logger) FlushWithTimeout(ctx context.Context) error {
+ return l.log.Logr().FlushWithTimeout(ctx)
+}
+
+// Shutdown shuts down the logger after making best efforts to flush any
+// remaining records.
+func (l *Logger) Shutdown() error {
+ ctx, cancel := context.WithTimeout(context.Background(), ShutdownTimeout)
+ defer cancel()
+ return l.log.Logr().ShutdownWithTimeout(ctx)
+}
+
+// Shutdown shuts down the logger after making best efforts to flush any
+// remaining records.
+func (l *Logger) ShutdownWithTimeout(ctx context.Context) error {
+ return l.log.Logr().ShutdownWithTimeout(ctx)
+}
+
+// GetPackageName reduces a fully qualified function name to the package name
+// By sirupsen: https://github.com/sirupsen/logrus/blob/master/entry.go
+func GetPackageName(f string) string {
+ for {
+ lastPeriod := strings.LastIndex(f, ".")
+ lastSlash := strings.LastIndex(f, "/")
+ if lastPeriod > lastSlash {
+ f = f[:lastPeriod]
+ } else {
+ break
+ }
+ }
+ return f
+}
+
+type logWriter struct {
+ logger *Logger
+}
+
+func (lw *logWriter) Write(p []byte) (int, error) {
+ lw.logger.Info(string(p))
+ return len(p), nil
+}
+
+// ErrConfigurationLock is returned when one of a logger's configuration APIs is called
+// while the configuration is locked.
+var ErrConfigurationLock = errors.New("configuration is locked")
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/options.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/options.go
new file mode 100644
index 00000000..3a98b480
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/options.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package mlog
+
+import "github.com/mattermost/logr/v2"
+
+// MaxQueueSize is the maximum number of log records that can be queued.
+// If exceeded, `OnQueueFull` is called which determines if the log
+// record will be dropped or block until add is successful.
+// Defaults to DefaultMaxQueueSize.
+func MaxQueueSize(size int) Option {
+ return logr.MaxQueueSize(size)
+}
+
+// OnLoggerError, when not nil, is called any time an internal
+// logging error occurs. For example, this can happen when a
+// target cannot connect to its data sink.
+func OnLoggerError(f func(error)) Option {
+ return logr.OnLoggerError(f)
+}
+
+// OnQueueFull, when not nil, is called on an attempt to add
+// a log record to a full Logr queue.
+// `MaxQueueSize` can be used to modify the maximum queue size.
+// This function should return quickly, with a bool indicating whether
+// the log record should be dropped (true) or block until the log record
+// is successfully added (false). If nil then blocking (false) is assumed.
+func OnQueueFull(f func(rec *LogRec, maxQueueSize int) bool) Option {
+ return logr.OnQueueFull(f)
+}
+
+// OnTargetQueueFull, when not nil, is called on an attempt to add
+// a log record to a full target queue provided the target supports reporting
+// this condition.
+// This function should return quickly, with a bool indicating whether
+// the log record should be dropped (true) or block until the log record
+// is successfully added (false). If nil then blocking (false) is assumed.
+func OnTargetQueueFull(f func(target Target, rec *LogRec, maxQueueSize int) bool) Option {
+ return logr.OnTargetQueueFull(f)
+}
+
+// SetMetricsCollector enables metrics collection by supplying a MetricsCollector.
+// The MetricsCollector provides counters and gauges that are updated by log targets.
+// `updateFreqMillis` determines how often polled metrics are updated. Defaults to 15000 (15 seconds)
+// and must be at least 250 so we don't peg the CPU.
+func SetMetricsCollector(collector MetricsCollector, updateFreqMillis int64) Option {
+ return logr.SetMetricsCollector(collector, updateFreqMillis)
+}
+
+// StackFilter provides a list of package names to exclude from the top of
+// stack traces. The Logr packages are automatically filtered.
+func StackFilter(pkg ...string) Option {
+ return logr.StackFilter(pkg...)
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/tlog.go b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/tlog.go
new file mode 100644
index 00000000..ef8f6016
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/shared/mlog/tlog.go
@@ -0,0 +1,79 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package mlog
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "sync"
+
+ "github.com/mattermost/logr/v2"
+ "github.com/mattermost/logr/v2/formatters"
+ "github.com/mattermost/logr/v2/targets"
+)
+
+// AddWriterTarget adds a simple io.Writer target to an existing Logger.
+// The `io.Writer` can be a buffer which is useful for testing.
+// When adding a buffer to collect logs make sure to use `mlog.Buffer` which is
+// a thread safe version of `bytes.Buffer`.
+func AddWriterTarget(logger *Logger, w io.Writer, useJSON bool, levels ...Level) error {
+ filter := logr.NewCustomFilter(levels...)
+
+ var formatter logr.Formatter
+ if useJSON {
+ formatter = &formatters.JSON{EnableCaller: true}
+ } else {
+ formatter = &formatters.Plain{EnableCaller: true}
+ }
+
+ target := targets.NewWriterTarget(w)
+ return logger.log.Logr().AddTarget(target, "_testWriter", filter, formatter, 1000)
+}
+
+// CreateConsoleTestLogger creates a logger for unit tests. Log records are output to `os.Stdout`.
+// Logs can also be mirrored to the optional `io.Writer`.
+func CreateConsoleTestLogger(useJSON bool, level Level) *Logger {
+ logger, _ := NewLogger()
+
+ filter := logr.StdFilter{
+ Lvl: level,
+ Stacktrace: LvlPanic,
+ }
+
+ var formatter logr.Formatter
+ if useJSON {
+ formatter = &formatters.JSON{EnableCaller: true}
+ } else {
+ formatter = &formatters.Plain{EnableCaller: true}
+ }
+
+ target := targets.NewWriterTarget(os.Stdout)
+ if err := logger.log.Logr().AddTarget(target, "_testcon", filter, formatter, 1000); err != nil {
+ panic(err)
+ }
+ return logger
+}
+
+// Buffer provides a thread-safe buffer useful for logging to memory in unit tests.
+type Buffer struct {
+ buf bytes.Buffer
+ mux sync.Mutex
+}
+
+func (b *Buffer) Read(p []byte) (n int, err error) {
+ b.mux.Lock()
+ defer b.mux.Unlock()
+ return b.buf.Read(p)
+}
+func (b *Buffer) Write(p []byte) (n int, err error) {
+ b.mux.Lock()
+ defer b.mux.Unlock()
+ return b.buf.Write(p)
+}
+func (b *Buffer) String() string {
+ b.mux.Lock()
+ defer b.mux.Unlock()
+ return b.buf.String()
+}
diff --git a/vendor/github.com/mattermost/mattermost-server/v6/utils/jsonutils/json.go b/vendor/github.com/mattermost/mattermost-server/v6/utils/jsonutils/json.go
new file mode 100644
index 00000000..9d5e7872
--- /dev/null
+++ b/vendor/github.com/mattermost/mattermost-server/v6/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/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go
index ff714a37..3eba4cb3 100644
--- a/vendor/github.com/mattn/go-isatty/isatty_others.go
+++ b/vendor/github.com/mattn/go-isatty/isatty_others.go
@@ -1,4 +1,4 @@
-// +build appengine js nacl
+// +build appengine js nacl wasm
package isatty
diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go
index bdd5c79a..30106707 100644
--- a/vendor/github.com/mattn/go-isatty/isatty_solaris.go
+++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go
@@ -8,10 +8,9 @@ import (
)
// IsTerminal returns true if the given file descriptor is a terminal.
-// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
+// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c
func IsTerminal(fd uintptr) bool {
- var termio unix.Termio
- err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
+ _, err := unix.IoctlGetTermio(int(fd), unix.TCGETA)
return err == nil
}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go
index 31a1ca97..4e7b850e 100644
--- a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go
+++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go
@@ -1,4 +1,4 @@
-// +build linux aix
+// +build linux aix zos
// +build !appengine
package isatty
diff --git a/vendor/github.com/mattn/go-isatty/renovate.json b/vendor/github.com/mattn/go-isatty/renovate.json
deleted file mode 100644
index 5ae9d96b..00000000
--- a/vendor/github.com/mattn/go-isatty/renovate.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "extends": [
- "config:base"
- ],
- "postUpdateOptions": [
- "gomodTidy"
- ]
-}
diff --git a/vendor/github.com/mattn/go-runewidth/go.mod b/vendor/github.com/mattn/go-runewidth/go.mod
index fa7f4d86..62dba1bf 100644
--- a/vendor/github.com/mattn/go-runewidth/go.mod
+++ b/vendor/github.com/mattn/go-runewidth/go.mod
@@ -1,3 +1,5 @@
module github.com/mattn/go-runewidth
go 1.9
+
+require github.com/rivo/uniseg v0.2.0
diff --git a/vendor/github.com/mattn/go-runewidth/go.sum b/vendor/github.com/mattn/go-runewidth/go.sum
new file mode 100644
index 00000000..03f902d5
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/go.sum
@@ -0,0 +1,2 @@
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth.go b/vendor/github.com/mattn/go-runewidth/runewidth.go
index 19f8e044..3d7fa560 100644
--- a/vendor/github.com/mattn/go-runewidth/runewidth.go
+++ b/vendor/github.com/mattn/go-runewidth/runewidth.go
@@ -2,6 +2,8 @@ package runewidth
import (
"os"
+
+ "github.com/rivo/uniseg"
)
//go:generate go run script/generate.go
@@ -10,11 +12,14 @@ var (
// EastAsianWidth will be set true if the current locale is CJK
EastAsianWidth bool
- // ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
- ZeroWidthJoiner bool
+ // StrictEmojiNeutral should be set false if handle broken fonts
+ StrictEmojiNeutral bool = true
// DefaultCondition is a condition in current locale
- DefaultCondition = &Condition{}
+ DefaultCondition = &Condition{
+ EastAsianWidth: false,
+ StrictEmojiNeutral: true,
+ }
)
func init() {
@@ -30,7 +35,6 @@ func handleEnv() {
}
// update DefaultCondition
DefaultCondition.EastAsianWidth = EastAsianWidth
- DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
}
type interval struct {
@@ -85,63 +89,69 @@ var nonprint = table{
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
type Condition struct {
- EastAsianWidth bool
- ZeroWidthJoiner bool
+ EastAsianWidth bool
+ StrictEmojiNeutral bool
}
// NewCondition return new instance of Condition which is current locale.
func NewCondition() *Condition {
return &Condition{
- EastAsianWidth: EastAsianWidth,
- ZeroWidthJoiner: ZeroWidthJoiner,
+ EastAsianWidth: EastAsianWidth,
+ StrictEmojiNeutral: StrictEmojiNeutral,
}
}
// RuneWidth returns the number of cells in r.
// See http://www.unicode.org/reports/tr11/
func (c *Condition) RuneWidth(r rune) int {
- switch {
- case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
- return 0
- case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
- return 2
- default:
- return 1
- }
-}
-
-func (c *Condition) stringWidth(s string) (width int) {
- for _, r := range []rune(s) {
- width += c.RuneWidth(r)
- }
- return width
-}
-
-func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
- r1, r2 := rune(0), rune(0)
- for _, r := range []rune(s) {
- if r == 0xFE0E || r == 0xFE0F {
- continue
+ // optimized version, verified by TestRuneWidthChecksums()
+ if !c.EastAsianWidth {
+ switch {
+ case r < 0x20 || r > 0x10FFFF:
+ return 0
+ case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint
+ return 0
+ case r < 0x300:
+ return 1
+ case inTable(r, narrow):
+ return 1
+ case inTables(r, nonprint, combining):
+ return 0
+ case inTable(r, doublewidth):
+ return 2
+ default:
+ return 1
}
- w := c.RuneWidth(r)
- if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
- if width < w {
- width = w
- }
- } else {
- width += w
+ } else {
+ switch {
+ case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining):
+ return 0
+ case inTable(r, narrow):
+ return 1
+ case inTables(r, ambiguous, doublewidth):
+ return 2
+ case !c.StrictEmojiNeutral && inTables(r, ambiguous, emoji, narrow):
+ return 2
+ default:
+ return 1
}
- r1, r2 = r2, r
}
- return width
}
// StringWidth return width as you can see
func (c *Condition) StringWidth(s string) (width int) {
- if c.ZeroWidthJoiner {
- return c.stringWidthZeroJoiner(s)
+ g := uniseg.NewGraphemes(s)
+ for g.Next() {
+ var chWidth int
+ for _, r := range g.Runes() {
+ chWidth = c.RuneWidth(r)
+ if chWidth > 0 {
+ break // Our best guess at this point is to use the width of the first non-zero-width rune.
+ }
+ }
+ width += chWidth
}
- return c.stringWidth(s)
+ return
}
// Truncate return string truncated with w cells
@@ -149,19 +159,25 @@ func (c *Condition) Truncate(s string, w int, tail string) string {
if c.StringWidth(s) <= w {
return s
}
- r := []rune(s)
- tw := c.StringWidth(tail)
- w -= tw
- width := 0
- i := 0
- for ; i < len(r); i++ {
- cw := c.RuneWidth(r[i])
- if width+cw > w {
+ w -= c.StringWidth(tail)
+ var width int
+ pos := len(s)
+ g := uniseg.NewGraphemes(s)
+ for g.Next() {
+ var chWidth int
+ for _, r := range g.Runes() {
+ chWidth = c.RuneWidth(r)
+ if chWidth > 0 {
+ break // See StringWidth() for details.
+ }
+ }
+ if width+chWidth > w {
+ pos, _ = g.Positions()
break
}
- width += cw
+ width += chWidth
}
- return string(r[0:i]) + tail
+ return s[:pos] + tail
}
// Wrap return string wrapped with w cells
@@ -169,7 +185,7 @@ func (c *Condition) Wrap(s string, w int) string {
width := 0
out := ""
for _, r := range []rune(s) {
- cw := RuneWidth(r)
+ cw := c.RuneWidth(r)
if r == '\n' {
out += string(r)
width = 0
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_table.go b/vendor/github.com/mattn/go-runewidth/runewidth_table.go
index b27d77d8..e5d890c2 100644
--- a/vendor/github.com/mattn/go-runewidth/runewidth_table.go
+++ b/vendor/github.com/mattn/go-runewidth/runewidth_table.go
@@ -124,8 +124,10 @@ var ambiguous = table{
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
{0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
}
-var notassigned = table{
- {0x27E6, 0x27ED}, {0x2985, 0x2986},
+var narrow = table{
+ {0x0020, 0x007E}, {0x00A2, 0x00A3}, {0x00A5, 0x00A6},
+ {0x00AC, 0x00AC}, {0x00AF, 0x00AF}, {0x27E6, 0x27ED},
+ {0x2985, 0x2986},
}
var neutral = table{
diff --git a/vendor/github.com/minio/md5-simd/LICENSE b/vendor/github.com/minio/md5-simd/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/minio/md5-simd/LICENSE.Golang b/vendor/github.com/minio/md5-simd/LICENSE.Golang
new file mode 100644
index 00000000..6a66aea5
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/LICENSE.Golang
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/minio/md5-simd/README.md b/vendor/github.com/minio/md5-simd/README.md
new file mode 100644
index 00000000..fa6fce1a
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/README.md
@@ -0,0 +1,198 @@
+
+# md5-simd
+
+This is a SIMD accelerated MD5 package, allowing up to either 8 (AVX2) or 16 (AVX512) independent MD5 sums to be calculated on a single CPU core.
+
+It was originally based on the [md5vec](https://github.com/igneous-systems/md5vec) repository by Igneous Systems, but has been made more flexible by amongst others supporting different message sizes per lane and adding AVX512.
+
+`md5-simd` integrates a similar mechanism as described in [minio/sha256-simd](https://github.com/minio/sha256-simd#support-for-avx512) for making it easy for clients to take advantages of the parallel nature of the MD5 calculation. This will result in reduced overall CPU load.
+
+It is important to understand that `md5-simd` **does not speed up** a single threaded MD5 hash sum.
+Rather it allows multiple __independent__ MD5 sums to be computed in parallel on the same CPU core,
+thereby making more efficient usage of the computing resources.
+
+## Usage
+
+[![Documentation](https://godoc.org/github.com/minio/md5-simd?status.svg)](https://pkg.go.dev/github.com/minio/md5-simd?tab=doc)
+
+
+In order to use `md5-simd`, you must first create an `Server` which can be
+used to instantiate one or more objects for MD5 hashing.
+
+These objects conform to the regular [`hash.Hash`](https://pkg.go.dev/hash?tab=doc#Hash) interface
+and as such the normal Write/Reset/Sum functionality works as expected.
+
+As an example:
+```
+ // Create server
+ server := md5simd.NewServer()
+ defer server.Close()
+
+ // Create hashing object (conforming to hash.Hash)
+ md5Hash := server.NewHash()
+ defer md5Hash.Close()
+
+ // Write one (or more) blocks
+ md5Hash.Write(block)
+
+ // Return digest
+ digest := md5Hash.Sum([]byte{})
+```
+
+To keep performance both a [Server](https://pkg.go.dev/github.com/minio/md5-simd?tab=doc#Server)
+and individual [Hasher](https://pkg.go.dev/github.com/minio/md5-simd?tab=doc#Hasher) should
+be closed using the `Close()` function when no longer needed.
+
+A Hasher can efficiently be re-used by using [`Reset()`](https://pkg.go.dev/hash?tab=doc#Hash) functionality.
+
+In case your system does not support the instructions required it will fall back to using `crypto/md5` for hashing.
+
+## Limitations
+
+As explained above `md5-simd` does not speed up an individual MD5 hash sum computation,
+unless some hierarchical tree construct is used but this will result in different outcomes.
+Running a single hash on a server results in approximately half the throughput.
+
+Instead, it allows running multiple MD5 calculations in parallel on a single CPU core.
+This can be beneficial in e.g. multi-threaded server applications where many go-routines
+are dealing with many requests and multiple MD5 calculations can be packed/scheduled for parallel execution on a single core.
+
+This will result in a lower overall CPU usage as compared to using the standard `crypto/md5`
+functionality where each MD5 hash computation will consume a single thread (core).
+
+It is best to test and measure the overall CPU usage in a representative usage scenario in your application
+to get an overall understanding of the benefits of `md5-simd` as compared to `crypto/md5`, ideally under heavy CPU load.
+
+Also note that `md5-simd` is best meant to work with large objects,
+so if your application only hashes small objects of a few kilobytes
+you may be better of by using `crypto/md5`.
+
+## Performance
+
+For the best performance writes should be a multiple of 64 bytes, ideally a multiple of 32KB.
+To help with that a [`buffered := bufio.NewWriterSize(hasher, 32<<10)`](https://golang.org/pkg/bufio/#NewWriterSize)
+can be inserted if you are unsure of the sizes of the writes.
+Remember to [flush](https://golang.org/pkg/bufio/#Writer.Flush) `buffered` before reading the hash.
+
+A single 'server' can process 16 streams concurrently with 1 core (AVX-512) or 2 cores (AVX2).
+In situations where it is likely that more than 16 streams are fully loaded it may be beneficial
+to use multiple servers.
+
+The following chart compares the multi-core performance between `crypto/md5` vs the AVX2 vs the AVX512 code:
+
+![md5-performance-overview](chart/Multi-core-MD5-Aggregated-Hashing-Performance.png)
+
+Compared to `crypto/md5`, the AVX2 version is up to 4x faster:
+
+```
+$ benchcmp crypto-md5.txt avx2.txt
+benchmark old MB/s new MB/s speedup
+BenchmarkParallel/32KB-4 2229.22 7370.50 3.31x
+BenchmarkParallel/64KB-4 2233.61 8248.46 3.69x
+BenchmarkParallel/128KB-4 2235.43 8660.74 3.87x
+BenchmarkParallel/256KB-4 2236.39 8863.87 3.96x
+BenchmarkParallel/512KB-4 2238.05 8985.39 4.01x
+BenchmarkParallel/1MB-4 2233.56 9042.62 4.05x
+BenchmarkParallel/2MB-4 2224.11 9014.46 4.05x
+BenchmarkParallel/4MB-4 2199.78 8993.61 4.09x
+BenchmarkParallel/8MB-4 2182.48 8748.22 4.01x
+```
+
+Compared to `crypto/md5`, the AVX512 is up to 8x faster (for larger block sizes):
+
+```
+$ benchcmp crypto-md5.txt avx512.txt
+benchmark old MB/s new MB/s speedup
+BenchmarkParallel/32KB-4 2229.22 11605.78 5.21x
+BenchmarkParallel/64KB-4 2233.61 14329.65 6.42x
+BenchmarkParallel/128KB-4 2235.43 16166.39 7.23x
+BenchmarkParallel/256KB-4 2236.39 15570.09 6.96x
+BenchmarkParallel/512KB-4 2238.05 16705.83 7.46x
+BenchmarkParallel/1MB-4 2233.56 16941.95 7.59x
+BenchmarkParallel/2MB-4 2224.11 17136.01 7.70x
+BenchmarkParallel/4MB-4 2199.78 17218.61 7.83x
+BenchmarkParallel/8MB-4 2182.48 17252.88 7.91x
+```
+
+These measurements were performed on AWS EC2 instance of type `c5.xlarge` equipped with a Xeon Platinum 8124M CPU at 3.0 GHz.
+
+If only one or two inputs are available the scalar calculation method will be used for the
+optimal speed in these cases.
+
+## Operation
+
+To make operation as easy as possible there is a โ€œServerโ€ coordinating everything. The server keeps track of individual hash states and updates them as new data comes in. This can be visualized as follows:
+
+![server-architecture](chart/server-architecture.png)
+
+The data is sent to the server from each hash input in blocks of up to 32KB per round. In our testing we found this to be the block size that yielded the best results.
+
+Whenever there is data available the server will collect data for up to 16 hashes and process all 16 lanes in parallel. This means that if 16 hashes have data available all the lanes will be filled. However since that may not be the case, the server will fill less lanes and do a round anyway. Lanes can also be partially filled if less than 32KB of data is written.
+
+![server-lanes-example](chart/server-lanes-example.png)
+
+In this example 4 lanes are fully filled and 2 lanes are partially filled. In this case the black areas will simply be masked out from the results and ignored. This is also why calculating a single hash on a server will not result in any speedup and hash writes should be a multiple of 32KB for the best performance.
+
+For AVX512 all 16 calculations will be done on a single core, on AVX2 on 2 cores if there is data for more than 8 lanes.
+So for optimal usage there should be data available for all 16 hashes. It may be perfectly reasonable to use more than 16 concurrent hashes.
+
+
+## Design & Tech
+
+md5-simd has both an AVX2 (8-lane parallel), and an AVX512 (16-lane parallel version) algorithm to accelerate the computation with the following function definitions:
+```
+//go:noescape
+func block8(state *uint32, base uintptr, bufs *int32, cache *byte, n int)
+
+//go:noescape
+func block16(state *uint32, ptrs *int64, mask uint64, n int)
+```
+
+The AVX2 version is based on the [md5vec](https://github.com/igneous-systems/md5vec) repository and is essentially unchanged except for minor (cosmetic) changes.
+
+The AVX512 version is derived from the AVX2 version but adds some further optimizations and simplifications.
+
+### Caching in upper ZMM registers
+
+The AVX2 version passes in a `cache8` block of memory (about 0.5 KB) for temporary storage of intermediate results during `ROUND1` which are subsequently used during `ROUND2` through to `ROUND4`.
+
+Since AVX512 has double the amount of registers (32 ZMM registers as compared to 16 YMM registers), it is possible to use the upper 16 ZMM registers for keeping the intermediate states on the CPU. As such, there is no need to pass in a corresponding `cache16` into the AVX512 block function.
+
+### Direct loading using 64-bit pointers
+
+The AVX2 uses the `VPGATHERDD` instruction (for YMM) to do a parallel load of 8 lanes using (8 independent) 32-bit offets. Since there is no control over how the 8 slices that are passed into the (Golang) `blockMd5` function are laid out into memory, it is not possible to derive a "base" address and corresponding offsets (all within 32-bits) for all 8 slices.
+
+As such the AVX2 version uses an interim buffer to collect the byte slices to be hashed from all 8 inut slices and passed this buffer along with (fixed) 32-bit offsets into the assembly code.
+
+For the AVX512 version this interim buffer is not needed since the AVX512 code uses a pair of `VPGATHERQD` instructions to directly dereference 64-bit pointers (from a base register address that is initialized to zero).
+
+Note that two load (gather) instructions are needed because the AVX512 version processes 16-lanes in parallel, requiring 16 times 64-bit = 1024 bits in total to be loaded. A simple `VALIGND` and `VPORD` are subsequently used to merge the lower and upper halves together into a single ZMM register (that contains 16 lanes of 32-bit DWORDS).
+
+### Masking support
+
+Due to the fact that pointers are passed directly from the Golang slices, we need to protect against NULL pointers.
+For this a 16-bit mask is passed in the AVX512 assembly code which is used during the `VPGATHERQD` instructions to mask out lanes that could otherwise result in segment violations.
+
+### Minor optimizations
+
+The `roll` macro (three instructions on AVX2) is no longer needed for AVX512 and is replaced by a single `VPROLD` instruction.
+
+Also several logical operations from the various ROUNDS of the AVX2 version could be combined into a single instruction using ternary logic (with the `VPTERMLOGD` instruction), resulting in a further simplification and speed-up.
+
+## Low level block function performance
+
+The benchmark below shows the (single thread) maximum performance of the `block()` function for AVX2 (having 8 lanes) and AVX512 (having 16 lanes). Also the baseline single-core performance from the standard `crypto/md5` package is shown for comparison.
+
+```
+BenchmarkCryptoMd5-4 687.66 MB/s 0 B/op 0 allocs/op
+BenchmarkBlock8-4 4144.80 MB/s 0 B/op 0 allocs/op
+BenchmarkBlock16-4 8228.88 MB/s 0 B/op 0 allocs/op
+```
+
+## License
+
+`md5-simd` is released under the Apache License v2.0. You can find the complete text in the file LICENSE.
+
+## Contributing
+
+Contributions are welcome, please send PRs for any enhancements. \ No newline at end of file
diff --git a/vendor/github.com/minio/md5-simd/block16_amd64.s b/vendor/github.com/minio/md5-simd/block16_amd64.s
new file mode 100644
index 00000000..be0a43a3
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/block16_amd64.s
@@ -0,0 +1,228 @@
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+//+build !noasm,!appengine,gc
+
+// This is the AVX512 implementation of the MD5 block function (16-way parallel)
+
+#define prep(index) \
+ KMOVQ kmask, ktmp \
+ VPGATHERDD index*4(base)(ptrs*1), ktmp, mem
+
+#define ROUND1(a, b, c, d, index, const, shift) \
+ VPXORQ c, tmp, tmp \
+ VPADDD 64*const(consts), a, a \
+ VPADDD mem, a, a \
+ VPTERNLOGD $0x6C, b, d, tmp \
+ prep(index) \
+ VPADDD tmp, a, a \
+ VPROLD $shift, a, a \
+ VMOVAPD c, tmp \
+ VPADDD b, a, a
+
+#define ROUND1noload(a, b, c, d, const, shift) \
+ VPXORQ c, tmp, tmp \
+ VPADDD 64*const(consts), a, a \
+ VPADDD mem, a, a \
+ VPTERNLOGD $0x6C, b, d, tmp \
+ VPADDD tmp, a, a \
+ VPROLD $shift, a, a \
+ VMOVAPD c, tmp \
+ VPADDD b, a, a
+
+#define ROUND2(a, b, c, d, zreg, const, shift) \
+ VPADDD 64*const(consts), a, a \
+ VPADDD zreg, a, a \
+ VANDNPD c, tmp, tmp \
+ VPTERNLOGD $0xEC, b, tmp, tmp2 \
+ VMOVAPD c, tmp \
+ VPADDD tmp2, a, a \
+ VMOVAPD c, tmp2 \
+ VPROLD $shift, a, a \
+ VPADDD b, a, a
+
+#define ROUND3(a, b, c, d, zreg, const, shift) \
+ VPADDD 64*const(consts), a, a \
+ VPADDD zreg, a, a \
+ VPTERNLOGD $0x96, b, d, tmp \
+ VPADDD tmp, a, a \
+ VPROLD $shift, a, a \
+ VMOVAPD b, tmp \
+ VPADDD b, a, a
+
+#define ROUND4(a, b, c, d, zreg, const, shift) \
+ VPADDD 64*const(consts), a, a \
+ VPADDD zreg, a, a \
+ VPTERNLOGD $0x36, b, c, tmp \
+ VPADDD tmp, a, a \
+ VPROLD $shift, a, a \
+ VPXORQ c, ones, tmp \
+ VPADDD b, a, a
+
+TEXT ยทblock16(SB), 4, $0-40
+
+ MOVQ state+0(FP), BX
+ MOVQ base+8(FP), SI
+ MOVQ ptrs+16(FP), AX
+ KMOVQ mask+24(FP), K1
+ MOVQ n+32(FP), DX
+ MOVQ ยทavx512md5consts+0(SB), DI
+
+#define a Z0
+#define b Z1
+#define c Z2
+#define d Z3
+
+#define sa Z4
+#define sb Z5
+#define sc Z6
+#define sd Z7
+
+#define tmp Z8
+#define tmp2 Z9
+#define ptrs Z10
+#define ones Z12
+#define mem Z15
+
+#define kmask K1
+#define ktmp K3
+
+// ----------------------------------------------------------
+// Registers Z16 through to Z31 are used for caching purposes
+// ----------------------------------------------------------
+
+#define dig BX
+#define count DX
+#define base SI
+#define consts DI
+
+ // load digest into state registers
+ VMOVUPD (dig), a
+ VMOVUPD 0x40(dig), b
+ VMOVUPD 0x80(dig), c
+ VMOVUPD 0xc0(dig), d
+
+ // load source pointers
+ VMOVUPD 0x00(AX), ptrs
+
+ MOVQ $-1, AX
+ VPBROADCASTQ AX, ones
+
+loop:
+ VMOVAPD a, sa
+ VMOVAPD b, sb
+ VMOVAPD c, sc
+ VMOVAPD d, sd
+
+ prep(0)
+ VMOVAPD d, tmp
+ VMOVAPD mem, Z16
+
+ ROUND1(a,b,c,d, 1,0x00, 7)
+ VMOVAPD mem, Z17
+ ROUND1(d,a,b,c, 2,0x01,12)
+ VMOVAPD mem, Z18
+ ROUND1(c,d,a,b, 3,0x02,17)
+ VMOVAPD mem, Z19
+ ROUND1(b,c,d,a, 4,0x03,22)
+ VMOVAPD mem, Z20
+ ROUND1(a,b,c,d, 5,0x04, 7)
+ VMOVAPD mem, Z21
+ ROUND1(d,a,b,c, 6,0x05,12)
+ VMOVAPD mem, Z22
+ ROUND1(c,d,a,b, 7,0x06,17)
+ VMOVAPD mem, Z23
+ ROUND1(b,c,d,a, 8,0x07,22)
+ VMOVAPD mem, Z24
+ ROUND1(a,b,c,d, 9,0x08, 7)
+ VMOVAPD mem, Z25
+ ROUND1(d,a,b,c,10,0x09,12)
+ VMOVAPD mem, Z26
+ ROUND1(c,d,a,b,11,0x0a,17)
+ VMOVAPD mem, Z27
+ ROUND1(b,c,d,a,12,0x0b,22)
+ VMOVAPD mem, Z28
+ ROUND1(a,b,c,d,13,0x0c, 7)
+ VMOVAPD mem, Z29
+ ROUND1(d,a,b,c,14,0x0d,12)
+ VMOVAPD mem, Z30
+ ROUND1(c,d,a,b,15,0x0e,17)
+ VMOVAPD mem, Z31
+
+ ROUND1noload(b,c,d,a, 0x0f,22)
+
+ VMOVAPD d, tmp
+ VMOVAPD d, tmp2
+
+ ROUND2(a,b,c,d, Z17,0x10, 5)
+ ROUND2(d,a,b,c, Z22,0x11, 9)
+ ROUND2(c,d,a,b, Z27,0x12,14)
+ ROUND2(b,c,d,a, Z16,0x13,20)
+ ROUND2(a,b,c,d, Z21,0x14, 5)
+ ROUND2(d,a,b,c, Z26,0x15, 9)
+ ROUND2(c,d,a,b, Z31,0x16,14)
+ ROUND2(b,c,d,a, Z20,0x17,20)
+ ROUND2(a,b,c,d, Z25,0x18, 5)
+ ROUND2(d,a,b,c, Z30,0x19, 9)
+ ROUND2(c,d,a,b, Z19,0x1a,14)
+ ROUND2(b,c,d,a, Z24,0x1b,20)
+ ROUND2(a,b,c,d, Z29,0x1c, 5)
+ ROUND2(d,a,b,c, Z18,0x1d, 9)
+ ROUND2(c,d,a,b, Z23,0x1e,14)
+ ROUND2(b,c,d,a, Z28,0x1f,20)
+
+ VMOVAPD c, tmp
+
+ ROUND3(a,b,c,d, Z21,0x20, 4)
+ ROUND3(d,a,b,c, Z24,0x21,11)
+ ROUND3(c,d,a,b, Z27,0x22,16)
+ ROUND3(b,c,d,a, Z30,0x23,23)
+ ROUND3(a,b,c,d, Z17,0x24, 4)
+ ROUND3(d,a,b,c, Z20,0x25,11)
+ ROUND3(c,d,a,b, Z23,0x26,16)
+ ROUND3(b,c,d,a, Z26,0x27,23)
+ ROUND3(a,b,c,d, Z29,0x28, 4)
+ ROUND3(d,a,b,c, Z16,0x29,11)
+ ROUND3(c,d,a,b, Z19,0x2a,16)
+ ROUND3(b,c,d,a, Z22,0x2b,23)
+ ROUND3(a,b,c,d, Z25,0x2c, 4)
+ ROUND3(d,a,b,c, Z28,0x2d,11)
+ ROUND3(c,d,a,b, Z31,0x2e,16)
+ ROUND3(b,c,d,a, Z18,0x2f,23)
+
+ VPXORQ d, ones, tmp
+
+ ROUND4(a,b,c,d, Z16,0x30, 6)
+ ROUND4(d,a,b,c, Z23,0x31,10)
+ ROUND4(c,d,a,b, Z30,0x32,15)
+ ROUND4(b,c,d,a, Z21,0x33,21)
+ ROUND4(a,b,c,d, Z28,0x34, 6)
+ ROUND4(d,a,b,c, Z19,0x35,10)
+ ROUND4(c,d,a,b, Z26,0x36,15)
+ ROUND4(b,c,d,a, Z17,0x37,21)
+ ROUND4(a,b,c,d, Z24,0x38, 6)
+ ROUND4(d,a,b,c, Z31,0x39,10)
+ ROUND4(c,d,a,b, Z22,0x3a,15)
+ ROUND4(b,c,d,a, Z29,0x3b,21)
+ ROUND4(a,b,c,d, Z20,0x3c, 6)
+ ROUND4(d,a,b,c, Z27,0x3d,10)
+ ROUND4(c,d,a,b, Z18,0x3e,15)
+ ROUND4(b,c,d,a, Z25,0x3f,21)
+
+ VPADDD sa, a, a
+ VPADDD sb, b, b
+ VPADDD sc, c, c
+ VPADDD sd, d, d
+
+ LEAQ 64(base), base
+ SUBQ $64, count
+ JNE loop
+
+ VMOVUPD a, (dig)
+ VMOVUPD b, 0x40(dig)
+ VMOVUPD c, 0x80(dig)
+ VMOVUPD d, 0xc0(dig)
+
+ VZEROUPPER
+ RET
diff --git a/vendor/github.com/minio/md5-simd/block8_amd64.s b/vendor/github.com/minio/md5-simd/block8_amd64.s
new file mode 100644
index 00000000..f57db17a
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/block8_amd64.s
@@ -0,0 +1,281 @@
+//+build !noasm,!appengine,gc
+
+// Copyright (c) 2018 Igneous Systems
+// MIT License
+//
+// 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.
+
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+// This is the AVX2 implementation of the MD5 block function (8-way parallel)
+
+// block8(state *uint64, base uintptr, bufs *int32, cache *byte, n int)
+TEXT ยทblock8(SB), 4, $0-40
+ MOVQ state+0(FP), BX
+ MOVQ base+8(FP), SI
+ MOVQ bufs+16(FP), AX
+ MOVQ cache+24(FP), CX
+ MOVQ n+32(FP), DX
+ MOVQ ยทavx256md5consts+0(SB), DI
+
+ // Align cache (which is stack allocated by the compiler)
+ // to a 256 bit boundary (ymm register alignment)
+ // The cache8 type is deliberately oversized to permit this.
+ ADDQ $31, CX
+ ANDB $-32, CL
+
+#define a Y0
+#define b Y1
+#define c Y2
+#define d Y3
+
+#define sa Y4
+#define sb Y5
+#define sc Y6
+#define sd Y7
+
+#define tmp Y8
+#define tmp2 Y9
+
+#define mask Y10
+#define off Y11
+
+#define ones Y12
+
+#define rtmp1 Y13
+#define rtmp2 Y14
+
+#define mem Y15
+
+#define dig BX
+#define cache CX
+#define count DX
+#define base SI
+#define consts DI
+
+#define prepmask \
+ VPXOR mask, mask, mask \
+ VPCMPGTD mask, off, mask
+
+#define prep(index) \
+ VMOVAPD mask, rtmp2 \
+ VPGATHERDD rtmp2, index*4(base)(off*1), mem
+
+#define load(index) \
+ VMOVAPD index*32(cache), mem
+
+#define store(index) \
+ VMOVAPD mem, index*32(cache)
+
+#define roll(shift, a) \
+ VPSLLD $shift, a, rtmp1 \
+ VPSRLD $32-shift, a, a \
+ VPOR rtmp1, a, a
+
+#define ROUND1(a, b, c, d, index, const, shift) \
+ VPXOR c, tmp, tmp \
+ VPADDD 32*const(consts), a, a \
+ VPADDD mem, a, a \
+ VPAND b, tmp, tmp \
+ VPXOR d, tmp, tmp \
+ prep(index) \
+ VPADDD tmp, a, a \
+ roll(shift,a) \
+ VMOVAPD c, tmp \
+ VPADDD b, a, a
+
+#define ROUND1load(a, b, c, d, index, const, shift) \
+ VXORPD c, tmp, tmp \
+ VPADDD 32*const(consts), a, a \
+ VPADDD mem, a, a \
+ VPAND b, tmp, tmp \
+ VPXOR d, tmp, tmp \
+ load(index) \
+ VPADDD tmp, a, a \
+ roll(shift,a) \
+ VMOVAPD c, tmp \
+ VPADDD b, a, a
+
+#define ROUND2(a, b, c, d, index, const, shift) \
+ VPADDD 32*const(consts), a, a \
+ VPADDD mem, a, a \
+ VPAND b, tmp2, tmp2 \
+ VANDNPD c, tmp, tmp \
+ load(index) \
+ VPOR tmp, tmp2, tmp2 \
+ VMOVAPD c, tmp \
+ VPADDD tmp2, a, a \
+ VMOVAPD c, tmp2 \
+ roll(shift,a) \
+ VPADDD b, a, a
+
+#define ROUND3(a, b, c, d, index, const, shift) \
+ VPADDD 32*const(consts), a, a \
+ VPADDD mem, a, a \
+ load(index) \
+ VPXOR d, tmp, tmp \
+ VPXOR b, tmp, tmp \
+ VPADDD tmp, a, a \
+ roll(shift,a) \
+ VMOVAPD b, tmp \
+ VPADDD b, a, a
+
+#define ROUND4(a, b, c, d, index, const, shift) \
+ VPADDD 32*const(consts), a, a \
+ VPADDD mem, a, a \
+ VPOR b, tmp, tmp \
+ VPXOR c, tmp, tmp \
+ VPADDD tmp, a, a \
+ load(index) \
+ roll(shift,a) \
+ VPXOR c, ones, tmp \
+ VPADDD b, a, a
+
+ // load digest into state registers
+ VMOVUPD (dig), a
+ VMOVUPD 32(dig), b
+ VMOVUPD 64(dig), c
+ VMOVUPD 96(dig), d
+
+ // load source buffer offsets
+ VMOVUPD (AX), off
+
+ prepmask
+ VPCMPEQD ones, ones, ones
+
+loop:
+ VMOVAPD a, sa
+ VMOVAPD b, sb
+ VMOVAPD c, sc
+ VMOVAPD d, sd
+
+ prep(0)
+ VMOVAPD d, tmp
+ store(0)
+
+ ROUND1(a,b,c,d, 1,0x00, 7)
+ store(1)
+ ROUND1(d,a,b,c, 2,0x01,12)
+ store(2)
+ ROUND1(c,d,a,b, 3,0x02,17)
+ store(3)
+ ROUND1(b,c,d,a, 4,0x03,22)
+ store(4)
+ ROUND1(a,b,c,d, 5,0x04, 7)
+ store(5)
+ ROUND1(d,a,b,c, 6,0x05,12)
+ store(6)
+ ROUND1(c,d,a,b, 7,0x06,17)
+ store(7)
+ ROUND1(b,c,d,a, 8,0x07,22)
+ store(8)
+ ROUND1(a,b,c,d, 9,0x08, 7)
+ store(9)
+ ROUND1(d,a,b,c,10,0x09,12)
+ store(10)
+ ROUND1(c,d,a,b,11,0x0a,17)
+ store(11)
+ ROUND1(b,c,d,a,12,0x0b,22)
+ store(12)
+ ROUND1(a,b,c,d,13,0x0c, 7)
+ store(13)
+ ROUND1(d,a,b,c,14,0x0d,12)
+ store(14)
+ ROUND1(c,d,a,b,15,0x0e,17)
+ store(15)
+ ROUND1load(b,c,d,a, 1,0x0f,22)
+
+ VMOVAPD d, tmp
+ VMOVAPD d, tmp2
+
+ ROUND2(a,b,c,d, 6,0x10, 5)
+ ROUND2(d,a,b,c,11,0x11, 9)
+ ROUND2(c,d,a,b, 0,0x12,14)
+ ROUND2(b,c,d,a, 5,0x13,20)
+ ROUND2(a,b,c,d,10,0x14, 5)
+ ROUND2(d,a,b,c,15,0x15, 9)
+ ROUND2(c,d,a,b, 4,0x16,14)
+ ROUND2(b,c,d,a, 9,0x17,20)
+ ROUND2(a,b,c,d,14,0x18, 5)
+ ROUND2(d,a,b,c, 3,0x19, 9)
+ ROUND2(c,d,a,b, 8,0x1a,14)
+ ROUND2(b,c,d,a,13,0x1b,20)
+ ROUND2(a,b,c,d, 2,0x1c, 5)
+ ROUND2(d,a,b,c, 7,0x1d, 9)
+ ROUND2(c,d,a,b,12,0x1e,14)
+ ROUND2(b,c,d,a, 0,0x1f,20)
+
+ load(5)
+ VMOVAPD c, tmp
+
+ ROUND3(a,b,c,d, 8,0x20, 4)
+ ROUND3(d,a,b,c,11,0x21,11)
+ ROUND3(c,d,a,b,14,0x22,16)
+ ROUND3(b,c,d,a, 1,0x23,23)
+ ROUND3(a,b,c,d, 4,0x24, 4)
+ ROUND3(d,a,b,c, 7,0x25,11)
+ ROUND3(c,d,a,b,10,0x26,16)
+ ROUND3(b,c,d,a,13,0x27,23)
+ ROUND3(a,b,c,d, 0,0x28, 4)
+ ROUND3(d,a,b,c, 3,0x29,11)
+ ROUND3(c,d,a,b, 6,0x2a,16)
+ ROUND3(b,c,d,a, 9,0x2b,23)
+ ROUND3(a,b,c,d,12,0x2c, 4)
+ ROUND3(d,a,b,c,15,0x2d,11)
+ ROUND3(c,d,a,b, 2,0x2e,16)
+ ROUND3(b,c,d,a, 0,0x2f,23)
+
+ load(0)
+ VPXOR d, ones, tmp
+
+ ROUND4(a,b,c,d, 7,0x30, 6)
+ ROUND4(d,a,b,c,14,0x31,10)
+ ROUND4(c,d,a,b, 5,0x32,15)
+ ROUND4(b,c,d,a,12,0x33,21)
+ ROUND4(a,b,c,d, 3,0x34, 6)
+ ROUND4(d,a,b,c,10,0x35,10)
+ ROUND4(c,d,a,b, 1,0x36,15)
+ ROUND4(b,c,d,a, 8,0x37,21)
+ ROUND4(a,b,c,d,15,0x38, 6)
+ ROUND4(d,a,b,c, 6,0x39,10)
+ ROUND4(c,d,a,b,13,0x3a,15)
+ ROUND4(b,c,d,a, 4,0x3b,21)
+ ROUND4(a,b,c,d,11,0x3c, 6)
+ ROUND4(d,a,b,c, 2,0x3d,10)
+ ROUND4(c,d,a,b, 9,0x3e,15)
+ ROUND4(b,c,d,a, 0,0x3f,21)
+
+ VPADDD sa, a, a
+ VPADDD sb, b, b
+ VPADDD sc, c, c
+ VPADDD sd, d, d
+
+ LEAQ 64(base), base
+ SUBQ $64, count
+ JNE loop
+
+ VMOVUPD a, (dig)
+ VMOVUPD b, 32(dig)
+ VMOVUPD c, 64(dig)
+ VMOVUPD d, 96(dig)
+
+ VZEROUPPER
+ RET
diff --git a/vendor/github.com/minio/md5-simd/block_amd64.go b/vendor/github.com/minio/md5-simd/block_amd64.go
new file mode 100644
index 00000000..16edda26
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/block_amd64.go
@@ -0,0 +1,210 @@
+//+build !noasm,!appengine,gc
+
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+package md5simd
+
+import (
+ "fmt"
+ "math"
+ "unsafe"
+
+ "github.com/klauspost/cpuid/v2"
+)
+
+var hasAVX512 bool
+
+func init() {
+ // VANDNPD requires AVX512DQ. Technically it could be VPTERNLOGQ which is AVX512F.
+ hasAVX512 = cpuid.CPU.Supports(cpuid.AVX512F, cpuid.AVX512DQ)
+}
+
+//go:noescape
+func block8(state *uint32, base uintptr, bufs *int32, cache *byte, n int)
+
+//go:noescape
+func block16(state *uint32, base uintptr, ptrs *int32, mask uint64, n int)
+
+// 8-way 4x uint32 digests in 4 ymm registers
+// (ymm0, ymm1, ymm2, ymm3)
+type digest8 struct {
+ v0, v1, v2, v3 [8]uint32
+}
+
+// Stack cache for 8x64 byte md5.BlockSize bytes.
+// Must be 32-byte aligned, so allocate 512+32 and
+// align upwards at runtime.
+type cache8 [512 + 32]byte
+
+// MD5 magic numbers for one lane of hashing; inflated
+// 8x below at init time.
+var md5consts = [64]uint32{
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+ 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+ 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+ 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
+}
+
+// inflate the consts 8-way for 8x md5 (256 bit ymm registers)
+var avx256md5consts = func(c []uint32) []uint32 {
+ inf := make([]uint32, 8*len(c))
+ for i := range c {
+ for j := 0; j < 8; j++ {
+ inf[(i*8)+j] = c[i]
+ }
+ }
+ return inf
+}(md5consts[:])
+
+// 16-way 4x uint32 digests in 4 zmm registers
+type digest16 struct {
+ v0, v1, v2, v3 [16]uint32
+}
+
+// inflate the consts 16-way for 16x md5 (512 bit zmm registers)
+var avx512md5consts = func(c []uint32) []uint32 {
+ inf := make([]uint32, 16*len(c))
+ for i := range c {
+ for j := 0; j < 16; j++ {
+ inf[(i*16)+j] = c[i]
+ }
+ }
+ return inf
+}(md5consts[:])
+
+// Interface function to assembly code
+func (s *md5Server) blockMd5_x16(d *digest16, input [16][]byte, half bool) {
+ if hasAVX512 {
+ blockMd5_avx512(d, input, s.allBufs, &s.maskRounds16)
+ return
+ }
+
+ // Preparing data using copy is slower since copies aren't inlined.
+
+ // Calculate on this goroutine
+ if half {
+ for i := range s.i8[0][:] {
+ s.i8[0][i] = input[i]
+ }
+ for i := range s.d8a.v0[:] {
+ s.d8a.v0[i], s.d8a.v1[i], s.d8a.v2[i], s.d8a.v3[i] = d.v0[i], d.v1[i], d.v2[i], d.v3[i]
+ }
+ blockMd5_avx2(&s.d8a, s.i8[0], s.allBufs, &s.maskRounds8a)
+ for i := range s.d8a.v0[:] {
+ d.v0[i], d.v1[i], d.v2[i], d.v3[i] = s.d8a.v0[i], s.d8a.v1[i], s.d8a.v2[i], s.d8a.v3[i]
+ }
+ return
+ }
+
+ for i := range s.i8[0][:] {
+ s.i8[0][i], s.i8[1][i] = input[i], input[8+i]
+ }
+
+ for i := range s.d8a.v0[:] {
+ j := (i + 8) & 15
+ s.d8a.v0[i], s.d8a.v1[i], s.d8a.v2[i], s.d8a.v3[i] = d.v0[i], d.v1[i], d.v2[i], d.v3[i]
+ s.d8b.v0[i], s.d8b.v1[i], s.d8b.v2[i], s.d8b.v3[i] = d.v0[j], d.v1[j], d.v2[j], d.v3[j]
+ }
+
+ // Benchmarks appears to be slightly faster when spinning up 2 goroutines instead
+ // of using the current for one of the blocks.
+ s.wg.Add(2)
+ go func() { blockMd5_avx2(&s.d8a, s.i8[0], s.allBufs, &s.maskRounds8a); s.wg.Done() }()
+ go func() { blockMd5_avx2(&s.d8b, s.i8[1], s.allBufs, &s.maskRounds8b); s.wg.Done() }()
+ s.wg.Wait()
+ for i := range s.d8a.v0[:] {
+ d.v0[i], d.v1[i], d.v2[i], d.v3[i] = s.d8a.v0[i], s.d8a.v1[i], s.d8a.v2[i], s.d8a.v3[i]
+ }
+ for i := range s.d8b.v0[:] {
+ j := (i + 8) & 15
+ d.v0[j], d.v1[j], d.v2[j], d.v3[j] = s.d8b.v0[i], s.d8b.v1[i], s.d8b.v2[i], s.d8b.v3[i]
+ }
+}
+
+// Interface function to AVX512 assembly code
+func blockMd5_avx512(s *digest16, input [16][]byte, base []byte, maskRounds *[16]maskRounds) {
+ baseMin := uint64(uintptr(unsafe.Pointer(&(base[0]))))
+ ptrs := [16]int32{}
+
+ for i := range ptrs {
+ if len(input[i]) > 0 {
+ if len(input[i]) > internalBlockSize {
+ panic(fmt.Sprintf("Sanity check fails for lane %d: maximum input length cannot exceed internalBlockSize", i))
+ }
+
+ off := uint64(uintptr(unsafe.Pointer(&(input[i][0])))) - baseMin
+ if off > math.MaxUint32 {
+ panic(fmt.Sprintf("invalid buffer sent with offset %x", off))
+ }
+ ptrs[i] = int32(off)
+ }
+ }
+
+ sdup := *s // create copy of initial states to receive intermediate updates
+
+ rounds := generateMaskAndRounds16(input, maskRounds)
+
+ for r := 0; r < rounds; r++ {
+ m := maskRounds[r]
+
+ block16(&sdup.v0[0], uintptr(baseMin), &ptrs[0], m.mask, int(64*m.rounds))
+
+ for j := 0; j < len(ptrs); j++ {
+ ptrs[j] += int32(64 * m.rounds) // update pointers for next round
+ if m.mask&(1<<j) != 0 { // update digest if still masked as active
+ (*s).v0[j], (*s).v1[j], (*s).v2[j], (*s).v3[j] = sdup.v0[j], sdup.v1[j], sdup.v2[j], sdup.v3[j]
+ }
+ }
+ }
+}
+
+// Interface function to AVX2 assembly code
+func blockMd5_avx2(s *digest8, input [8][]byte, base []byte, maskRounds *[8]maskRounds) {
+ baseMin := uint64(uintptr(unsafe.Pointer(&(base[0])))) - 4
+ ptrs := [8]int32{}
+
+ for i := range ptrs {
+ if len(input[i]) > 0 {
+ if len(input[i]) > internalBlockSize {
+ panic(fmt.Sprintf("Sanity check fails for lane %d: maximum input length cannot exceed internalBlockSize", i))
+ }
+
+ off := uint64(uintptr(unsafe.Pointer(&(input[i][0])))) - baseMin
+ if off > math.MaxUint32 {
+ panic(fmt.Sprintf("invalid buffer sent with offset %x", off))
+ }
+ ptrs[i] = int32(off)
+ }
+ }
+
+ sdup := *s // create copy of initial states to receive intermediate updates
+
+ rounds := generateMaskAndRounds8(input, maskRounds)
+
+ for r := 0; r < rounds; r++ {
+ m := maskRounds[r]
+ var cache cache8 // stack storage for block8 tmp state
+ block8(&sdup.v0[0], uintptr(baseMin), &ptrs[0], &cache[0], int(64*m.rounds))
+
+ for j := 0; j < len(ptrs); j++ {
+ ptrs[j] += int32(64 * m.rounds) // update pointers for next round
+ if m.mask&(1<<j) != 0 { // update digest if still masked as active
+ (*s).v0[j], (*s).v1[j], (*s).v2[j], (*s).v3[j] = sdup.v0[j], sdup.v1[j], sdup.v2[j], sdup.v3[j]
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/minio/md5-simd/go.mod b/vendor/github.com/minio/md5-simd/go.mod
new file mode 100644
index 00000000..e7eda9fa
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/go.mod
@@ -0,0 +1,5 @@
+module github.com/minio/md5-simd
+
+go 1.14
+
+require github.com/klauspost/cpuid/v2 v2.0.1
diff --git a/vendor/github.com/minio/md5-simd/go.sum b/vendor/github.com/minio/md5-simd/go.sum
new file mode 100644
index 00000000..392eb82e
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/go.sum
@@ -0,0 +1,2 @@
+github.com/klauspost/cpuid/v2 v2.0.1 h1:lb04bBEJoAoV48eHs4Eq0UyhmJCkRSdIjQ3uS8WJRM4=
+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
diff --git a/vendor/github.com/minio/md5-simd/md5-digest_amd64.go b/vendor/github.com/minio/md5-simd/md5-digest_amd64.go
new file mode 100644
index 00000000..5ea23a49
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5-digest_amd64.go
@@ -0,0 +1,188 @@
+//+build !noasm,!appengine,gc
+
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+package md5simd
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "sync"
+ "sync/atomic"
+)
+
+// md5Digest - Type for computing MD5 using either AVX2 or AVX512
+type md5Digest struct {
+ uid uint64
+ blocksCh chan blockInput
+ cycleServer chan uint64
+ x [BlockSize]byte
+ nx int
+ len uint64
+ buffers <-chan []byte
+}
+
+// NewHash - initialize instance for Md5 implementation.
+func (s *md5Server) NewHash() Hasher {
+ uid := atomic.AddUint64(&s.uidCounter, 1)
+ blockCh := make(chan blockInput, buffersPerLane)
+ s.newInput <- newClient{
+ uid: uid,
+ input: blockCh,
+ }
+ return &md5Digest{
+ uid: uid,
+ buffers: s.buffers,
+ blocksCh: blockCh,
+ cycleServer: s.cycle,
+ }
+}
+
+// Size - Return size of checksum
+func (d *md5Digest) Size() int { return Size }
+
+// BlockSize - Return blocksize of checksum
+func (d md5Digest) BlockSize() int { return BlockSize }
+
+func (d *md5Digest) Reset() {
+ if d.blocksCh == nil {
+ panic("reset after close")
+ }
+ d.nx = 0
+ d.len = 0
+ d.sendBlock(blockInput{uid: d.uid, reset: true}, false)
+}
+
+// write to digest
+func (d *md5Digest) Write(p []byte) (nn int, err error) {
+ if d.blocksCh == nil {
+ return 0, errors.New("md5Digest closed")
+ }
+
+ // break input into chunks of maximum internalBlockSize size
+ for {
+ l := len(p)
+ if l > internalBlockSize {
+ l = internalBlockSize
+ }
+ nnn, err := d.write(p[:l])
+ if err != nil {
+ return nn, err
+ }
+ nn += nnn
+ p = p[l:]
+
+ if len(p) == 0 {
+ break
+ }
+
+ }
+ return
+}
+
+func (d *md5Digest) write(p []byte) (nn int, err error) {
+
+ nn = len(p)
+ d.len += uint64(nn)
+ if d.nx > 0 {
+ n := copy(d.x[d.nx:], p)
+ d.nx += n
+ if d.nx == BlockSize {
+ // Create a copy of the overflow buffer in order to send it async over the channel
+ // (since we will modify the overflow buffer down below with any access beyond multiples of 64)
+ tmp := <-d.buffers
+ tmp = tmp[:BlockSize]
+ copy(tmp, d.x[:])
+ d.sendBlock(blockInput{uid: d.uid, msg: tmp}, len(p)-n < BlockSize)
+ d.nx = 0
+ }
+ p = p[n:]
+ }
+ if len(p) >= BlockSize {
+ n := len(p) &^ (BlockSize - 1)
+ buf := <-d.buffers
+ buf = buf[:n]
+ copy(buf, p)
+ d.sendBlock(blockInput{uid: d.uid, msg: buf}, len(p)-n < BlockSize)
+ p = p[n:]
+ }
+ if len(p) > 0 {
+ d.nx = copy(d.x[:], p)
+ }
+ return
+}
+
+func (d *md5Digest) Close() {
+ if d.blocksCh != nil {
+ close(d.blocksCh)
+ d.blocksCh = nil
+ }
+}
+
+var sumChPool sync.Pool
+
+func init() {
+ sumChPool.New = func() interface{} {
+ return make(chan sumResult, 1)
+ }
+}
+
+// Sum - Return MD5 sum in bytes
+func (d *md5Digest) Sum(in []byte) (result []byte) {
+ if d.blocksCh == nil {
+ panic("sum after close")
+ }
+
+ trail := <-d.buffers
+ trail = append(trail[:0], d.x[:d.nx]...)
+
+ length := d.len
+ // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
+ var tmp [64]byte
+ tmp[0] = 0x80
+ if length%64 < 56 {
+ trail = append(trail, tmp[0:56-length%64]...)
+ } else {
+ trail = append(trail, tmp[0:64+56-length%64]...)
+ }
+
+ // Length in bits.
+ length <<= 3
+ binary.LittleEndian.PutUint64(tmp[:], length) // append length in bits
+
+ trail = append(trail, tmp[0:8]...)
+ if len(trail)%BlockSize != 0 {
+ panic(fmt.Errorf("internal error: sum block was not aligned. len=%d, nx=%d", len(trail), d.nx))
+ }
+ sumCh := sumChPool.Get().(chan sumResult)
+ d.sendBlock(blockInput{uid: d.uid, msg: trail, sumCh: sumCh}, true)
+
+ sum := <-sumCh
+ sumChPool.Put(sumCh)
+
+ return append(in, sum.digest[:]...)
+}
+
+// sendBlock will send a block for processing.
+// If cycle is true we will block on cycle, otherwise we will only block
+// if the block channel is full.
+func (d *md5Digest) sendBlock(bi blockInput, cycle bool) {
+ if cycle {
+ select {
+ case d.blocksCh <- bi:
+ d.cycleServer <- d.uid
+ }
+ return
+ }
+ // Only block on cycle if we filled the buffer
+ select {
+ case d.blocksCh <- bi:
+ return
+ default:
+ d.cycleServer <- d.uid
+ d.blocksCh <- bi
+ }
+}
diff --git a/vendor/github.com/minio/md5-simd/md5-server_amd64.go b/vendor/github.com/minio/md5-simd/md5-server_amd64.go
new file mode 100644
index 00000000..94f741c5
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5-server_amd64.go
@@ -0,0 +1,397 @@
+//+build !noasm,!appengine,gc
+
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+package md5simd
+
+import (
+ "encoding/binary"
+ "fmt"
+ "runtime"
+ "sync"
+
+ "github.com/klauspost/cpuid/v2"
+)
+
+// MD5 initialization constants
+const (
+ // Lanes is the number of concurrently calculated hashes.
+ Lanes = 16
+
+ init0 = 0x67452301
+ init1 = 0xefcdab89
+ init2 = 0x98badcfe
+ init3 = 0x10325476
+
+ // Use scalar routine when below this many lanes
+ useScalarBelow = 3
+)
+
+// md5ServerUID - Does not start at 0 but next multiple of 16 so as to be able to
+// differentiate with default initialisation value of 0
+const md5ServerUID = Lanes
+
+const buffersPerLane = 3
+
+// Message to send across input channel
+type blockInput struct {
+ uid uint64
+ msg []byte
+ sumCh chan sumResult
+ reset bool
+}
+
+type sumResult struct {
+ digest [Size]byte
+}
+
+type lanesInfo [Lanes]blockInput
+
+// md5Server - Type to implement parallel handling of MD5 invocations
+type md5Server struct {
+ uidCounter uint64
+ cycle chan uint64 // client with uid has update.
+ newInput chan newClient // Add new client.
+ digests map[uint64][Size]byte // Map of uids to (interim) digest results
+ maskRounds16 [16]maskRounds // Pre-allocated static array for max 16 rounds
+ maskRounds8a [8]maskRounds // Pre-allocated static array for max 8 rounds (1st AVX2 core)
+ maskRounds8b [8]maskRounds // Pre-allocated static array for max 8 rounds (2nd AVX2 core)
+ allBufs []byte // Preallocated buffer.
+ buffers chan []byte // Preallocated buffers, sliced from allBufs.
+
+ i8 [2][8][]byte // avx2 temporary vars
+ d8a, d8b digest8
+ wg sync.WaitGroup
+}
+
+// NewServer - Create new object for parallel processing handling
+func NewServer() Server {
+ if !cpuid.CPU.Supports(cpuid.AVX2) {
+ return &fallbackServer{}
+ }
+ md5srv := &md5Server{}
+ md5srv.digests = make(map[uint64][Size]byte)
+ md5srv.newInput = make(chan newClient, Lanes)
+ md5srv.cycle = make(chan uint64, Lanes*10)
+ md5srv.uidCounter = md5ServerUID - 1
+ md5srv.allBufs = make([]byte, 32+buffersPerLane*Lanes*internalBlockSize)
+ md5srv.buffers = make(chan []byte, buffersPerLane*Lanes)
+ // Fill buffers.
+ for i := 0; i < buffersPerLane*Lanes; i++ {
+ s := 32 + i*internalBlockSize
+ md5srv.buffers <- md5srv.allBufs[s : s+internalBlockSize : s+internalBlockSize]
+ }
+
+ // Start a single thread for reading from the input channel
+ go md5srv.process(md5srv.newInput)
+ return md5srv
+}
+
+type newClient struct {
+ uid uint64
+ input chan blockInput
+}
+
+// process - Sole handler for reading from the input channel.
+func (s *md5Server) process(newClients chan newClient) {
+ // To fill up as many lanes as possible:
+ //
+ // 1. Wait for a cycle id.
+ // 2. If not already in a lane, add, otherwise leave on channel
+ // 3. Start timer
+ // 4. Check if lanes is full, if so, goto 10 (process).
+ // 5. If timeout, goto 10.
+ // 6. Wait for new id (goto 2) or timeout (goto 10).
+ // 10. Process.
+ // 11. Check all input if there is already input, if so add to lanes.
+ // 12. Goto 1
+
+ // lanes contains the lanes.
+ var lanes lanesInfo
+ // lanesFilled contains the number of filled lanes for current cycle.
+ var lanesFilled int
+ // clients contains active clients
+ var clients = make(map[uint64]chan blockInput, Lanes)
+
+ addToLane := func(uid uint64) {
+ cl, ok := clients[uid]
+ if !ok {
+ // Unknown client. Maybe it was already removed.
+ return
+ }
+ // Check if we already have it.
+ for _, lane := range lanes[:lanesFilled] {
+ if lane.uid == uid {
+ return
+ }
+ }
+ // Continue until we get a block or there is nothing on channel
+ for {
+ select {
+ case block, ok := <-cl:
+ if !ok {
+ // Client disconnected
+ delete(clients, block.uid)
+ return
+ }
+ if block.uid != uid {
+ panic(fmt.Errorf("uid mismatch, %d (block) != %d (client)", block.uid, uid))
+ }
+ // If reset message, reset and we're done
+ if block.reset {
+ delete(s.digests, uid)
+ continue
+ }
+
+ // If requesting sum, we will need to maintain state.
+ if block.sumCh != nil {
+ var dig digest
+ d, ok := s.digests[uid]
+ if ok {
+ dig.s[0] = binary.LittleEndian.Uint32(d[0:4])
+ dig.s[1] = binary.LittleEndian.Uint32(d[4:8])
+ dig.s[2] = binary.LittleEndian.Uint32(d[8:12])
+ dig.s[3] = binary.LittleEndian.Uint32(d[12:16])
+ } else {
+ dig.s[0], dig.s[1], dig.s[2], dig.s[3] = init0, init1, init2, init3
+ }
+
+ sum := sumResult{}
+ // Add end block to current digest.
+ blockScalar(&dig.s, block.msg)
+
+ binary.LittleEndian.PutUint32(sum.digest[0:], dig.s[0])
+ binary.LittleEndian.PutUint32(sum.digest[4:], dig.s[1])
+ binary.LittleEndian.PutUint32(sum.digest[8:], dig.s[2])
+ binary.LittleEndian.PutUint32(sum.digest[12:], dig.s[3])
+ block.sumCh <- sum
+ if block.msg != nil {
+ s.buffers <- block.msg
+ }
+ continue
+ }
+ if len(block.msg) == 0 {
+ continue
+ }
+ lanes[lanesFilled] = block
+ lanesFilled++
+ return
+ default:
+ return
+ }
+ }
+ }
+ addNewClient := func(cl newClient) {
+ if _, ok := clients[cl.uid]; ok {
+ panic("internal error: duplicate client registration")
+ }
+ clients[cl.uid] = cl.input
+ }
+
+ allLanesFilled := func() bool {
+ return lanesFilled == Lanes || lanesFilled >= len(clients)
+ }
+
+ for {
+ // Step 1.
+ for lanesFilled == 0 {
+ select {
+ case cl, ok := <-newClients:
+ if !ok {
+ return
+ }
+ addNewClient(cl)
+ // Check if it already sent a payload.
+ addToLane(cl.uid)
+ continue
+ case uid := <-s.cycle:
+ addToLane(uid)
+ }
+ }
+
+ fillLanes:
+ for !allLanesFilled() {
+ select {
+ case cl, ok := <-newClients:
+ if !ok {
+ return
+ }
+ addNewClient(cl)
+
+ case uid := <-s.cycle:
+ addToLane(uid)
+ default:
+ // Nothing more queued...
+ break fillLanes
+ }
+ }
+
+ // If we did not fill all lanes, check if there is more waiting
+ if !allLanesFilled() {
+ runtime.Gosched()
+ for uid := range clients {
+ addToLane(uid)
+ if allLanesFilled() {
+ break
+ }
+ }
+ }
+ if false {
+ if !allLanesFilled() {
+ fmt.Println("Not all lanes filled", lanesFilled, "of", len(clients))
+ //pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
+ } else if true {
+ fmt.Println("all lanes filled")
+ }
+ }
+ // Process the lanes we could collect
+ s.blocks(lanes[:lanesFilled])
+
+ // Clear lanes...
+ lanesFilled = 0
+ // Add all current queued
+ for uid := range clients {
+ addToLane(uid)
+ if allLanesFilled() {
+ break
+ }
+ }
+ }
+}
+
+func (s *md5Server) Close() {
+ if s.newInput != nil {
+ close(s.newInput)
+ s.newInput = nil
+ }
+}
+
+// Invoke assembly and send results back
+func (s *md5Server) blocks(lanes []blockInput) {
+ if len(lanes) < useScalarBelow {
+ // Use scalar routine when below this many lanes
+ switch len(lanes) {
+ case 0:
+ case 1:
+ lane := lanes[0]
+ var d digest
+ a, ok := s.digests[lane.uid]
+ if ok {
+ d.s[0] = binary.LittleEndian.Uint32(a[0:4])
+ d.s[1] = binary.LittleEndian.Uint32(a[4:8])
+ d.s[2] = binary.LittleEndian.Uint32(a[8:12])
+ d.s[3] = binary.LittleEndian.Uint32(a[12:16])
+ } else {
+ d.s[0] = init0
+ d.s[1] = init1
+ d.s[2] = init2
+ d.s[3] = init3
+ }
+ if len(lane.msg) > 0 {
+ // Update...
+ blockScalar(&d.s, lane.msg)
+ }
+ dig := [Size]byte{}
+ binary.LittleEndian.PutUint32(dig[0:], d.s[0])
+ binary.LittleEndian.PutUint32(dig[4:], d.s[1])
+ binary.LittleEndian.PutUint32(dig[8:], d.s[2])
+ binary.LittleEndian.PutUint32(dig[12:], d.s[3])
+ s.digests[lane.uid] = dig
+
+ if lane.msg != nil {
+ s.buffers <- lane.msg
+ }
+ lanes[0] = blockInput{}
+
+ default:
+ s.wg.Add(len(lanes))
+ var results [useScalarBelow]digest
+ for i := range lanes {
+ lane := lanes[i]
+ go func(i int) {
+ var d digest
+ defer s.wg.Done()
+ a, ok := s.digests[lane.uid]
+ if ok {
+ d.s[0] = binary.LittleEndian.Uint32(a[0:4])
+ d.s[1] = binary.LittleEndian.Uint32(a[4:8])
+ d.s[2] = binary.LittleEndian.Uint32(a[8:12])
+ d.s[3] = binary.LittleEndian.Uint32(a[12:16])
+ } else {
+ d.s[0] = init0
+ d.s[1] = init1
+ d.s[2] = init2
+ d.s[3] = init3
+ }
+ if len(lane.msg) == 0 {
+ results[i] = d
+ return
+ }
+ // Update...
+ blockScalar(&d.s, lane.msg)
+ results[i] = d
+ }(i)
+ }
+ s.wg.Wait()
+ for i, lane := range lanes {
+ dig := [Size]byte{}
+ binary.LittleEndian.PutUint32(dig[0:], results[i].s[0])
+ binary.LittleEndian.PutUint32(dig[4:], results[i].s[1])
+ binary.LittleEndian.PutUint32(dig[8:], results[i].s[2])
+ binary.LittleEndian.PutUint32(dig[12:], results[i].s[3])
+ s.digests[lane.uid] = dig
+
+ if lane.msg != nil {
+ s.buffers <- lane.msg
+ }
+ lanes[i] = blockInput{}
+ }
+ }
+ return
+ }
+
+ inputs := [16][]byte{}
+ for i := range lanes {
+ inputs[i] = lanes[i].msg
+ }
+
+ // Collect active digests...
+ state := s.getDigests(lanes)
+ // Process all lanes...
+ s.blockMd5_x16(&state, inputs, len(lanes) <= 8)
+
+ for i, lane := range lanes {
+ uid := lane.uid
+ dig := [Size]byte{}
+ binary.LittleEndian.PutUint32(dig[0:], state.v0[i])
+ binary.LittleEndian.PutUint32(dig[4:], state.v1[i])
+ binary.LittleEndian.PutUint32(dig[8:], state.v2[i])
+ binary.LittleEndian.PutUint32(dig[12:], state.v3[i])
+
+ s.digests[uid] = dig
+ if lane.msg != nil {
+ s.buffers <- lane.msg
+ }
+ lanes[i] = blockInput{}
+ }
+}
+
+func (s *md5Server) getDigests(lanes []blockInput) (d digest16) {
+ for i, lane := range lanes {
+ a, ok := s.digests[lane.uid]
+ if ok {
+ d.v0[i] = binary.LittleEndian.Uint32(a[0:4])
+ d.v1[i] = binary.LittleEndian.Uint32(a[4:8])
+ d.v2[i] = binary.LittleEndian.Uint32(a[8:12])
+ d.v3[i] = binary.LittleEndian.Uint32(a[12:16])
+ } else {
+ d.v0[i] = init0
+ d.v1[i] = init1
+ d.v2[i] = init2
+ d.v3[i] = init3
+ }
+ }
+ return
+}
diff --git a/vendor/github.com/minio/md5-simd/md5-server_fallback.go b/vendor/github.com/minio/md5-simd/md5-server_fallback.go
new file mode 100644
index 00000000..7814dada
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5-server_fallback.go
@@ -0,0 +1,12 @@
+//+build !amd64 appengine !gc noasm
+
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+package md5simd
+
+// NewServer - Create new object for parallel processing handling
+func NewServer() *fallbackServer {
+ return &fallbackServer{}
+}
diff --git a/vendor/github.com/minio/md5-simd/md5-util_amd64.go b/vendor/github.com/minio/md5-simd/md5-util_amd64.go
new file mode 100644
index 00000000..73981b0e
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5-util_amd64.go
@@ -0,0 +1,85 @@
+//+build !noasm,!appengine,gc
+
+// Copyright (c) 2020 MinIO Inc. All rights reserved.
+// Use of this source code is governed by a license that can be
+// found in the LICENSE file.
+
+package md5simd
+
+// Helper struct for sorting blocks based on length
+type lane struct {
+ len uint
+ pos uint
+}
+
+type digest struct {
+ s [4]uint32
+}
+
+// Helper struct for generating number of rounds in combination with mask for valid lanes
+type maskRounds struct {
+ mask uint64
+ rounds uint64
+}
+
+func generateMaskAndRounds8(input [8][]byte, mr *[8]maskRounds) (rounds int) {
+ // Sort on blocks length small to large
+ var sorted [8]lane
+ for c, inpt := range input[:] {
+ sorted[c] = lane{uint(len(inpt)), uint(c)}
+ for i := c - 1; i >= 0; i-- {
+ // swap so largest is at the end...
+ if sorted[i].len > sorted[i+1].len {
+ sorted[i], sorted[i+1] = sorted[i+1], sorted[i]
+ continue
+ }
+ break
+ }
+ }
+
+ // Create mask array including 'rounds' (of processing blocks of 64 bytes) between masks
+ m, round := uint64(0xff), uint64(0)
+
+ for _, s := range sorted[:] {
+ if s.len > 0 {
+ if uint64(s.len)>>6 > round {
+ mr[rounds] = maskRounds{m, (uint64(s.len) >> 6) - round}
+ rounds++
+ }
+ round = uint64(s.len) >> 6
+ }
+ m = m & ^(1 << uint(s.pos))
+ }
+ return
+}
+
+func generateMaskAndRounds16(input [16][]byte, mr *[16]maskRounds) (rounds int) {
+ // Sort on blocks length small to large
+ var sorted [16]lane
+ for c, inpt := range input[:] {
+ sorted[c] = lane{uint(len(inpt)), uint(c)}
+ for i := c - 1; i >= 0; i-- {
+ // swap so largest is at the end...
+ if sorted[i].len > sorted[i+1].len {
+ sorted[i], sorted[i+1] = sorted[i+1], sorted[i]
+ continue
+ }
+ break
+ }
+ }
+
+ // Create mask array including 'rounds' (of processing blocks of 64 bytes) between masks
+ m, round := uint64(0xffff), uint64(0)
+
+ for _, s := range sorted[:] {
+ if s.len > 0 {
+ if uint64(s.len)>>6 > round {
+ mr[rounds] = maskRounds{m, (uint64(s.len) >> 6) - round}
+ rounds++
+ }
+ round = uint64(s.len) >> 6
+ }
+ m = m & ^(1 << uint(s.pos))
+ }
+ return
+}
diff --git a/vendor/github.com/minio/md5-simd/md5.go b/vendor/github.com/minio/md5-simd/md5.go
new file mode 100644
index 00000000..11b0cb96
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5.go
@@ -0,0 +1,63 @@
+package md5simd
+
+import (
+ "crypto/md5"
+ "hash"
+ "sync"
+)
+
+const (
+ // The blocksize of MD5 in bytes.
+ BlockSize = 64
+
+ // The size of an MD5 checksum in bytes.
+ Size = 16
+
+ // internalBlockSize is the internal block size.
+ internalBlockSize = 32 << 10
+)
+
+type Server interface {
+ NewHash() Hasher
+ Close()
+}
+
+type Hasher interface {
+ hash.Hash
+ Close()
+}
+
+// StdlibHasher returns a Hasher that uses the stdlib for hashing.
+// Used hashers are stored in a pool for fast reuse.
+func StdlibHasher() Hasher {
+ return &md5Wrapper{Hash: md5Pool.New().(hash.Hash)}
+}
+
+// md5Wrapper is a wrapper around the builtin hasher.
+type md5Wrapper struct {
+ hash.Hash
+}
+
+var md5Pool = sync.Pool{New: func() interface{} {
+ return md5.New()
+}}
+
+// fallbackServer - Fallback when no assembly is available.
+type fallbackServer struct {
+}
+
+// NewHash -- return regular Golang md5 hashing from crypto
+func (s *fallbackServer) NewHash() Hasher {
+ return &md5Wrapper{Hash: md5Pool.New().(hash.Hash)}
+}
+
+func (s *fallbackServer) Close() {
+}
+
+func (m *md5Wrapper) Close() {
+ if m.Hash != nil {
+ m.Reset()
+ md5Pool.Put(m.Hash)
+ m.Hash = nil
+ }
+}
diff --git a/vendor/github.com/minio/md5-simd/md5block_amd64.go b/vendor/github.com/minio/md5-simd/md5block_amd64.go
new file mode 100644
index 00000000..4c279366
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5block_amd64.go
@@ -0,0 +1,11 @@
+// Code generated by command: go run gen.go -out ../md5block_amd64.s -stubs ../md5block_amd64.go -pkg=md5simd. DO NOT EDIT.
+
+// +build !appengine
+// +build !noasm
+// +build gc
+
+package md5simd
+
+// Encode p to digest
+//go:noescape
+func blockScalar(dig *[4]uint32, p []byte)
diff --git a/vendor/github.com/minio/md5-simd/md5block_amd64.s b/vendor/github.com/minio/md5-simd/md5block_amd64.s
new file mode 100644
index 00000000..fbc4a21f
--- /dev/null
+++ b/vendor/github.com/minio/md5-simd/md5block_amd64.s
@@ -0,0 +1,714 @@
+// Code generated by command: go run gen.go -out ../md5block_amd64.s -stubs ../md5block_amd64.go -pkg=md5simd. DO NOT EDIT.
+
+// +build !appengine
+// +build !noasm
+// +build gc
+
+// func blockScalar(dig *[4]uint32, p []byte)
+TEXT ยทblockScalar(SB), $0-32
+ MOVQ p_len+16(FP), AX
+ MOVQ dig+0(FP), CX
+ MOVQ p_base+8(FP), DX
+ SHRQ $0x06, AX
+ SHLQ $0x06, AX
+ LEAQ (DX)(AX*1), AX
+ CMPQ DX, AX
+ JEQ end
+ MOVL (CX), BX
+ MOVL 4(CX), BP
+ MOVL 8(CX), SI
+ MOVL 12(CX), CX
+ MOVL $0xffffffff, DI
+
+loop:
+ MOVL (DX), R8
+ MOVL CX, R9
+ MOVL BX, R10
+ MOVL BP, R11
+ MOVL SI, R12
+ MOVL CX, R13
+
+ // ROUND1
+ XORL SI, R9
+ ADDL $0xd76aa478, BX
+ ADDL R8, BX
+ ANDL BP, R9
+ XORL CX, R9
+ MOVL 4(DX), R8
+ ADDL R9, BX
+ ROLL $0x07, BX
+ MOVL SI, R9
+ ADDL BP, BX
+ XORL BP, R9
+ ADDL $0xe8c7b756, CX
+ ADDL R8, CX
+ ANDL BX, R9
+ XORL SI, R9
+ MOVL 8(DX), R8
+ ADDL R9, CX
+ ROLL $0x0c, CX
+ MOVL BP, R9
+ ADDL BX, CX
+ XORL BX, R9
+ ADDL $0x242070db, SI
+ ADDL R8, SI
+ ANDL CX, R9
+ XORL BP, R9
+ MOVL 12(DX), R8
+ ADDL R9, SI
+ ROLL $0x11, SI
+ MOVL BX, R9
+ ADDL CX, SI
+ XORL CX, R9
+ ADDL $0xc1bdceee, BP
+ ADDL R8, BP
+ ANDL SI, R9
+ XORL BX, R9
+ MOVL 16(DX), R8
+ ADDL R9, BP
+ ROLL $0x16, BP
+ MOVL CX, R9
+ ADDL SI, BP
+ XORL SI, R9
+ ADDL $0xf57c0faf, BX
+ ADDL R8, BX
+ ANDL BP, R9
+ XORL CX, R9
+ MOVL 20(DX), R8
+ ADDL R9, BX
+ ROLL $0x07, BX
+ MOVL SI, R9
+ ADDL BP, BX
+ XORL BP, R9
+ ADDL $0x4787c62a, CX
+ ADDL R8, CX
+ ANDL BX, R9
+ XORL SI, R9
+ MOVL 24(DX), R8
+ ADDL R9, CX
+ ROLL $0x0c, CX
+ MOVL BP, R9
+ ADDL BX, CX
+ XORL BX, R9
+ ADDL $0xa8304613, SI
+ ADDL R8, SI
+ ANDL CX, R9
+ XORL BP, R9
+ MOVL 28(DX), R8
+ ADDL R9, SI
+ ROLL $0x11, SI
+ MOVL BX, R9
+ ADDL CX, SI
+ XORL CX, R9
+ ADDL $0xfd469501, BP
+ ADDL R8, BP
+ ANDL SI, R9
+ XORL BX, R9
+ MOVL 32(DX), R8
+ ADDL R9, BP
+ ROLL $0x16, BP
+ MOVL CX, R9
+ ADDL SI, BP
+ XORL SI, R9
+ ADDL $0x698098d8, BX
+ ADDL R8, BX
+ ANDL BP, R9
+ XORL CX, R9
+ MOVL 36(DX), R8
+ ADDL R9, BX
+ ROLL $0x07, BX
+ MOVL SI, R9
+ ADDL BP, BX
+ XORL BP, R9
+ ADDL $0x8b44f7af, CX
+ ADDL R8, CX
+ ANDL BX, R9
+ XORL SI, R9
+ MOVL 40(DX), R8
+ ADDL R9, CX
+ ROLL $0x0c, CX
+ MOVL BP, R9
+ ADDL BX, CX
+ XORL BX, R9
+ ADDL $0xffff5bb1, SI
+ ADDL R8, SI
+ ANDL CX, R9
+ XORL BP, R9
+ MOVL 44(DX), R8
+ ADDL R9, SI
+ ROLL $0x11, SI
+ MOVL BX, R9
+ ADDL CX, SI
+ XORL CX, R9
+ ADDL $0x895cd7be, BP
+ ADDL R8, BP
+ ANDL SI, R9
+ XORL BX, R9
+ MOVL 48(DX), R8
+ ADDL R9, BP
+ ROLL $0x16, BP
+ MOVL CX, R9
+ ADDL SI, BP
+ XORL SI, R9
+ ADDL $0x6b901122, BX
+ ADDL R8, BX
+ ANDL BP, R9
+ XORL CX, R9
+ MOVL 52(DX), R8
+ ADDL R9, BX
+ ROLL $0x07, BX
+ MOVL SI, R9
+ ADDL BP, BX
+ XORL BP, R9
+ ADDL $0xfd987193, CX
+ ADDL R8, CX
+ ANDL BX, R9
+ XORL SI, R9
+ MOVL 56(DX), R8
+ ADDL R9, CX
+ ROLL $0x0c, CX
+ MOVL BP, R9
+ ADDL BX, CX
+ XORL BX, R9
+ ADDL $0xa679438e, SI
+ ADDL R8, SI
+ ANDL CX, R9
+ XORL BP, R9
+ MOVL 60(DX), R8
+ ADDL R9, SI
+ ROLL $0x11, SI
+ MOVL BX, R9
+ ADDL CX, SI
+ XORL CX, R9
+ ADDL $0x49b40821, BP
+ ADDL R8, BP
+ ANDL SI, R9
+ XORL BX, R9
+ MOVL 4(DX), R8
+ ADDL R9, BP
+ ROLL $0x16, BP
+ MOVL CX, R9
+ ADDL SI, BP
+
+ // ROUND2
+ MOVL CX, R9
+ MOVL CX, R14
+ XORL DI, R9
+ ADDL $0xf61e2562, BX
+ ADDL R8, BX
+ ANDL BP, R14
+ ANDL SI, R9
+ MOVL 24(DX), R8
+ ORL R9, R14
+ MOVL SI, R9
+ ADDL R14, BX
+ MOVL SI, R14
+ ROLL $0x05, BX
+ ADDL BP, BX
+ XORL DI, R9
+ ADDL $0xc040b340, CX
+ ADDL R8, CX
+ ANDL BX, R14
+ ANDL BP, R9
+ MOVL 44(DX), R8
+ ORL R9, R14
+ MOVL BP, R9
+ ADDL R14, CX
+ MOVL BP, R14
+ ROLL $0x09, CX
+ ADDL BX, CX
+ XORL DI, R9
+ ADDL $0x265e5a51, SI
+ ADDL R8, SI
+ ANDL CX, R14
+ ANDL BX, R9
+ MOVL (DX), R8
+ ORL R9, R14
+ MOVL BX, R9
+ ADDL R14, SI
+ MOVL BX, R14
+ ROLL $0x0e, SI
+ ADDL CX, SI
+ XORL DI, R9
+ ADDL $0xe9b6c7aa, BP
+ ADDL R8, BP
+ ANDL SI, R14
+ ANDL CX, R9
+ MOVL 20(DX), R8
+ ORL R9, R14
+ MOVL CX, R9
+ ADDL R14, BP
+ MOVL CX, R14
+ ROLL $0x14, BP
+ ADDL SI, BP
+ XORL DI, R9
+ ADDL $0xd62f105d, BX
+ ADDL R8, BX
+ ANDL BP, R14
+ ANDL SI, R9
+ MOVL 40(DX), R8
+ ORL R9, R14
+ MOVL SI, R9
+ ADDL R14, BX
+ MOVL SI, R14
+ ROLL $0x05, BX
+ ADDL BP, BX
+ XORL DI, R9
+ ADDL $0x02441453, CX
+ ADDL R8, CX
+ ANDL BX, R14
+ ANDL BP, R9
+ MOVL 60(DX), R8
+ ORL R9, R14
+ MOVL BP, R9
+ ADDL R14, CX
+ MOVL BP, R14
+ ROLL $0x09, CX
+ ADDL BX, CX
+ XORL DI, R9
+ ADDL $0xd8a1e681, SI
+ ADDL R8, SI
+ ANDL CX, R14
+ ANDL BX, R9
+ MOVL 16(DX), R8
+ ORL R9, R14
+ MOVL BX, R9
+ ADDL R14, SI
+ MOVL BX, R14
+ ROLL $0x0e, SI
+ ADDL CX, SI
+ XORL DI, R9
+ ADDL $0xe7d3fbc8, BP
+ ADDL R8, BP
+ ANDL SI, R14
+ ANDL CX, R9
+ MOVL 36(DX), R8
+ ORL R9, R14
+ MOVL CX, R9
+ ADDL R14, BP
+ MOVL CX, R14
+ ROLL $0x14, BP
+ ADDL SI, BP
+ XORL DI, R9
+ ADDL $0x21e1cde6, BX
+ ADDL R8, BX
+ ANDL BP, R14
+ ANDL SI, R9
+ MOVL 56(DX), R8
+ ORL R9, R14
+ MOVL SI, R9
+ ADDL R14, BX
+ MOVL SI, R14
+ ROLL $0x05, BX
+ ADDL BP, BX
+ XORL DI, R9
+ ADDL $0xc33707d6, CX
+ ADDL R8, CX
+ ANDL BX, R14
+ ANDL BP, R9
+ MOVL 12(DX), R8
+ ORL R9, R14
+ MOVL BP, R9
+ ADDL R14, CX
+ MOVL BP, R14
+ ROLL $0x09, CX
+ ADDL BX, CX
+ XORL DI, R9
+ ADDL $0xf4d50d87, SI
+ ADDL R8, SI
+ ANDL CX, R14
+ ANDL BX, R9
+ MOVL 32(DX), R8
+ ORL R9, R14
+ MOVL BX, R9
+ ADDL R14, SI
+ MOVL BX, R14
+ ROLL $0x0e, SI
+ ADDL CX, SI
+ XORL DI, R9
+ ADDL $0x455a14ed, BP
+ ADDL R8, BP
+ ANDL SI, R14
+ ANDL CX, R9
+ MOVL 52(DX), R8
+ ORL R9, R14
+ MOVL CX, R9
+ ADDL R14, BP
+ MOVL CX, R14
+ ROLL $0x14, BP
+ ADDL SI, BP
+ XORL DI, R9
+ ADDL $0xa9e3e905, BX
+ ADDL R8, BX
+ ANDL BP, R14
+ ANDL SI, R9
+ MOVL 8(DX), R8
+ ORL R9, R14
+ MOVL SI, R9
+ ADDL R14, BX
+ MOVL SI, R14
+ ROLL $0x05, BX
+ ADDL BP, BX
+ XORL DI, R9
+ ADDL $0xfcefa3f8, CX
+ ADDL R8, CX
+ ANDL BX, R14
+ ANDL BP, R9
+ MOVL 28(DX), R8
+ ORL R9, R14
+ MOVL BP, R9
+ ADDL R14, CX
+ MOVL BP, R14
+ ROLL $0x09, CX
+ ADDL BX, CX
+ XORL DI, R9
+ ADDL $0x676f02d9, SI
+ ADDL R8, SI
+ ANDL CX, R14
+ ANDL BX, R9
+ MOVL 48(DX), R8
+ ORL R9, R14
+ MOVL BX, R9
+ ADDL R14, SI
+ MOVL BX, R14
+ ROLL $0x0e, SI
+ ADDL CX, SI
+ XORL DI, R9
+ ADDL $0x8d2a4c8a, BP
+ ADDL R8, BP
+ ANDL SI, R14
+ ANDL CX, R9
+ MOVL 20(DX), R8
+ ORL R9, R14
+ MOVL CX, R9
+ ADDL R14, BP
+ MOVL CX, R14
+ ROLL $0x14, BP
+ ADDL SI, BP
+
+ // ROUND3
+ MOVL SI, R9
+ ADDL $0xfffa3942, BX
+ ADDL R8, BX
+ MOVL 32(DX), R8
+ XORL CX, R9
+ XORL BP, R9
+ ADDL R9, BX
+ ROLL $0x04, BX
+ MOVL BP, R9
+ ADDL BP, BX
+ ADDL $0x8771f681, CX
+ ADDL R8, CX
+ MOVL 44(DX), R8
+ XORL SI, R9
+ XORL BX, R9
+ ADDL R9, CX
+ ROLL $0x0b, CX
+ MOVL BX, R9
+ ADDL BX, CX
+ ADDL $0x6d9d6122, SI
+ ADDL R8, SI
+ MOVL 56(DX), R8
+ XORL BP, R9
+ XORL CX, R9
+ ADDL R9, SI
+ ROLL $0x10, SI
+ MOVL CX, R9
+ ADDL CX, SI
+ ADDL $0xfde5380c, BP
+ ADDL R8, BP
+ MOVL 4(DX), R8
+ XORL BX, R9
+ XORL SI, R9
+ ADDL R9, BP
+ ROLL $0x17, BP
+ MOVL SI, R9
+ ADDL SI, BP
+ ADDL $0xa4beea44, BX
+ ADDL R8, BX
+ MOVL 16(DX), R8
+ XORL CX, R9
+ XORL BP, R9
+ ADDL R9, BX
+ ROLL $0x04, BX
+ MOVL BP, R9
+ ADDL BP, BX
+ ADDL $0x4bdecfa9, CX
+ ADDL R8, CX
+ MOVL 28(DX), R8
+ XORL SI, R9
+ XORL BX, R9
+ ADDL R9, CX
+ ROLL $0x0b, CX
+ MOVL BX, R9
+ ADDL BX, CX
+ ADDL $0xf6bb4b60, SI
+ ADDL R8, SI
+ MOVL 40(DX), R8
+ XORL BP, R9
+ XORL CX, R9
+ ADDL R9, SI
+ ROLL $0x10, SI
+ MOVL CX, R9
+ ADDL CX, SI
+ ADDL $0xbebfbc70, BP
+ ADDL R8, BP
+ MOVL 52(DX), R8
+ XORL BX, R9
+ XORL SI, R9
+ ADDL R9, BP
+ ROLL $0x17, BP
+ MOVL SI, R9
+ ADDL SI, BP
+ ADDL $0x289b7ec6, BX
+ ADDL R8, BX
+ MOVL (DX), R8
+ XORL CX, R9
+ XORL BP, R9
+ ADDL R9, BX
+ ROLL $0x04, BX
+ MOVL BP, R9
+ ADDL BP, BX
+ ADDL $0xeaa127fa, CX
+ ADDL R8, CX
+ MOVL 12(DX), R8
+ XORL SI, R9
+ XORL BX, R9
+ ADDL R9, CX
+ ROLL $0x0b, CX
+ MOVL BX, R9
+ ADDL BX, CX
+ ADDL $0xd4ef3085, SI
+ ADDL R8, SI
+ MOVL 24(DX), R8
+ XORL BP, R9
+ XORL CX, R9
+ ADDL R9, SI
+ ROLL $0x10, SI
+ MOVL CX, R9
+ ADDL CX, SI
+ ADDL $0x04881d05, BP
+ ADDL R8, BP
+ MOVL 36(DX), R8
+ XORL BX, R9
+ XORL SI, R9
+ ADDL R9, BP
+ ROLL $0x17, BP
+ MOVL SI, R9
+ ADDL SI, BP
+ ADDL $0xd9d4d039, BX
+ ADDL R8, BX
+ MOVL 48(DX), R8
+ XORL CX, R9
+ XORL BP, R9
+ ADDL R9, BX
+ ROLL $0x04, BX
+ MOVL BP, R9
+ ADDL BP, BX
+ ADDL $0xe6db99e5, CX
+ ADDL R8, CX
+ MOVL 60(DX), R8
+ XORL SI, R9
+ XORL BX, R9
+ ADDL R9, CX
+ ROLL $0x0b, CX
+ MOVL BX, R9
+ ADDL BX, CX
+ ADDL $0x1fa27cf8, SI
+ ADDL R8, SI
+ MOVL 8(DX), R8
+ XORL BP, R9
+ XORL CX, R9
+ ADDL R9, SI
+ ROLL $0x10, SI
+ MOVL CX, R9
+ ADDL CX, SI
+ ADDL $0xc4ac5665, BP
+ ADDL R8, BP
+ MOVL (DX), R8
+ XORL BX, R9
+ XORL SI, R9
+ ADDL R9, BP
+ ROLL $0x17, BP
+ MOVL SI, R9
+ ADDL SI, BP
+
+ // ROUND4
+ MOVL DI, R9
+ XORL CX, R9
+ ADDL $0xf4292244, BX
+ ADDL R8, BX
+ ORL BP, R9
+ XORL SI, R9
+ ADDL R9, BX
+ MOVL 28(DX), R8
+ MOVL DI, R9
+ ROLL $0x06, BX
+ XORL SI, R9
+ ADDL BP, BX
+ ADDL $0x432aff97, CX
+ ADDL R8, CX
+ ORL BX, R9
+ XORL BP, R9
+ ADDL R9, CX
+ MOVL 56(DX), R8
+ MOVL DI, R9
+ ROLL $0x0a, CX
+ XORL BP, R9
+ ADDL BX, CX
+ ADDL $0xab9423a7, SI
+ ADDL R8, SI
+ ORL CX, R9
+ XORL BX, R9
+ ADDL R9, SI
+ MOVL 20(DX), R8
+ MOVL DI, R9
+ ROLL $0x0f, SI
+ XORL BX, R9
+ ADDL CX, SI
+ ADDL $0xfc93a039, BP
+ ADDL R8, BP
+ ORL SI, R9
+ XORL CX, R9
+ ADDL R9, BP
+ MOVL 48(DX), R8
+ MOVL DI, R9
+ ROLL $0x15, BP
+ XORL CX, R9
+ ADDL SI, BP
+ ADDL $0x655b59c3, BX
+ ADDL R8, BX
+ ORL BP, R9
+ XORL SI, R9
+ ADDL R9, BX
+ MOVL 12(DX), R8
+ MOVL DI, R9
+ ROLL $0x06, BX
+ XORL SI, R9
+ ADDL BP, BX
+ ADDL $0x8f0ccc92, CX
+ ADDL R8, CX
+ ORL BX, R9
+ XORL BP, R9
+ ADDL R9, CX
+ MOVL 40(DX), R8
+ MOVL DI, R9
+ ROLL $0x0a, CX
+ XORL BP, R9
+ ADDL BX, CX
+ ADDL $0xffeff47d, SI
+ ADDL R8, SI
+ ORL CX, R9
+ XORL BX, R9
+ ADDL R9, SI
+ MOVL 4(DX), R8
+ MOVL DI, R9
+ ROLL $0x0f, SI
+ XORL BX, R9
+ ADDL CX, SI
+ ADDL $0x85845dd1, BP
+ ADDL R8, BP
+ ORL SI, R9
+ XORL CX, R9
+ ADDL R9, BP
+ MOVL 32(DX), R8
+ MOVL DI, R9
+ ROLL $0x15, BP
+ XORL CX, R9
+ ADDL SI, BP
+ ADDL $0x6fa87e4f, BX
+ ADDL R8, BX
+ ORL BP, R9
+ XORL SI, R9
+ ADDL R9, BX
+ MOVL 60(DX), R8
+ MOVL DI, R9
+ ROLL $0x06, BX
+ XORL SI, R9
+ ADDL BP, BX
+ ADDL $0xfe2ce6e0, CX
+ ADDL R8, CX
+ ORL BX, R9
+ XORL BP, R9
+ ADDL R9, CX
+ MOVL 24(DX), R8
+ MOVL DI, R9
+ ROLL $0x0a, CX
+ XORL BP, R9
+ ADDL BX, CX
+ ADDL $0xa3014314, SI
+ ADDL R8, SI
+ ORL CX, R9
+ XORL BX, R9
+ ADDL R9, SI
+ MOVL 52(DX), R8
+ MOVL DI, R9
+ ROLL $0x0f, SI
+ XORL BX, R9
+ ADDL CX, SI
+ ADDL $0x4e0811a1, BP
+ ADDL R8, BP
+ ORL SI, R9
+ XORL CX, R9
+ ADDL R9, BP
+ MOVL 16(DX), R8
+ MOVL DI, R9
+ ROLL $0x15, BP
+ XORL CX, R9
+ ADDL SI, BP
+ ADDL $0xf7537e82, BX
+ ADDL R8, BX
+ ORL BP, R9
+ XORL SI, R9
+ ADDL R9, BX
+ MOVL 44(DX), R8
+ MOVL DI, R9
+ ROLL $0x06, BX
+ XORL SI, R9
+ ADDL BP, BX
+ ADDL $0xbd3af235, CX
+ ADDL R8, CX
+ ORL BX, R9
+ XORL BP, R9
+ ADDL R9, CX
+ MOVL 8(DX), R8
+ MOVL DI, R9
+ ROLL $0x0a, CX
+ XORL BP, R9
+ ADDL BX, CX
+ ADDL $0x2ad7d2bb, SI
+ ADDL R8, SI
+ ORL CX, R9
+ XORL BX, R9
+ ADDL R9, SI
+ MOVL 36(DX), R8
+ MOVL DI, R9
+ ROLL $0x0f, SI
+ XORL BX, R9
+ ADDL CX, SI
+ ADDL $0xeb86d391, BP
+ ADDL R8, BP
+ ORL SI, R9
+ XORL CX, R9
+ ADDL R9, BP
+ ROLL $0x15, BP
+ ADDL SI, BP
+ ADDL R10, BX
+ ADDL R11, BP
+ ADDL R12, SI
+ ADDL R13, CX
+
+ // Prepare next loop
+ ADDQ $0x40, DX
+ CMPQ DX, AX
+ JB loop
+
+ // Write output
+ MOVQ dig+0(FP), AX
+ MOVL BX, (AX)
+ MOVL BP, 4(AX)
+ MOVL SI, 8(AX)
+ MOVL CX, 12(AX)
+
+end:
+ RET
diff --git a/vendor/github.com/minio/minio-go/v7/.gitignore b/vendor/github.com/minio/minio-go/v7/.gitignore
new file mode 100644
index 00000000..8081bd0f
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/.gitignore
@@ -0,0 +1,4 @@
+*~
+*.test
+validator
+golangci-lint \ No newline at end of file
diff --git a/vendor/github.com/minio/minio-go/v7/.golangci.yml b/vendor/github.com/minio/minio-go/v7/.golangci.yml
new file mode 100644
index 00000000..7d1dd335
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/.golangci.yml
@@ -0,0 +1,16 @@
+linters-settings:
+ misspell:
+ locale: US
+
+linters:
+ disable-all: true
+ enable:
+ - typecheck
+ - goimports
+ - misspell
+ - govet
+ - golint
+ - ineffassign
+ - gosimple
+ - deadcode
+ - structcheck
diff --git a/vendor/github.com/minio/minio-go/v7/CNAME b/vendor/github.com/minio/minio-go/v7/CNAME
new file mode 100644
index 00000000..d365a7bb
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/CNAME
@@ -0,0 +1 @@
+minio-go.min.io \ No newline at end of file
diff --git a/vendor/github.com/minio/minio-go/v7/CONTRIBUTING.md b/vendor/github.com/minio/minio-go/v7/CONTRIBUTING.md
new file mode 100644
index 00000000..8b1ee86c
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+
+### Developer Guidelines
+
+``minio-go`` welcomes your contribution. To make the process as seamless as possible, we ask for the following:
+
+* Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes.
+ - Fork it
+ - Create your feature branch (git checkout -b my-new-feature)
+ - Commit your changes (git commit -am 'Add some feature')
+ - Push to the branch (git push origin my-new-feature)
+ - Create new Pull Request
+
+* When you're ready to create a pull request, be sure to:
+ - Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
+ - Run `go fmt`
+ - Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
+ - Make sure `go test -race ./...` and `go build` completes.
+ NOTE: go test runs functional tests and requires you to have a AWS S3 account. Set them as environment variables
+ ``ACCESS_KEY`` and ``SECRET_KEY``. To run shorter version of the tests please use ``go test -short -race ./...``
+
+* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
+ - `minio-go` project is strictly conformant with Golang style
+ - if you happen to observe offending code, please feel free to send a pull request
diff --git a/vendor/github.com/minio/minio-go/v7/LICENSE b/vendor/github.com/minio/minio-go/v7/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/minio/minio-go/v7/MAINTAINERS.md b/vendor/github.com/minio/minio-go/v7/MAINTAINERS.md
new file mode 100644
index 00000000..f640dfb9
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/MAINTAINERS.md
@@ -0,0 +1,35 @@
+# For maintainers only
+
+## Responsibilities
+
+Please go through this link [Maintainer Responsibility](https://gist.github.com/abperiasamy/f4d9b31d3186bbd26522)
+
+### Making new releases
+Tag and sign your release commit, additionally this step requires you to have access to MinIO's trusted private key.
+```sh
+$ export GNUPGHOME=/media/${USER}/minio/trusted
+$ git tag -s 4.0.0
+$ git push
+$ git push --tags
+```
+
+### Update version
+Once release has been made update `libraryVersion` constant in `api.go` to next to be released version.
+
+```sh
+$ grep libraryVersion api.go
+ libraryVersion = "4.0.1"
+```
+
+Commit your changes
+```
+$ git commit -a -m "Update version for next release" --author "MinIO Trusted <trusted@min.io>"
+```
+
+### Announce
+Announce new release by adding release notes at https://github.com/minio/minio-go/releases from `trusted@min.io` account. Release notes requires two sections `highlights` and `changelog`. Highlights is a bulleted list of salient features in this release and Changelog contains list of all commits since the last release.
+
+To generate `changelog`
+```sh
+$ git log --no-color --pretty=format:'-%d %s (%cr) <%an>' <last_release_tag>..<latest_release_tag>
+```
diff --git a/vendor/github.com/minio/minio-go/v7/Makefile b/vendor/github.com/minio/minio-go/v7/Makefile
new file mode 100644
index 00000000..ae640c4d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/Makefile
@@ -0,0 +1,35 @@
+GOPATH := $(shell go env GOPATH)
+TMPDIR := $(shell mktemp -d)
+
+all: checks
+
+.PHONY: examples docs
+
+checks: lint vet test examples functional-test
+
+lint:
+ @mkdir -p ${GOPATH}/bin
+ @which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.27.0)
+ @echo "Running $@ check"
+ @GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
+ @GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
+
+vet:
+ @GO111MODULE=on go vet ./...
+
+test:
+ @GO111MODULE=on SERVER_ENDPOINT=localhost:9000 ACCESS_KEY=minio SECRET_KEY=minio123 ENABLE_HTTPS=1 MINT_MODE=full go test -race -v ./...
+
+examples:
+ @echo "Building s3 examples"
+ @cd ./examples/s3 && $(foreach v,$(wildcard examples/s3/*.go),go build -mod=mod -o ${TMPDIR}/$(basename $(v)) $(notdir $(v)) || exit 1;)
+ @echo "Building minio examples"
+ @cd ./examples/minio && $(foreach v,$(wildcard examples/minio/*.go),go build -mod=mod -o ${TMPDIR}/$(basename $(v)) $(notdir $(v)) || exit 1;)
+
+functional-test:
+ @GO111MODULE=on SERVER_ENDPOINT=localhost:9000 ACCESS_KEY=minio SECRET_KEY=minio123 ENABLE_HTTPS=1 MINT_MODE=full go run functional_tests.go
+
+clean:
+ @echo "Cleaning up all the generated files"
+ @find . -name '*.test' | xargs rm -fv
+ @find . -name '*~' | xargs rm -fv
diff --git a/vendor/github.com/minio/minio-go/v7/NOTICE b/vendor/github.com/minio/minio-go/v7/NOTICE
new file mode 100644
index 00000000..1e8fd3b9
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/NOTICE
@@ -0,0 +1,9 @@
+MinIO Cloud Storage, (C) 2014-2020 MinIO, Inc.
+
+This product includes software developed at MinIO, Inc.
+(https://min.io/).
+
+The MinIO project contains unmodified/modified subcomponents too with
+separate copyright notices and license terms. Your use of the source
+code for these subcomponents is subject to the terms and conditions
+of Apache License Version 2.0
diff --git a/vendor/github.com/minio/minio-go/v7/README.md b/vendor/github.com/minio/minio-go/v7/README.md
new file mode 100644
index 00000000..b5c26d53
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/README.md
@@ -0,0 +1,251 @@
+# MinIO Go Client SDK for Amazon S3 Compatible Cloud Storage [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) [![Sourcegraph](https://sourcegraph.com/github.com/minio/minio-go/-/badge.svg)](https://sourcegraph.com/github.com/minio/minio-go?badge) [![Apache V2 License](http://img.shields.io/badge/license-Apache%20V2-blue.svg)](https://github.com/minio/minio-go/blob/master/LICENSE)
+
+The MinIO Go Client SDK provides simple APIs to access any Amazon S3 compatible object storage.
+
+This quickstart guide will show you how to install the MinIO client SDK, connect to MinIO, and provide a walkthrough for a simple file uploader. For a complete list of APIs and examples, please take a look at the [Go Client API Reference](https://docs.min.io/docs/golang-client-api-reference).
+
+This document assumes that you have a working [Go development environment](https://golang.org/doc/install).
+
+## Download from Github
+```sh
+GO111MODULE=on go get github.com/minio/minio-go/v7
+```
+
+## Initialize MinIO Client
+MinIO client requires the following four parameters specified to connect to an Amazon S3 compatible object storage.
+
+| Parameter | Description|
+| :--- | :--- |
+| endpoint | URL to object storage service. |
+| _minio.Options_ | All the options such as credentials, custom transport etc. |
+
+```go
+package main
+
+import (
+ "log"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+func main() {
+ endpoint := "play.min.io"
+ accessKeyID := "Q3AM3UQ867SPQQA43P2F"
+ secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
+ useSSL := true
+
+ // Initialize minio client object.
+ minioClient, err := minio.New(endpoint, &minio.Options{
+ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
+ Secure: useSSL,
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ log.Printf("%#v\n", minioClient) // minioClient is now set up
+}
+```
+
+## Quick Start Example - File Uploader
+This example program connects to an object storage server, creates a bucket and uploads a file to the bucket.
+
+We will use the MinIO server running at [https://play.min.io](https://play.min.io) in this example. Feel free to use this service for testing and development. Access credentials shown in this example are open to the public.
+
+### FileUploader.go
+```go
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+func main() {
+ ctx := context.Background()
+ endpoint := "play.min.io"
+ accessKeyID := "Q3AM3UQ867SPQQA43P2F"
+ secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
+ useSSL := true
+
+ // Initialize minio client object.
+ minioClient, err := minio.New(endpoint, &minio.Options{
+ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
+ Secure: useSSL,
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // Make a new bucket called mymusic.
+ bucketName := "mymusic"
+ location := "us-east-1"
+
+ err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location})
+ if err != nil {
+ // Check to see if we already own this bucket (which happens if you run this twice)
+ exists, errBucketExists := minioClient.BucketExists(ctx, bucketName)
+ if errBucketExists == nil && exists {
+ log.Printf("We already own %s\n", bucketName)
+ } else {
+ log.Fatalln(err)
+ }
+ } else {
+ log.Printf("Successfully created %s\n", bucketName)
+ }
+
+ // Upload the zip file
+ objectName := "golden-oldies.zip"
+ filePath := "/tmp/golden-oldies.zip"
+ contentType := "application/zip"
+
+ // Upload the zip file with FPutObject
+ info, err := minioClient.FPutObject(ctx, bucketName, objectName, filePath, minio.PutObjectOptions{ContentType: contentType})
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
+}
+```
+
+### Run FileUploader
+```sh
+export GO111MODULE=on
+go run file-uploader.go
+2016/08/13 17:03:28 Successfully created mymusic
+2016/08/13 17:03:40 Successfully uploaded golden-oldies.zip of size 16253413
+
+mc ls play/mymusic/
+[2016-05-27 16:02:16 PDT] 17MiB golden-oldies.zip
+```
+
+## API Reference
+The full API Reference is available here.
+
+* [Complete API Reference](https://docs.min.io/docs/golang-client-api-reference)
+
+### API Reference : Bucket Operations
+* [`MakeBucket`](https://docs.min.io/docs/golang-client-api-reference#MakeBucket)
+* [`ListBuckets`](https://docs.min.io/docs/golang-client-api-reference#ListBuckets)
+* [`BucketExists`](https://docs.min.io/docs/golang-client-api-reference#BucketExists)
+* [`RemoveBucket`](https://docs.min.io/docs/golang-client-api-reference#RemoveBucket)
+* [`ListObjects`](https://docs.min.io/docs/golang-client-api-reference#ListObjects)
+* [`ListIncompleteUploads`](https://docs.min.io/docs/golang-client-api-reference#ListIncompleteUploads)
+
+### API Reference : Bucket policy Operations
+* [`SetBucketPolicy`](https://docs.min.io/docs/golang-client-api-reference#SetBucketPolicy)
+* [`GetBucketPolicy`](https://docs.min.io/docs/golang-client-api-reference#GetBucketPolicy)
+
+### API Reference : Bucket notification Operations
+* [`SetBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#SetBucketNotification)
+* [`GetBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#GetBucketNotification)
+* [`RemoveAllBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#RemoveAllBucketNotification)
+* [`ListenBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#ListenBucketNotification) (MinIO Extension)
+* [`ListenNotification`](https://docs.min.io/docs/golang-client-api-reference#ListenNotification) (MinIO Extension)
+
+### API Reference : File Object Operations
+* [`FPutObject`](https://docs.min.io/docs/golang-client-api-reference#FPutObject)
+* [`FGetObject`](https://docs.min.io/docs/golang-client-api-reference#FGetObject)
+
+### API Reference : Object Operations
+* [`GetObject`](https://docs.min.io/docs/golang-client-api-reference#GetObject)
+* [`PutObject`](https://docs.min.io/docs/golang-client-api-reference#PutObject)
+* [`PutObjectStreaming`](https://docs.min.io/docs/golang-client-api-reference#PutObjectStreaming)
+* [`StatObject`](https://docs.min.io/docs/golang-client-api-reference#StatObject)
+* [`CopyObject`](https://docs.min.io/docs/golang-client-api-reference#CopyObject)
+* [`RemoveObject`](https://docs.min.io/docs/golang-client-api-reference#RemoveObject)
+* [`RemoveObjects`](https://docs.min.io/docs/golang-client-api-reference#RemoveObjects)
+* [`RemoveIncompleteUpload`](https://docs.min.io/docs/golang-client-api-reference#RemoveIncompleteUpload)
+* [`SelectObjectContent`](https://docs.min.io/docs/golang-client-api-reference#SelectObjectContent)
+
+
+### API Reference : Presigned Operations
+* [`PresignedGetObject`](https://docs.min.io/docs/golang-client-api-reference#PresignedGetObject)
+* [`PresignedPutObject`](https://docs.min.io/docs/golang-client-api-reference#PresignedPutObject)
+* [`PresignedHeadObject`](https://docs.min.io/docs/golang-client-api-reference#PresignedHeadObject)
+* [`PresignedPostPolicy`](https://docs.min.io/docs/golang-client-api-reference#PresignedPostPolicy)
+
+### API Reference : Client custom settings
+* [`SetAppInfo`](http://docs.min.io/docs/golang-client-api-reference#SetAppInfo)
+* [`TraceOn`](http://docs.min.io/docs/golang-client-api-reference#TraceOn)
+* [`TraceOff`](http://docs.min.io/docs/golang-client-api-reference#TraceOff)
+
+## Full Examples
+
+### Full Examples : Bucket Operations
+* [makebucket.go](https://github.com/minio/minio-go/blob/master/examples/s3/makebucket.go)
+* [listbuckets.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbuckets.go)
+* [bucketexists.go](https://github.com/minio/minio-go/blob/master/examples/s3/bucketexists.go)
+* [removebucket.go](https://github.com/minio/minio-go/blob/master/examples/s3/removebucket.go)
+* [listobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/listobjects.go)
+* [listobjectsV2.go](https://github.com/minio/minio-go/blob/master/examples/s3/listobjectsV2.go)
+* [listincompleteuploads.go](https://github.com/minio/minio-go/blob/master/examples/s3/listincompleteuploads.go)
+
+### Full Examples : Bucket policy Operations
+* [setbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketpolicy.go)
+* [getbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketpolicy.go)
+* [listbucketpolicies.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbucketpolicies.go)
+
+### Full Examples : Bucket lifecycle Operations
+* [setbucketlifecycle.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketlifecycle.go)
+* [getbucketlifecycle.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketlifecycle.go)
+
+### Full Examples : Bucket encryption Operations
+* [setbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketencryption.go)
+* [getbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketencryption.go)
+* [deletebucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/deletebucketencryption.go)
+
+### Full Examples : Bucket replication Operations
+* [setbucketreplication.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketreplication.go)
+* [getbucketreplication.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketreplication.go)
+* [removebucketreplication.go](https://github.com/minio/minio-go/blob/master/examples/s3/removebucketreplication.go)
+
+### Full Examples : Bucket notification Operations
+* [setbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketnotification.go)
+* [getbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketnotification.go)
+* [removeallbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeallbucketnotification.go)
+* [listenbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/minio/listenbucketnotification.go) (MinIO Extension)
+* [listennotification.go](https://github.com/minio/minio-go/blob/master/examples/minio/listen-notification.go) (MinIO Extension)
+
+### Full Examples : File Object Operations
+* [fputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputobject.go)
+* [fgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/fgetobject.go)
+* [fputobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputobject-context.go)
+* [fgetobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/fgetobject-context.go)
+
+### Full Examples : Object Operations
+* [putobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/putobject.go)
+* [getobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/getobject.go)
+* [putobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/putobject-context.go)
+* [getobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/getobject-context.go)
+* [statobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/statobject.go)
+* [copyobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/copyobject.go)
+* [removeobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobject.go)
+* [removeincompleteupload.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeincompleteupload.go)
+* [removeobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobjects.go)
+
+### Full Examples : Encrypted Object Operations
+* [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go)
+* [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go)
+* [fput-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputencrypted-object.go)
+
+### Full Examples : Presigned Operations
+* [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go)
+* [presignedputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedputobject.go)
+* [presignedheadobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedheadobject.go)
+* [presignedpostpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedpostpolicy.go)
+
+## Explore Further
+* [Complete Documentation](https://docs.min.io)
+* [MinIO Go Client SDK API Reference](https://docs.min.io/docs/golang-client-api-reference)
+
+## Contribute
+[Contributors Guide](https://github.com/minio/minio-go/blob/master/CONTRIBUTING.md)
+
+## License
+This SDK is distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0), see [LICENSE](https://github.com/minio/minio-go/blob/master/LICENSE) and [NOTICE](https://github.com/minio/minio-go/blob/master/NOTICE) for more information.
diff --git a/vendor/github.com/minio/minio-go/v7/README_zh_CN.md b/vendor/github.com/minio/minio-go/v7/README_zh_CN.md
new file mode 100644
index 00000000..64e79341
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/README_zh_CN.md
@@ -0,0 +1,260 @@
+# ้€‚็”จไบŽไธŽAmazon S3ๅ…ผๅฎนไบ‘ๅญ˜ๅ‚จ็š„MinIO Go SDK [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) [![Sourcegraph](https://sourcegraph.com/github.com/minio/minio-go/-/badge.svg)](https://sourcegraph.com/github.com/minio/minio-go?badge)
+
+MinIO Go Client SDKๆไพ›ไบ†็ฎ€ๅ•็š„APIๆฅ่ฎฟ้—ฎไปปไฝ•ไธŽAmazon S3ๅ…ผๅฎน็š„ๅฏน่ฑกๅญ˜ๅ‚จๆœๅŠกใ€‚
+
+**ๆ”ฏๆŒ็š„ไบ‘ๅญ˜ๅ‚จ:**
+
+- AWS Signature Version 4
+ - Amazon S3
+ - MinIO
+
+- AWS Signature Version 2
+ - Google Cloud Storage (ๅ…ผๅฎนๆจกๅผ)
+ - Openstack Swift + Swift3 middleware
+ - Ceph Object Gateway
+ - Riak CS
+
+ๆœฌๆ–‡ๆˆ‘ไปฌๅฐ†ๅญฆไน ๅฆ‚ไฝ•ๅฎ‰่ฃ…MinIO client SDK๏ผŒ่ฟžๆŽฅๅˆฐMinIO๏ผŒๅนถๆไพ›ไธ€ไธ‹ๆ–‡ไปถไธŠไผ ็š„็คบไพ‹ใ€‚ๅฏนไบŽๅฎŒๆ•ด็š„APIไปฅๅŠ็คบไพ‹๏ผŒ่ฏทๅ‚่€ƒ[Go Client API Reference](https://docs.min.io/docs/golang-client-api-reference)ใ€‚
+
+ๆœฌๆ–‡ๅ‡่ฎพไฝ ๅทฒ็ปๆœ‰ [Goๅผ€ๅ‘็Žฏๅขƒ](https://golang.org/doc/install)ใ€‚
+
+## ไปŽGithubไธ‹่ฝฝ
+```sh
+go get -u github.com/minio/minio-go
+```
+
+## ๅˆๅง‹ๅŒ–MinIO Client
+MinIO client้œ€่ฆไปฅไธ‹4ไธชๅ‚ๆ•ฐๆฅ่ฟžๆŽฅไธŽAmazon S3ๅ…ผๅฎน็š„ๅฏน่ฑกๅญ˜ๅ‚จใ€‚
+
+| ๅ‚ๆ•ฐ | ๆ่ฟฐ|
+| :--- | :--- |
+| endpoint | ๅฏน่ฑกๅญ˜ๅ‚จๆœๅŠก็š„URL |
+| accessKeyID | Access keyๆ˜ฏๅ”ฏไธ€ๆ ‡่ฏ†ไฝ ็š„่ดฆๆˆท็š„็”จๆˆทIDใ€‚ |
+| secretAccessKey | Secret keyๆ˜ฏไฝ ่ดฆๆˆท็š„ๅฏ†็ ใ€‚ |
+| secure | trueไปฃ่กจไฝฟ็”จHTTPS |
+
+
+```go
+package main
+
+import (
+ "log"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+func main() {
+ endpoint := "play.min.io"
+ accessKeyID := "Q3AM3UQ867SPQQA43P2F"
+ secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
+ useSSL := true
+
+ // ๅˆไฝฟๅŒ– minio clientๅฏน่ฑกใ€‚
+ minioClient, err := minio.New(endpoint, &minio.Options{
+ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
+ Secure: useSSL,
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ log.Printf("%#v\n", minioClient) // minioClientๅˆไฝฟๅŒ–ๆˆๅŠŸ
+}
+```
+
+## ็คบไพ‹-ๆ–‡ไปถไธŠไผ 
+ๆœฌ็คบไพ‹่ฟžๆŽฅๅˆฐไธ€ไธชๅฏน่ฑกๅญ˜ๅ‚จๆœๅŠก๏ผŒๅˆ›ๅปบไธ€ไธชๅญ˜ๅ‚จๆกถๅนถไธŠไผ ไธ€ไธชๆ–‡ไปถๅˆฐๅญ˜ๅ‚จๆกถไธญใ€‚
+
+ๆˆ‘ไปฌๅœจๆœฌ็คบไพ‹ไธญไฝฟ็”จ่ฟ่กŒๅœจ [https://play.min.io](https://play.min.io) ไธŠ็š„MinIOๆœๅŠก๏ผŒไฝ ๅฏไปฅ็”จ่ฟ™ไธชๆœๅŠกๆฅๅผ€ๅ‘ๅ’Œๆต‹่ฏ•ใ€‚็คบไพ‹ไธญ็š„่ฎฟ้—ฎๅ‡ญๆฎๆ˜ฏๅ…ฌๅผ€็š„ใ€‚
+
+### FileUploader.go
+```go
+package main
+
+import (
+ "context"
+ "log"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+func main() {
+ ctx := context.Background()
+ endpoint := "play.min.io"
+ accessKeyID := "Q3AM3UQ867SPQQA43P2F"
+ secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
+ useSSL := true
+
+ // ๅˆไฝฟๅŒ– minio clientๅฏน่ฑกใ€‚
+ minioClient, err := minio.New(endpoint, &minio.Options{
+ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
+ Secure: useSSL,
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // ๅˆ›ๅปบไธ€ไธชๅซmymusic็š„ๅญ˜ๅ‚จๆกถใ€‚
+ bucketName := "mymusic"
+ location := "us-east-1"
+
+ err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location})
+ if err != nil {
+ // ๆฃ€ๆŸฅๅญ˜ๅ‚จๆกถๆ˜ฏๅฆๅทฒ็ปๅญ˜ๅœจใ€‚
+ exists, errBucketExists := minioClient.BucketExists(ctx, bucketName)
+ if errBucketExists == nil && exists {
+ log.Printf("We already own %s\n", bucketName)
+ } else {
+ log.Fatalln(err)
+ }
+ } else {
+ log.Printf("Successfully created %s\n", bucketName)
+ }
+
+ // ไธŠไผ ไธ€ไธชzipๆ–‡ไปถใ€‚
+ objectName := "golden-oldies.zip"
+ filePath := "/tmp/golden-oldies.zip"
+ contentType := "application/zip"
+
+ // ไฝฟ็”จFPutObjectไธŠไผ ไธ€ไธชzipๆ–‡ไปถใ€‚
+ n, err := minioClient.FPutObject(ctx, bucketName, objectName, filePath, minio.PutObjectOptions{ContentType: contentType})
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ log.Printf("Successfully uploaded %s of size %d\n", objectName, n)
+}
+```
+
+### ่ฟ่กŒFileUploader
+```sh
+go run file-uploader.go
+2016/08/13 17:03:28 Successfully created mymusic
+2016/08/13 17:03:40 Successfully uploaded golden-oldies.zip of size 16253413
+
+mc ls play/mymusic/
+[2016-05-27 16:02:16 PDT] 17MiB golden-oldies.zip
+```
+
+## APIๆ–‡ๆกฃ
+ๅฎŒๆ•ด็š„APIๆ–‡ๆกฃๅœจ่ฟ™้‡Œใ€‚
+* [ๅฎŒๆ•ดAPIๆ–‡ๆกฃ](https://docs.min.io/docs/golang-client-api-reference)
+
+### APIๆ–‡ๆกฃ : ๆ“ไฝœๅญ˜ๅ‚จๆกถ
+* [`MakeBucket`](https://docs.min.io/docs/golang-client-api-reference#MakeBucket)
+* [`ListBuckets`](https://docs.min.io/docs/golang-client-api-reference#ListBuckets)
+* [`BucketExists`](https://docs.min.io/docs/golang-client-api-reference#BucketExists)
+* [`RemoveBucket`](https://docs.min.io/docs/golang-client-api-reference#RemoveBucket)
+* [`ListObjects`](https://docs.min.io/docs/golang-client-api-reference#ListObjects)
+* [`ListIncompleteUploads`](https://docs.min.io/docs/golang-client-api-reference#ListIncompleteUploads)
+
+### APIๆ–‡ๆกฃ : ๅญ˜ๅ‚จๆกถ็ญ–็•ฅ
+* [`SetBucketPolicy`](https://docs.min.io/docs/golang-client-api-reference#SetBucketPolicy)
+* [`GetBucketPolicy`](https://docs.min.io/docs/golang-client-api-reference#GetBucketPolicy)
+
+### APIๆ–‡ๆกฃ : ๅญ˜ๅ‚จๆกถ้€š็Ÿฅ
+* [`SetBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#SetBucketNotification)
+* [`GetBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#GetBucketNotification)
+* [`RemoveAllBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#RemoveAllBucketNotification)
+* [`ListenBucketNotification`](https://docs.min.io/docs/golang-client-api-reference#ListenBucketNotification) (MinIO ๆ‰ฉๅฑ•)
+* [`ListenNotification`](https://docs.min.io/docs/golang-client-api-reference#ListenNotification) (MinIO ๆ‰ฉๅฑ•)
+
+### APIๆ–‡ๆกฃ : ๆ“ไฝœๆ–‡ไปถๅฏน่ฑก
+* [`FPutObject`](https://docs.min.io/docs/golang-client-api-reference#FPutObject)
+* [`FGetObject`](https://docs.min.io/docs/golang-client-api-reference#FPutObject)
+
+### APIๆ–‡ๆกฃ : ๆ“ไฝœๅฏน่ฑก
+* [`GetObject`](https://docs.min.io/docs/golang-client-api-reference#GetObject)
+* [`PutObject`](https://docs.min.io/docs/golang-client-api-reference#PutObject)
+* [`PutObjectStreaming`](https://docs.min.io/docs/golang-client-api-reference#PutObjectStreaming)
+* [`StatObject`](https://docs.min.io/docs/golang-client-api-reference#StatObject)
+* [`CopyObject`](https://docs.min.io/docs/golang-client-api-reference#CopyObject)
+* [`RemoveObject`](https://docs.min.io/docs/golang-client-api-reference#RemoveObject)
+* [`RemoveObjects`](https://docs.min.io/docs/golang-client-api-reference#RemoveObjects)
+* [`RemoveIncompleteUpload`](https://docs.min.io/docs/golang-client-api-reference#RemoveIncompleteUpload)
+* [`SelectObjectContent`](https://docs.min.io/docs/golang-client-api-reference#SelectObjectContent)
+
+### APIๆ–‡ๆกฃ : Presignedๆ“ไฝœ
+* [`PresignedGetObject`](https://docs.min.io/docs/golang-client-api-reference#PresignedGetObject)
+* [`PresignedPutObject`](https://docs.min.io/docs/golang-client-api-reference#PresignedPutObject)
+* [`PresignedHeadObject`](https://docs.min.io/docs/golang-client-api-reference#PresignedHeadObject)
+* [`PresignedPostPolicy`](https://docs.min.io/docs/golang-client-api-reference#PresignedPostPolicy)
+
+### APIๆ–‡ๆกฃ : ๅฎขๆˆท็ซฏ่‡ชๅฎšไน‰่ฎพ็ฝฎ
+* [`SetAppInfo`](http://docs.min.io/docs/golang-client-api-reference#SetAppInfo)
+* [`TraceOn`](http://docs.min.io/docs/golang-client-api-reference#TraceOn)
+* [`TraceOff`](http://docs.min.io/docs/golang-client-api-reference#TraceOff)
+
+## ๅฎŒๆ•ด็คบไพ‹
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๆ“ไฝœๅญ˜ๅ‚จๆกถ
+* [makebucket.go](https://github.com/minio/minio-go/blob/master/examples/s3/makebucket.go)
+* [listbuckets.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbuckets.go)
+* [bucketexists.go](https://github.com/minio/minio-go/blob/master/examples/s3/bucketexists.go)
+* [removebucket.go](https://github.com/minio/minio-go/blob/master/examples/s3/removebucket.go)
+* [listobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/listobjects.go)
+* [listobjectsV2.go](https://github.com/minio/minio-go/blob/master/examples/s3/listobjectsV2.go)
+* [listincompleteuploads.go](https://github.com/minio/minio-go/blob/master/examples/s3/listincompleteuploads.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๅญ˜ๅ‚จๆกถ็ญ–็•ฅ
+* [setbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketpolicy.go)
+* [getbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketpolicy.go)
+* [listbucketpolicies.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbucketpolicies.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๅญ˜ๅ‚จๆกถ็”Ÿๅ‘ฝๅ‘จๆœŸ
+* [setbucketlifecycle.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketlifecycle.go)
+* [getbucketlifecycle.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketlifecycle.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๅญ˜ๅ‚จๆกถๅŠ ๅฏ†
+* [setbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketencryption.go)
+* [getbucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketencryption.go)
+* [deletebucketencryption.go](https://github.com/minio/minio-go/blob/master/examples/s3/deletebucketencryption.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๅญ˜ๅ‚จๆกถๅคๅˆถ
+* [setbucketreplication.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketreplication.go)
+* [getbucketreplication.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketreplication.go)
+* [removebucketreplication.go](https://github.com/minio/minio-go/blob/master/examples/s3/removebucketreplication.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๅญ˜ๅ‚จๆกถ้€š็Ÿฅ
+* [setbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketnotification.go)
+* [getbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketnotification.go)
+* [removeallbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeallbucketnotification.go)
+* [listenbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/minio/listenbucketnotification.go) (MinIOๆ‰ฉๅฑ•)
+* [listennotification.go](https://github.com/minio/minio-go/blob/master/examples/minio/listen-notification.go) (MinIO ๆ‰ฉๅฑ•)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๆ“ไฝœๆ–‡ไปถๅฏน่ฑก
+* [fputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputobject.go)
+* [fgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/fgetobject.go)
+* [fputobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputobject-context.go)
+* [fgetobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/fgetobject-context.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๆ“ไฝœๅฏน่ฑก
+* [putobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/putobject.go)
+* [getobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/getobject.go)
+* [putobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/putobject-context.go)
+* [getobject-context.go](https://github.com/minio/minio-go/blob/master/examples/s3/getobject-context.go)
+* [statobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/statobject.go)
+* [copyobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/copyobject.go)
+* [removeobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobject.go)
+* [removeincompleteupload.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeincompleteupload.go)
+* [removeobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobjects.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : ๆ“ไฝœๅŠ ๅฏ†ๅฏน่ฑก
+* [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go)
+* [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go)
+* [fput-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputencrypted-object.go)
+
+### ๅฎŒๆ•ด็คบไพ‹ : Presignedๆ“ไฝœ
+* [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go)
+* [presignedputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedputobject.go)
+* [presignedheadobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedheadobject.go)
+* [presignedpostpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedpostpolicy.go)
+
+## ไบ†่งฃๆ›ดๅคš
+* [ๅฎŒๆ•ดๆ–‡ๆกฃ](https://docs.min.io)
+* [MinIO Go Client SDK APIๆ–‡ๆกฃ](https://docs.min.io/docs/golang-client-api-reference)
+
+## ่ดก็Œฎ
+[่ดก็ŒฎๆŒ‡ๅ—](https://github.com/minio/minio-go/blob/master/docs/zh_CN/CONTRIBUTING.md)
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-encryption.go b/vendor/github.com/minio/minio-go/v7/api-bucket-encryption.go
new file mode 100644
index 00000000..e02ab84a
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-encryption.go
@@ -0,0 +1,134 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/sse"
+)
+
+// SetBucketEncryption sets the default encryption configuration on an existing bucket.
+func (c Client) SetBucketEncryption(ctx context.Context, bucketName string, config *sse.Configuration) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ if config == nil {
+ return errInvalidArgument("configuration cannot be empty")
+ }
+
+ buf, err := xml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("encryption", "")
+
+ // Content-length is mandatory to set a default encryption configuration
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(buf),
+ contentLength: int64(len(buf)),
+ contentMD5Base64: sumMD5Base64(buf),
+ }
+
+ // Execute PUT to upload a new bucket default encryption configuration.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ return nil
+}
+
+// RemoveBucketEncryption removes the default encryption configuration on a bucket with a context to control cancellations and timeouts.
+func (c Client) RemoveBucketEncryption(ctx context.Context, bucketName string) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("encryption", "")
+
+ // DELETE default encryption configuration on a bucket.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ return nil
+}
+
+// GetBucketEncryption gets the default encryption configuration
+// on an existing bucket with a context to control cancellations and timeouts.
+func (c Client) GetBucketEncryption(ctx context.Context, bucketName string) (*sse.Configuration, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("encryption", "")
+
+ // Execute GET on bucket to get the default encryption configuration.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ encryptionConfig := &sse.Configuration{}
+ if err = xmlDecoder(resp.Body, encryptionConfig); err != nil {
+ return nil, err
+ }
+
+ return encryptionConfig, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-lifecycle.go b/vendor/github.com/minio/minio-go/v7/api-bucket-lifecycle.go
new file mode 100644
index 00000000..e1fac813
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-lifecycle.go
@@ -0,0 +1,147 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/lifecycle"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// SetBucketLifecycle set the lifecycle on an existing bucket.
+func (c Client) SetBucketLifecycle(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // If lifecycle is empty then delete it.
+ if config.Empty() {
+ return c.removeBucketLifecycle(ctx, bucketName)
+ }
+
+ buf, err := xml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ // Save the updated lifecycle.
+ return c.putBucketLifecycle(ctx, bucketName, buf)
+}
+
+// Saves a new bucket lifecycle.
+func (c Client) putBucketLifecycle(ctx context.Context, bucketName string, buf []byte) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("lifecycle", "")
+
+ // Content-length is mandatory for put lifecycle request
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(buf),
+ contentLength: int64(len(buf)),
+ contentMD5Base64: sumMD5Base64(buf),
+ }
+
+ // Execute PUT to upload a new bucket lifecycle.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return nil
+}
+
+// Remove lifecycle from a bucket.
+func (c Client) removeBucketLifecycle(ctx context.Context, bucketName string) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("lifecycle", "")
+
+ // Execute DELETE on objectName.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// GetBucketLifecycle fetch bucket lifecycle configuration
+func (c Client) GetBucketLifecycle(ctx context.Context, bucketName string) (*lifecycle.Configuration, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+
+ bucketLifecycle, err := c.getBucketLifecycle(ctx, bucketName)
+ if err != nil {
+ return nil, err
+ }
+
+ config := lifecycle.NewConfiguration()
+ if err = xml.Unmarshal(bucketLifecycle, config); err != nil {
+ return nil, err
+ }
+ return config, nil
+}
+
+// Request server for current bucket lifecycle.
+func (c Client) getBucketLifecycle(ctx context.Context, bucketName string) ([]byte, error) {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("lifecycle", "")
+
+ // Execute GET on bucket to get lifecycle.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ return ioutil.ReadAll(resp.Body)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-notification.go b/vendor/github.com/minio/minio-go/v7/api-bucket-notification.go
new file mode 100644
index 00000000..76787eca
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-notification.go
@@ -0,0 +1,255 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "encoding/xml"
+ "net/http"
+ "net/url"
+ "time"
+
+ jsoniter "github.com/json-iterator/go"
+ "github.com/minio/minio-go/v7/pkg/notification"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// SetBucketNotification saves a new bucket notification with a context to control cancellations and timeouts.
+func (c Client) SetBucketNotification(ctx context.Context, bucketName string, config notification.Configuration) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("notification", "")
+
+ notifBytes, err := xml.Marshal(&config)
+ if err != nil {
+ return err
+ }
+
+ notifBuffer := bytes.NewReader(notifBytes)
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: notifBuffer,
+ contentLength: int64(len(notifBytes)),
+ contentMD5Base64: sumMD5Base64(notifBytes),
+ contentSHA256Hex: sum256Hex(notifBytes),
+ }
+
+ // Execute PUT to upload a new bucket notification.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return nil
+}
+
+// RemoveAllBucketNotification - Remove bucket notification clears all previously specified config
+func (c Client) RemoveAllBucketNotification(ctx context.Context, bucketName string) error {
+ return c.SetBucketNotification(ctx, bucketName, notification.Configuration{})
+}
+
+// GetBucketNotification returns current bucket notification configuration
+func (c Client) GetBucketNotification(ctx context.Context, bucketName string) (bucketNotification notification.Configuration, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return notification.Configuration{}, err
+ }
+ return c.getBucketNotification(ctx, bucketName)
+}
+
+// Request server for notification rules.
+func (c Client) getBucketNotification(ctx context.Context, bucketName string) (notification.Configuration, error) {
+ urlValues := make(url.Values)
+ urlValues.Set("notification", "")
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return notification.Configuration{}, err
+ }
+ return processBucketNotificationResponse(bucketName, resp)
+
+}
+
+// processes the GetNotification http response from the server.
+func processBucketNotificationResponse(bucketName string, resp *http.Response) (notification.Configuration, error) {
+ if resp.StatusCode != http.StatusOK {
+ errResponse := httpRespToErrorResponse(resp, bucketName, "")
+ return notification.Configuration{}, errResponse
+ }
+ var bucketNotification notification.Configuration
+ err := xmlDecoder(resp.Body, &bucketNotification)
+ if err != nil {
+ return notification.Configuration{}, err
+ }
+ return bucketNotification, nil
+}
+
+// ListenNotification listen for all events, this is a MinIO specific API
+func (c Client) ListenNotification(ctx context.Context, prefix, suffix string, events []string) <-chan notification.Info {
+ return c.ListenBucketNotification(ctx, "", prefix, suffix, events)
+}
+
+// ListenBucketNotification listen for bucket events, this is a MinIO specific API
+func (c Client) ListenBucketNotification(ctx context.Context, bucketName, prefix, suffix string, events []string) <-chan notification.Info {
+ notificationInfoCh := make(chan notification.Info, 1)
+ const notificationCapacity = 4 * 1024 * 1024
+ notificationEventBuffer := make([]byte, notificationCapacity)
+ // Only success, start a routine to start reading line by line.
+ go func(notificationInfoCh chan<- notification.Info) {
+ defer close(notificationInfoCh)
+
+ // Validate the bucket name.
+ if bucketName != "" {
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ select {
+ case notificationInfoCh <- notification.Info{
+ Err: err,
+ }:
+ case <-ctx.Done():
+ }
+ return
+ }
+ }
+
+ // Check ARN partition to verify if listening bucket is supported
+ if s3utils.IsAmazonEndpoint(*c.endpointURL) || s3utils.IsGoogleEndpoint(*c.endpointURL) {
+ select {
+ case notificationInfoCh <- notification.Info{
+ Err: errAPINotSupported("Listening for bucket notification is specific only to `minio` server endpoints"),
+ }:
+ case <-ctx.Done():
+ }
+ return
+ }
+
+ // Continuously run and listen on bucket notification.
+ // Create a done channel to control 'ListObjects' go routine.
+ retryDoneCh := make(chan struct{}, 1)
+
+ // Indicate to our routine to exit cleanly upon return.
+ defer close(retryDoneCh)
+
+ // Prepare urlValues to pass into the request on every loop
+ urlValues := make(url.Values)
+ urlValues.Set("prefix", prefix)
+ urlValues.Set("suffix", suffix)
+ urlValues["events"] = events
+
+ // Wait on the jitter retry loop.
+ for range c.newRetryTimerContinous(time.Second, time.Second*30, MaxJitter, retryDoneCh) {
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ if err != nil {
+ select {
+ case notificationInfoCh <- notification.Info{
+ Err: err,
+ }:
+ case <-ctx.Done():
+ }
+ return
+ }
+
+ // Validate http response, upon error return quickly.
+ if resp.StatusCode != http.StatusOK {
+ errResponse := httpRespToErrorResponse(resp, bucketName, "")
+ select {
+ case notificationInfoCh <- notification.Info{
+ Err: errResponse,
+ }:
+ case <-ctx.Done():
+ }
+ return
+ }
+
+ // Initialize a new bufio scanner, to read line by line.
+ bio := bufio.NewScanner(resp.Body)
+
+ // Use a higher buffer to support unexpected
+ // caching done by proxies
+ bio.Buffer(notificationEventBuffer, notificationCapacity)
+ var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+ // Unmarshal each line, returns marshaled values.
+ for bio.Scan() {
+ var notificationInfo notification.Info
+ if err = json.Unmarshal(bio.Bytes(), &notificationInfo); err != nil {
+ // Unexpected error during json unmarshal, send
+ // the error to caller for actionable as needed.
+ select {
+ case notificationInfoCh <- notification.Info{
+ Err: err,
+ }:
+ case <-ctx.Done():
+ return
+ }
+ closeResponse(resp)
+ continue
+ }
+ // Send notificationInfo
+ select {
+ case notificationInfoCh <- notificationInfo:
+ case <-ctx.Done():
+ closeResponse(resp)
+ return
+ }
+ }
+
+ if err = bio.Err(); err != nil {
+ select {
+ case notificationInfoCh <- notification.Info{
+ Err: err,
+ }:
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // Close current connection before looping further.
+ closeResponse(resp)
+
+ }
+ }(notificationInfoCh)
+
+ // Returns the notification info channel, for caller to start reading from.
+ return notificationInfoCh
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-policy.go b/vendor/github.com/minio/minio-go/v7/api-bucket-policy.go
new file mode 100644
index 00000000..7e01275d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-policy.go
@@ -0,0 +1,142 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// SetBucketPolicy sets the access permissions on an existing bucket.
+func (c Client) SetBucketPolicy(ctx context.Context, bucketName, policy string) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // If policy is empty then delete the bucket policy.
+ if policy == "" {
+ return c.removeBucketPolicy(ctx, bucketName)
+ }
+
+ // Save the updated policies.
+ return c.putBucketPolicy(ctx, bucketName, policy)
+}
+
+// Saves a new bucket policy.
+func (c Client) putBucketPolicy(ctx context.Context, bucketName, policy string) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("policy", "")
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: strings.NewReader(policy),
+ contentLength: int64(len(policy)),
+ }
+
+ // Execute PUT to upload a new bucket policy.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return nil
+}
+
+// Removes all policies on a bucket.
+func (c Client) removeBucketPolicy(ctx context.Context, bucketName string) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("policy", "")
+
+ // Execute DELETE on objectName.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// GetBucketPolicy returns the current policy
+func (c Client) GetBucketPolicy(ctx context.Context, bucketName string) (string, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return "", err
+ }
+ bucketPolicy, err := c.getBucketPolicy(ctx, bucketName)
+ if err != nil {
+ errResponse := ToErrorResponse(err)
+ if errResponse.Code == "NoSuchBucketPolicy" {
+ return "", nil
+ }
+ return "", err
+ }
+ return bucketPolicy, nil
+}
+
+// Request server for current bucket policy.
+func (c Client) getBucketPolicy(ctx context.Context, bucketName string) (string, error) {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("policy", "")
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return "", err
+ }
+
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return "", httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ bucketPolicyBuf, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+
+ policy := string(bucketPolicyBuf)
+ return policy, err
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-replication.go b/vendor/github.com/minio/minio-go/v7/api-bucket-replication.go
new file mode 100644
index 00000000..41054e13
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-replication.go
@@ -0,0 +1,228 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "encoding/xml"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/replication"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// RemoveBucketReplication removes a replication config on an existing bucket.
+func (c Client) RemoveBucketReplication(ctx context.Context, bucketName string) error {
+ return c.removeBucketReplication(ctx, bucketName)
+}
+
+// SetBucketReplication sets a replication config on an existing bucket.
+func (c Client) SetBucketReplication(ctx context.Context, bucketName string, cfg replication.Config) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // If replication is empty then delete it.
+ if cfg.Empty() {
+ return c.removeBucketReplication(ctx, bucketName)
+ }
+ // Save the updated replication.
+ return c.putBucketReplication(ctx, bucketName, cfg)
+}
+
+// Saves a new bucket replication.
+func (c Client) putBucketReplication(ctx context.Context, bucketName string, cfg replication.Config) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("replication", "")
+ replication, err := xml.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(replication),
+ contentLength: int64(len(replication)),
+ contentMD5Base64: sumMD5Base64(replication),
+ }
+
+ // Execute PUT to upload a new bucket replication config.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ return nil
+}
+
+// Remove replication from a bucket.
+func (c Client) removeBucketReplication(ctx context.Context, bucketName string) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("replication", "")
+
+ // Execute DELETE on objectName.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// GetBucketReplication fetches bucket replication configuration.If config is not
+// found, returns empty config with nil error.
+func (c Client) GetBucketReplication(ctx context.Context, bucketName string) (cfg replication.Config, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return cfg, err
+ }
+ bucketReplicationCfg, err := c.getBucketReplication(ctx, bucketName)
+ if err != nil {
+ errResponse := ToErrorResponse(err)
+ if errResponse.Code == "ReplicationConfigurationNotFoundError" {
+ return cfg, nil
+ }
+ return cfg, err
+ }
+ return bucketReplicationCfg, nil
+}
+
+// Request server for current bucket replication config.
+func (c Client) getBucketReplication(ctx context.Context, bucketName string) (cfg replication.Config, err error) {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("replication", "")
+
+ // Execute GET on bucket to get replication config.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return cfg, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return cfg, httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ if err = xmlDecoder(resp.Body, &cfg); err != nil {
+ return cfg, err
+ }
+
+ return cfg, nil
+}
+
+// GetBucketReplicationMetrics fetches bucket replication status metrics
+func (c Client) GetBucketReplicationMetrics(ctx context.Context, bucketName string) (s replication.Metrics, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return s, err
+ }
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("replication-metrics", "")
+
+ // Execute GET on bucket to get replication config.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return s, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return s, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ respBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return s, err
+ }
+
+ if err := json.Unmarshal(respBytes, &s); err != nil {
+ return s, err
+ }
+ return s, nil
+}
+
+// ResetBucketReplication kicks off replication of previously replicated objects if ExistingObjectReplication
+// is enabled in the replication config
+func (c Client) ResetBucketReplication(ctx context.Context, bucketName string, olderThan time.Duration) (resetID string, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return "", err
+ }
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("replication-reset", "")
+ if olderThan > 0 {
+ urlValues.Set("older-than", olderThan.String())
+ }
+
+ // Execute GET on bucket to get replication config.
+ resp, err := c.executeMethod(ctx, http.MethodPut, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return "", err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return "", httpRespToErrorResponse(resp, bucketName, "")
+ }
+ respBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+
+ if err := json.Unmarshal(respBytes, &resetID); err != nil {
+ return "", err
+ }
+ return resetID, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-tagging.go b/vendor/github.com/minio/minio-go/v7/api-bucket-tagging.go
new file mode 100644
index 00000000..fcb966e6
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-tagging.go
@@ -0,0 +1,135 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/tags"
+)
+
+// GetBucketTagging fetch tagging configuration for a bucket with a
+// context to control cancellations and timeouts.
+func (c Client) GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("tagging", "")
+
+ // Execute GET on bucket to get tagging configuration.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ defer io.Copy(ioutil.Discard, resp.Body)
+ return tags.ParseBucketXML(resp.Body)
+}
+
+// SetBucketTagging sets tagging configuration for a bucket
+// with a context to control cancellations and timeouts.
+func (c Client) SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ if tags == nil {
+ return errors.New("nil tags passed")
+ }
+
+ buf, err := xml.Marshal(tags)
+ if err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("tagging", "")
+
+ // Content-length is mandatory to set a default encryption configuration
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(buf),
+ contentLength: int64(len(buf)),
+ contentMD5Base64: sumMD5Base64(buf),
+ }
+
+ // Execute PUT on bucket to put tagging configuration.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ return nil
+}
+
+// RemoveBucketTagging removes tagging configuration for a
+// bucket with a context to control cancellations and timeouts.
+func (c Client) RemoveBucketTagging(ctx context.Context, bucketName string) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("tagging", "")
+
+ // Execute DELETE on bucket to remove tagging configuration.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ return nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-bucket-versioning.go b/vendor/github.com/minio/minio-go/v7/api-bucket-versioning.go
new file mode 100644
index 00000000..e3ceeb33
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-bucket-versioning.go
@@ -0,0 +1,137 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// SetBucketVersioning sets a bucket versioning configuration
+func (c Client) SetBucketVersioning(ctx context.Context, bucketName string, config BucketVersioningConfiguration) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ buf, err := xml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("versioning", "")
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(buf),
+ contentLength: int64(len(buf)),
+ contentMD5Base64: sumMD5Base64(buf),
+ contentSHA256Hex: sum256Hex(buf),
+ }
+
+ // Execute PUT to set a bucket versioning.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return nil
+}
+
+// EnableVersioning - enable object versioning in given bucket.
+func (c Client) EnableVersioning(ctx context.Context, bucketName string) error {
+ return c.SetBucketVersioning(ctx, bucketName, BucketVersioningConfiguration{Status: "Enabled"})
+}
+
+// SuspendVersioning - suspend object versioning in given bucket.
+func (c Client) SuspendVersioning(ctx context.Context, bucketName string) error {
+ return c.SetBucketVersioning(ctx, bucketName, BucketVersioningConfiguration{Status: "Suspended"})
+}
+
+// BucketVersioningConfiguration is the versioning configuration structure
+type BucketVersioningConfiguration struct {
+ XMLName xml.Name `xml:"VersioningConfiguration"`
+ Status string `xml:"Status"`
+ MFADelete string `xml:"MfaDelete,omitempty"`
+}
+
+// Various supported states
+const (
+ Enabled = "Enabled"
+ // Disabled State = "Disabled" only used by MFA Delete not supported yet.
+ Suspended = "Suspended"
+)
+
+// Enabled returns true if bucket versioning is enabled
+func (b BucketVersioningConfiguration) Enabled() bool {
+ return b.Status == Enabled
+}
+
+// Suspended returns true if bucket versioning is suspended
+func (b BucketVersioningConfiguration) Suspended() bool {
+ return b.Status == Suspended
+}
+
+// GetBucketVersioning gets the versioning configuration on
+// an existing bucket with a context to control cancellations and timeouts.
+func (c Client) GetBucketVersioning(ctx context.Context, bucketName string) (BucketVersioningConfiguration, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return BucketVersioningConfiguration{}, err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("versioning", "")
+
+ // Execute GET on bucket to get the versioning configuration.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return BucketVersioningConfiguration{}, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return BucketVersioningConfiguration{}, httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ versioningConfig := BucketVersioningConfiguration{}
+ if err = xmlDecoder(resp.Body, &versioningConfig); err != nil {
+ return versioningConfig, err
+ }
+
+ return versioningConfig, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-compose-object.go b/vendor/github.com/minio/minio-go/v7/api-compose-object.go
new file mode 100644
index 00000000..dd597e46
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-compose-object.go
@@ -0,0 +1,580 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017, 2018 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// CopyDestOptions represents options specified by user for CopyObject/ComposeObject APIs
+type CopyDestOptions struct {
+ Bucket string // points to destination bucket
+ Object string // points to destination object
+
+ // `Encryption` is the key info for server-side-encryption with customer
+ // provided key. If it is nil, no encryption is performed.
+ Encryption encrypt.ServerSide
+
+ // `userMeta` is the user-metadata key-value pairs to be set on the
+ // destination. The keys are automatically prefixed with `x-amz-meta-`
+ // if needed. If nil is passed, and if only a single source (of any
+ // size) is provided in the ComposeObject call, then metadata from the
+ // source is copied to the destination.
+ // if no user-metadata is provided, it is copied from source
+ // (when there is only once source object in the compose
+ // request)
+ UserMetadata map[string]string
+ // UserMetadata is only set to destination if ReplaceMetadata is true
+ // other value is UserMetadata is ignored and we preserve src.UserMetadata
+ // NOTE: if you set this value to true and now metadata is present
+ // in UserMetadata your destination object will not have any metadata
+ // set.
+ ReplaceMetadata bool
+
+ // `userTags` is the user defined object tags to be set on destination.
+ // This will be set only if the `replaceTags` field is set to true.
+ // Otherwise this field is ignored
+ UserTags map[string]string
+ ReplaceTags bool
+
+ // Specifies whether you want to apply a Legal Hold to the copied object.
+ LegalHold LegalHoldStatus
+
+ // Object Retention related fields
+ Mode RetentionMode
+ RetainUntilDate time.Time
+
+ Size int64 // Needs to be specified if progress bar is specified.
+ // Progress of the entire copy operation will be sent here.
+ Progress io.Reader
+}
+
+// Process custom-metadata to remove a `x-amz-meta-` prefix if
+// present and validate that keys are distinct (after this
+// prefix removal).
+func filterCustomMeta(userMeta map[string]string) map[string]string {
+ m := make(map[string]string)
+ for k, v := range userMeta {
+ if strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") {
+ k = k[len("x-amz-meta-"):]
+ }
+ if _, ok := m[k]; ok {
+ continue
+ }
+ m[k] = v
+ }
+ return m
+}
+
+// Marshal converts all the CopyDestOptions into their
+// equivalent HTTP header representation
+func (opts CopyDestOptions) Marshal(header http.Header) {
+ const replaceDirective = "REPLACE"
+ if opts.ReplaceTags {
+ header.Set(amzTaggingHeaderDirective, replaceDirective)
+ if tags := s3utils.TagEncode(opts.UserTags); tags != "" {
+ header.Set(amzTaggingHeader, tags)
+ }
+ }
+
+ if opts.LegalHold != LegalHoldStatus("") {
+ header.Set(amzLegalHoldHeader, opts.LegalHold.String())
+ }
+
+ if opts.Mode != RetentionMode("") && !opts.RetainUntilDate.IsZero() {
+ header.Set(amzLockMode, opts.Mode.String())
+ header.Set(amzLockRetainUntil, opts.RetainUntilDate.Format(time.RFC3339))
+ }
+
+ if opts.Encryption != nil {
+ opts.Encryption.Marshal(header)
+ }
+
+ if opts.ReplaceMetadata {
+ header.Set("x-amz-metadata-directive", replaceDirective)
+ for k, v := range filterCustomMeta(opts.UserMetadata) {
+ if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) {
+ header.Set(k, v)
+ } else {
+ header.Set("x-amz-meta-"+k, v)
+ }
+ }
+ }
+}
+
+// toDestinationInfo returns a validated copyOptions object.
+func (opts CopyDestOptions) validate() (err error) {
+ // Input validation.
+ if err = s3utils.CheckValidBucketName(opts.Bucket); err != nil {
+ return err
+ }
+ if err = s3utils.CheckValidObjectName(opts.Object); err != nil {
+ return err
+ }
+ if opts.Progress != nil && opts.Size < 0 {
+ return errInvalidArgument("For progress bar effective size needs to be specified")
+ }
+ return nil
+}
+
+// CopySrcOptions represents a source object to be copied, using
+// server-side copying APIs.
+type CopySrcOptions struct {
+ Bucket, Object string
+ VersionID string
+ MatchETag string
+ NoMatchETag string
+ MatchModifiedSince time.Time
+ MatchUnmodifiedSince time.Time
+ MatchRange bool
+ Start, End int64
+ Encryption encrypt.ServerSide
+}
+
+// Marshal converts all the CopySrcOptions into their
+// equivalent HTTP header representation
+func (opts CopySrcOptions) Marshal(header http.Header) {
+ // Set the source header
+ header.Set("x-amz-copy-source", s3utils.EncodePath(opts.Bucket+"/"+opts.Object))
+ if opts.VersionID != "" {
+ header.Set("x-amz-copy-source", s3utils.EncodePath(opts.Bucket+"/"+opts.Object)+"?versionId="+opts.VersionID)
+ }
+
+ if opts.MatchETag != "" {
+ header.Set("x-amz-copy-source-if-match", opts.MatchETag)
+ }
+ if opts.NoMatchETag != "" {
+ header.Set("x-amz-copy-source-if-none-match", opts.NoMatchETag)
+ }
+
+ if !opts.MatchModifiedSince.IsZero() {
+ header.Set("x-amz-copy-source-if-modified-since", opts.MatchModifiedSince.Format(http.TimeFormat))
+ }
+ if !opts.MatchUnmodifiedSince.IsZero() {
+ header.Set("x-amz-copy-source-if-unmodified-since", opts.MatchUnmodifiedSince.Format(http.TimeFormat))
+ }
+
+ if opts.Encryption != nil {
+ encrypt.SSECopy(opts.Encryption).Marshal(header)
+ }
+}
+
+func (opts CopySrcOptions) validate() (err error) {
+ // Input validation.
+ if err = s3utils.CheckValidBucketName(opts.Bucket); err != nil {
+ return err
+ }
+ if err = s3utils.CheckValidObjectName(opts.Object); err != nil {
+ return err
+ }
+ if opts.Start > opts.End || opts.Start < 0 {
+ return errInvalidArgument("start must be non-negative, and start must be at most end.")
+ }
+ return nil
+}
+
+// Low level implementation of CopyObject API, supports only upto 5GiB worth of copy.
+func (c Client) copyObjectDo(ctx context.Context, srcBucket, srcObject, destBucket, destObject string,
+ metadata map[string]string, srcOpts CopySrcOptions, dstOpts PutObjectOptions) (ObjectInfo, error) {
+
+ // Build headers.
+ headers := make(http.Header)
+
+ // Set all the metadata headers.
+ for k, v := range metadata {
+ headers.Set(k, v)
+ }
+ if !dstOpts.Internal.ReplicationStatus.Empty() {
+ headers.Set(amzBucketReplicationStatus, string(dstOpts.Internal.ReplicationStatus))
+ }
+ if !dstOpts.Internal.SourceMTime.IsZero() {
+ headers.Set(minIOBucketSourceMTime, dstOpts.Internal.SourceMTime.Format(time.RFC3339Nano))
+ }
+ if dstOpts.Internal.SourceETag != "" {
+ headers.Set(minIOBucketSourceETag, dstOpts.Internal.SourceETag)
+ }
+ if dstOpts.Internal.ReplicationRequest {
+ headers.Set(minIOBucketReplicationRequest, "")
+ }
+ if len(dstOpts.UserTags) != 0 {
+ headers.Set(amzTaggingHeader, s3utils.TagEncode(dstOpts.UserTags))
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: destBucket,
+ objectName: destObject,
+ customHeader: headers,
+ }
+ if dstOpts.Internal.SourceVersionID != "" {
+ if _, err := uuid.Parse(dstOpts.Internal.SourceVersionID); err != nil {
+ return ObjectInfo{}, errInvalidArgument(err.Error())
+ }
+ urlValues := make(url.Values)
+ urlValues.Set("versionId", dstOpts.Internal.SourceVersionID)
+ reqMetadata.queryValues = urlValues
+ }
+
+ // Set the source header
+ headers.Set("x-amz-copy-source", s3utils.EncodePath(srcBucket+"/"+srcObject))
+ if srcOpts.VersionID != "" {
+ headers.Set("x-amz-copy-source", s3utils.EncodePath(srcBucket+"/"+srcObject)+"?versionId="+srcOpts.VersionID)
+ }
+ // Send upload-part-copy request
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return ObjectInfo{}, err
+ }
+
+ // Check if we got an error response.
+ if resp.StatusCode != http.StatusOK {
+ return ObjectInfo{}, httpRespToErrorResponse(resp, srcBucket, srcObject)
+ }
+
+ cpObjRes := copyObjectResult{}
+ err = xmlDecoder(resp.Body, &cpObjRes)
+ if err != nil {
+ return ObjectInfo{}, err
+ }
+
+ objInfo := ObjectInfo{
+ Key: destObject,
+ ETag: strings.Trim(cpObjRes.ETag, "\""),
+ LastModified: cpObjRes.LastModified,
+ }
+ return objInfo, nil
+}
+
+func (c Client) copyObjectPartDo(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string,
+ partID int, startOffset int64, length int64, metadata map[string]string) (p CompletePart, err error) {
+
+ headers := make(http.Header)
+
+ // Set source
+ headers.Set("x-amz-copy-source", s3utils.EncodePath(srcBucket+"/"+srcObject))
+
+ if startOffset < 0 {
+ return p, errInvalidArgument("startOffset must be non-negative")
+ }
+
+ if length >= 0 {
+ headers.Set("x-amz-copy-source-range", fmt.Sprintf("bytes=%d-%d", startOffset, startOffset+length-1))
+ }
+
+ for k, v := range metadata {
+ headers.Set(k, v)
+ }
+
+ queryValues := make(url.Values)
+ queryValues.Set("partNumber", strconv.Itoa(partID))
+ queryValues.Set("uploadId", uploadID)
+
+ resp, err := c.executeMethod(ctx, http.MethodPut, requestMetadata{
+ bucketName: destBucket,
+ objectName: destObject,
+ customHeader: headers,
+ queryValues: queryValues,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return
+ }
+
+ // Check if we got an error response.
+ if resp.StatusCode != http.StatusOK {
+ return p, httpRespToErrorResponse(resp, destBucket, destObject)
+ }
+
+ // Decode copy-part response on success.
+ cpObjRes := copyObjectResult{}
+ err = xmlDecoder(resp.Body, &cpObjRes)
+ if err != nil {
+ return p, err
+ }
+ p.PartNumber, p.ETag = partID, cpObjRes.ETag
+ return p, nil
+}
+
+// uploadPartCopy - helper function to create a part in a multipart
+// upload via an upload-part-copy request
+// https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPartCopy.html
+func (c Client) uploadPartCopy(ctx context.Context, bucket, object, uploadID string, partNumber int,
+ headers http.Header) (p CompletePart, err error) {
+
+ // Build query parameters
+ urlValues := make(url.Values)
+ urlValues.Set("partNumber", strconv.Itoa(partNumber))
+ urlValues.Set("uploadId", uploadID)
+
+ // Send upload-part-copy request
+ resp, err := c.executeMethod(ctx, http.MethodPut, requestMetadata{
+ bucketName: bucket,
+ objectName: object,
+ customHeader: headers,
+ queryValues: urlValues,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return p, err
+ }
+
+ // Check if we got an error response.
+ if resp.StatusCode != http.StatusOK {
+ return p, httpRespToErrorResponse(resp, bucket, object)
+ }
+
+ // Decode copy-part response on success.
+ cpObjRes := copyObjectResult{}
+ err = xmlDecoder(resp.Body, &cpObjRes)
+ if err != nil {
+ return p, err
+ }
+ p.PartNumber, p.ETag = partNumber, cpObjRes.ETag
+ return p, nil
+}
+
+// ComposeObject - creates an object using server-side copying
+// of existing objects. It takes a list of source objects (with optional offsets)
+// and concatenates them into a new object using only server-side copying
+// operations. Optionally takes progress reader hook for applications to
+// look at current progress.
+func (c Client) ComposeObject(ctx context.Context, dst CopyDestOptions, srcs ...CopySrcOptions) (UploadInfo, error) {
+ if len(srcs) < 1 || len(srcs) > maxPartsCount {
+ return UploadInfo{}, errInvalidArgument("There must be as least one and up to 10000 source objects.")
+ }
+
+ for _, src := range srcs {
+ if err := src.validate(); err != nil {
+ return UploadInfo{}, err
+ }
+ }
+
+ if err := dst.validate(); err != nil {
+ return UploadInfo{}, err
+ }
+
+ srcObjectInfos := make([]ObjectInfo, len(srcs))
+ srcObjectSizes := make([]int64, len(srcs))
+ var totalSize, totalParts int64
+ var err error
+ for i, src := range srcs {
+ opts := StatObjectOptions{ServerSideEncryption: encrypt.SSE(src.Encryption), VersionID: src.VersionID}
+ srcObjectInfos[i], err = c.statObject(context.Background(), src.Bucket, src.Object, opts)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ srcCopySize := srcObjectInfos[i].Size
+ // Check if a segment is specified, and if so, is the
+ // segment within object bounds?
+ if src.MatchRange {
+ // Since range is specified,
+ // 0 <= src.start <= src.end
+ // so only invalid case to check is:
+ if src.End >= srcCopySize || src.Start < 0 {
+ return UploadInfo{}, errInvalidArgument(
+ fmt.Sprintf("CopySrcOptions %d has invalid segment-to-copy [%d, %d] (size is %d)",
+ i, src.Start, src.End, srcCopySize))
+ }
+ srcCopySize = src.End - src.Start + 1
+ }
+
+ // Only the last source may be less than `absMinPartSize`
+ if srcCopySize < absMinPartSize && i < len(srcs)-1 {
+ return UploadInfo{}, errInvalidArgument(
+ fmt.Sprintf("CopySrcOptions %d is too small (%d) and it is not the last part", i, srcCopySize))
+ }
+
+ // Is data to copy too large?
+ totalSize += srcCopySize
+ if totalSize > maxMultipartPutObjectSize {
+ return UploadInfo{}, errInvalidArgument(fmt.Sprintf("Cannot compose an object of size %d (> 5TiB)", totalSize))
+ }
+
+ // record source size
+ srcObjectSizes[i] = srcCopySize
+
+ // calculate parts needed for current source
+ totalParts += partsRequired(srcCopySize)
+ // Do we need more parts than we are allowed?
+ if totalParts > maxPartsCount {
+ return UploadInfo{}, errInvalidArgument(fmt.Sprintf(
+ "Your proposed compose object requires more than %d parts", maxPartsCount))
+ }
+ }
+
+ // Single source object case (i.e. when only one source is
+ // involved, it is being copied wholly and at most 5GiB in
+ // size, emptyfiles are also supported).
+ if (totalParts == 1 && srcs[0].Start == -1 && totalSize <= maxPartSize) || (totalSize == 0) {
+ return c.CopyObject(ctx, dst, srcs[0])
+ }
+
+ // Now, handle multipart-copy cases.
+
+ // 1. Ensure that the object has not been changed while
+ // we are copying data.
+ for i, src := range srcs {
+ src.MatchETag = srcObjectInfos[i].ETag
+ }
+
+ // 2. Initiate a new multipart upload.
+
+ // Set user-metadata on the destination object. If no
+ // user-metadata is specified, and there is only one source,
+ // (only) then metadata from source is copied.
+ var userMeta map[string]string
+ if dst.ReplaceMetadata {
+ userMeta = dst.UserMetadata
+ } else {
+ userMeta = srcObjectInfos[0].UserMetadata
+ }
+
+ var userTags map[string]string
+ if dst.ReplaceTags {
+ userTags = dst.UserTags
+ } else {
+ userTags = srcObjectInfos[0].UserTags
+ }
+
+ uploadID, err := c.newUploadID(ctx, dst.Bucket, dst.Object, PutObjectOptions{
+ ServerSideEncryption: dst.Encryption,
+ UserMetadata: userMeta,
+ UserTags: userTags,
+ Mode: dst.Mode,
+ RetainUntilDate: dst.RetainUntilDate,
+ LegalHold: dst.LegalHold,
+ })
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // 3. Perform copy part uploads
+ objParts := []CompletePart{}
+ partIndex := 1
+ for i, src := range srcs {
+ var h = make(http.Header)
+ src.Marshal(h)
+ if dst.Encryption != nil && dst.Encryption.Type() == encrypt.SSEC {
+ dst.Encryption.Marshal(h)
+ }
+
+ // calculate start/end indices of parts after
+ // splitting.
+ startIdx, endIdx := calculateEvenSplits(srcObjectSizes[i], src)
+ for j, start := range startIdx {
+ end := endIdx[j]
+
+ // Add (or reset) source range header for
+ // upload part copy request.
+ h.Set("x-amz-copy-source-range",
+ fmt.Sprintf("bytes=%d-%d", start, end))
+
+ // make upload-part-copy request
+ complPart, err := c.uploadPartCopy(ctx, dst.Bucket,
+ dst.Object, uploadID, partIndex, h)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ if dst.Progress != nil {
+ io.CopyN(ioutil.Discard, dst.Progress, end-start+1)
+ }
+ objParts = append(objParts, complPart)
+ partIndex++
+ }
+ }
+
+ // 4. Make final complete-multipart request.
+ uploadInfo, err := c.completeMultipartUpload(ctx, dst.Bucket, dst.Object, uploadID,
+ completeMultipartUpload{Parts: objParts})
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ uploadInfo.Size = totalSize
+ return uploadInfo, nil
+}
+
+// partsRequired is maximum parts possible with
+// max part size of ceiling(maxMultipartPutObjectSize / (maxPartsCount - 1))
+func partsRequired(size int64) int64 {
+ maxPartSize := maxMultipartPutObjectSize / (maxPartsCount - 1)
+ r := size / int64(maxPartSize)
+ if size%int64(maxPartSize) > 0 {
+ r++
+ }
+ return r
+}
+
+// calculateEvenSplits - computes splits for a source and returns
+// start and end index slices. Splits happen evenly to be sure that no
+// part is less than 5MiB, as that could fail the multipart request if
+// it is not the last part.
+func calculateEvenSplits(size int64, src CopySrcOptions) (startIndex, endIndex []int64) {
+ if size == 0 {
+ return
+ }
+
+ reqParts := partsRequired(size)
+ startIndex = make([]int64, reqParts)
+ endIndex = make([]int64, reqParts)
+ // Compute number of required parts `k`, as:
+ //
+ // k = ceiling(size / copyPartSize)
+ //
+ // Now, distribute the `size` bytes in the source into
+ // k parts as evenly as possible:
+ //
+ // r parts sized (q+1) bytes, and
+ // (k - r) parts sized q bytes, where
+ //
+ // size = q * k + r (by simple division of size by k,
+ // so that 0 <= r < k)
+ //
+ start := src.Start
+ if start == -1 {
+ start = 0
+ }
+ quot, rem := size/reqParts, size%reqParts
+ nextStart := start
+ for j := int64(0); j < reqParts; j++ {
+ curPartSize := quot
+ if j < rem {
+ curPartSize++
+ }
+
+ cStart := nextStart
+ cEnd := cStart + curPartSize - 1
+ nextStart = cEnd + 1
+
+ startIndex[j], endIndex[j] = cStart, cEnd
+ }
+ return
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-copy-object.go b/vendor/github.com/minio/minio-go/v7/api-copy-object.go
new file mode 100644
index 00000000..9af036ec
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-copy-object.go
@@ -0,0 +1,77 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017, 2018 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "io"
+ "io/ioutil"
+ "net/http"
+)
+
+// CopyObject - copy a source object into a new object
+func (c Client) CopyObject(ctx context.Context, dst CopyDestOptions, src CopySrcOptions) (UploadInfo, error) {
+ if err := src.validate(); err != nil {
+ return UploadInfo{}, err
+ }
+
+ if err := dst.validate(); err != nil {
+ return UploadInfo{}, err
+ }
+
+ header := make(http.Header)
+ dst.Marshal(header)
+ src.Marshal(header)
+
+ resp, err := c.executeMethod(ctx, http.MethodPut, requestMetadata{
+ bucketName: dst.Bucket,
+ objectName: dst.Object,
+ customHeader: header,
+ })
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ defer closeResponse(resp)
+
+ if resp.StatusCode != http.StatusOK {
+ return UploadInfo{}, httpRespToErrorResponse(resp, dst.Bucket, dst.Object)
+ }
+
+ // Update the progress properly after successful copy.
+ if dst.Progress != nil {
+ io.Copy(ioutil.Discard, io.LimitReader(dst.Progress, dst.Size))
+ }
+
+ cpObjRes := copyObjectResult{}
+ if err = xmlDecoder(resp.Body, &cpObjRes); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // extract lifecycle expiry date and rule ID
+ expTime, ruleID := amzExpirationToExpiryDateRuleID(resp.Header.Get(amzExpiration))
+
+ return UploadInfo{
+ Bucket: dst.Bucket,
+ Key: dst.Object,
+ LastModified: cpObjRes.LastModified,
+ ETag: trimEtag(resp.Header.Get("ETag")),
+ VersionID: resp.Header.Get(amzVersionID),
+ Expiration: expTime,
+ ExpirationRuleID: ruleID,
+ }, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-datatypes.go b/vendor/github.com/minio/minio-go/v7/api-datatypes.go
new file mode 100644
index 00000000..970e1fa5
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-datatypes.go
@@ -0,0 +1,173 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "encoding/xml"
+ "io"
+ "net/http"
+ "time"
+)
+
+// BucketInfo container for bucket metadata.
+type BucketInfo struct {
+ // The name of the bucket.
+ Name string `json:"name"`
+ // Date the bucket was created.
+ CreationDate time.Time `json:"creationDate"`
+}
+
+// StringMap represents map with custom UnmarshalXML
+type StringMap map[string]string
+
+// UnmarshalXML unmarshals the XML into a map of string to strings,
+// creating a key in the map for each tag and setting it's value to the
+// tags contents.
+//
+// The fact this function is on the pointer of Map is important, so that
+// if m is nil it can be initialized, which is often the case if m is
+// nested in another xml structural. This is also why the first thing done
+// on the first line is initialize it.
+func (m *StringMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ *m = StringMap{}
+ type xmlMapEntry struct {
+ XMLName xml.Name
+ Value string `xml:",chardata"`
+ }
+ for {
+ var e xmlMapEntry
+ err := d.Decode(&e)
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return err
+ }
+ (*m)[e.XMLName.Local] = e.Value
+ }
+ return nil
+}
+
+// Owner name.
+type Owner struct {
+ DisplayName string `json:"name"`
+ ID string `json:"id"`
+}
+
+// UploadInfo contains information about the
+// newly uploaded or copied object.
+type UploadInfo struct {
+ Bucket string
+ Key string
+ ETag string
+ Size int64
+ LastModified time.Time
+ Location string
+ VersionID string
+
+ // Lifecycle expiry-date and ruleID associated with the expiry
+ // not to be confused with `Expires` HTTP header.
+ Expiration time.Time
+ ExpirationRuleID string
+}
+
+// ObjectInfo container for object metadata.
+type ObjectInfo struct {
+ // An ETag is optionally set to md5sum of an object. In case of multipart objects,
+ // ETag is of the form MD5SUM-N where MD5SUM is md5sum of all individual md5sums of
+ // each parts concatenated into one string.
+ ETag string `json:"etag"`
+
+ Key string `json:"name"` // Name of the object
+ LastModified time.Time `json:"lastModified"` // Date and time the object was last modified.
+ Size int64 `json:"size"` // Size in bytes of the object.
+ ContentType string `json:"contentType"` // A standard MIME type describing the format of the object data.
+ Expires time.Time `json:"expires"` // The date and time at which the object is no longer able to be cached.
+
+ // Collection of additional metadata on the object.
+ // eg: x-amz-meta-*, content-encoding etc.
+ Metadata http.Header `json:"metadata" xml:"-"`
+
+ // x-amz-meta-* headers stripped "x-amz-meta-" prefix containing the first value.
+ UserMetadata StringMap `json:"userMetadata"`
+
+ // x-amz-tagging values in their k/v values.
+ UserTags map[string]string `json:"userTags"`
+
+ // x-amz-tagging-count value
+ UserTagCount int
+
+ // Owner name.
+ Owner Owner
+
+ // ACL grant.
+ Grant []struct {
+ Grantee struct {
+ ID string `xml:"ID"`
+ DisplayName string `xml:"DisplayName"`
+ URI string `xml:"URI"`
+ } `xml:"Grantee"`
+ Permission string `xml:"Permission"`
+ } `xml:"Grant"`
+
+ // The class of storage used to store the object.
+ StorageClass string `json:"storageClass"`
+
+ // Versioning related information
+ IsLatest bool
+ IsDeleteMarker bool
+ VersionID string `xml:"VersionId"`
+
+ // x-amz-replication-status value is either in one of the following states
+ // - COMPLETE
+ // - PENDING
+ // - FAILED
+ // - REPLICA (on the destination)
+ ReplicationStatus string `xml:"ReplicationStatus"`
+
+ // Lifecycle expiry-date and ruleID associated with the expiry
+ // not to be confused with `Expires` HTTP header.
+ Expiration time.Time
+ ExpirationRuleID string
+
+ // Error
+ Err error `json:"-"`
+}
+
+// ObjectMultipartInfo container for multipart object metadata.
+type ObjectMultipartInfo struct {
+ // Date and time at which the multipart upload was initiated.
+ Initiated time.Time `type:"timestamp" timestampFormat:"iso8601"`
+
+ Initiator initiator
+ Owner owner
+
+ // The type of storage to use for the object. Defaults to 'STANDARD'.
+ StorageClass string
+
+ // Key of the object for which the multipart upload was initiated.
+ Key string
+
+ // Size in bytes of the object.
+ Size int64
+
+ // Upload ID that identifies the multipart upload.
+ UploadID string `xml:"UploadId"`
+
+ // Error
+ Err error
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-error-response.go b/vendor/github.com/minio/minio-go/v7/api-error-response.go
new file mode 100644
index 00000000..c45c4fdc
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-error-response.go
@@ -0,0 +1,271 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "encoding/xml"
+ "fmt"
+ "net/http"
+)
+
+/* **** SAMPLE ERROR RESPONSE ****
+<?xml version="1.0" encoding="UTF-8"?>
+<Error>
+ <Code>AccessDenied</Code>
+ <Message>Access Denied</Message>
+ <BucketName>bucketName</BucketName>
+ <Key>objectName</Key>
+ <RequestId>F19772218238A85A</RequestId>
+ <HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
+</Error>
+*/
+
+// ErrorResponse - Is the typed error returned by all API operations.
+// ErrorResponse struct should be comparable since it is compared inside
+// golang http API (https://github.com/golang/go/issues/29768)
+type ErrorResponse struct {
+ XMLName xml.Name `xml:"Error" json:"-"`
+ Code string
+ Message string
+ BucketName string
+ Key string
+ RequestID string `xml:"RequestId"`
+ HostID string `xml:"HostId"`
+
+ // Region where the bucket is located. This header is returned
+ // only in HEAD bucket and ListObjects response.
+ Region string
+
+ // Captures the server string returned in response header.
+ Server string
+
+ // Underlying HTTP status code for the returned error
+ StatusCode int `xml:"-" json:"-"`
+}
+
+// ToErrorResponse - Returns parsed ErrorResponse struct from body and
+// http headers.
+//
+// For example:
+//
+// import s3 "github.com/minio/minio-go/v7"
+// ...
+// ...
+// reader, stat, err := s3.GetObject(...)
+// if err != nil {
+// resp := s3.ToErrorResponse(err)
+// }
+// ...
+func ToErrorResponse(err error) ErrorResponse {
+ switch err := err.(type) {
+ case ErrorResponse:
+ return err
+ default:
+ return ErrorResponse{}
+ }
+}
+
+// Error - Returns S3 error string.
+func (e ErrorResponse) Error() string {
+ if e.Message == "" {
+ msg, ok := s3ErrorResponseMap[e.Code]
+ if !ok {
+ msg = fmt.Sprintf("Error response code %s.", e.Code)
+ }
+ return msg
+ }
+ return e.Message
+}
+
+// Common string for errors to report issue location in unexpected
+// cases.
+const (
+ reportIssue = "Please report this issue at https://github.com/minio/minio-go/issues."
+)
+
+// httpRespToErrorResponse returns a new encoded ErrorResponse
+// structure as error.
+func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string) error {
+ if resp == nil {
+ msg := "Empty http response. " + reportIssue
+ return errInvalidArgument(msg)
+ }
+
+ errResp := ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Server: resp.Header.Get("Server"),
+ }
+
+ err := xmlDecoder(resp.Body, &errResp)
+ // Xml decoding failed with no body, fall back to HTTP headers.
+ if err != nil {
+ switch resp.StatusCode {
+ case http.StatusNotFound:
+ if objectName == "" {
+ errResp = ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: "NoSuchBucket",
+ Message: "The specified bucket does not exist.",
+ BucketName: bucketName,
+ }
+ } else {
+ errResp = ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: "NoSuchKey",
+ Message: "The specified key does not exist.",
+ BucketName: bucketName,
+ Key: objectName,
+ }
+ }
+ case http.StatusForbidden:
+ errResp = ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: "AccessDenied",
+ Message: "Access Denied.",
+ BucketName: bucketName,
+ Key: objectName,
+ }
+ case http.StatusConflict:
+ errResp = ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: "Conflict",
+ Message: "Bucket not empty.",
+ BucketName: bucketName,
+ }
+ case http.StatusPreconditionFailed:
+ errResp = ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: "PreconditionFailed",
+ Message: s3ErrorResponseMap["PreconditionFailed"],
+ BucketName: bucketName,
+ Key: objectName,
+ }
+ default:
+ errResp = ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: resp.Status,
+ Message: resp.Status,
+ BucketName: bucketName,
+ }
+ }
+ }
+
+ // Save hostID, requestID and region information
+ // from headers if not available through error XML.
+ if errResp.RequestID == "" {
+ errResp.RequestID = resp.Header.Get("x-amz-request-id")
+ }
+ if errResp.HostID == "" {
+ errResp.HostID = resp.Header.Get("x-amz-id-2")
+ }
+ if errResp.Region == "" {
+ errResp.Region = resp.Header.Get("x-amz-bucket-region")
+ }
+ if errResp.Code == "InvalidRegion" && errResp.Region != "" {
+ errResp.Message = fmt.Sprintf("Region does not match, expecting region โ€˜%sโ€™.", errResp.Region)
+ }
+
+ return errResp
+}
+
+// errTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
+func errTransferAccelerationBucket(bucketName string) error {
+ return ErrorResponse{
+ StatusCode: http.StatusBadRequest,
+ Code: "InvalidArgument",
+ Message: "The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods โ€˜.โ€™.",
+ BucketName: bucketName,
+ }
+}
+
+// errEntityTooLarge - Input size is larger than supported maximum.
+func errEntityTooLarge(totalSize, maxObjectSize int64, bucketName, objectName string) error {
+ msg := fmt.Sprintf("Your proposed upload size โ€˜%dโ€™ exceeds the maximum allowed object size โ€˜%dโ€™ for single PUT operation.", totalSize, maxObjectSize)
+ return ErrorResponse{
+ StatusCode: http.StatusBadRequest,
+ Code: "EntityTooLarge",
+ Message: msg,
+ BucketName: bucketName,
+ Key: objectName,
+ }
+}
+
+// errEntityTooSmall - Input size is smaller than supported minimum.
+func errEntityTooSmall(totalSize int64, bucketName, objectName string) error {
+ msg := fmt.Sprintf("Your proposed upload size โ€˜%dโ€™ is below the minimum allowed object size โ€˜0Bโ€™ for single PUT operation.", totalSize)
+ return ErrorResponse{
+ StatusCode: http.StatusBadRequest,
+ Code: "EntityTooSmall",
+ Message: msg,
+ BucketName: bucketName,
+ Key: objectName,
+ }
+}
+
+// errUnexpectedEOF - Unexpected end of file reached.
+func errUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string) error {
+ msg := fmt.Sprintf("Data read โ€˜%dโ€™ is not equal to the size โ€˜%dโ€™ of the input Reader.", totalRead, totalSize)
+ return ErrorResponse{
+ StatusCode: http.StatusBadRequest,
+ Code: "UnexpectedEOF",
+ Message: msg,
+ BucketName: bucketName,
+ Key: objectName,
+ }
+}
+
+// errInvalidBucketName - Invalid bucket name response.
+func errInvalidBucketName(message string) error {
+ return ErrorResponse{
+ StatusCode: http.StatusBadRequest,
+ Code: "InvalidBucketName",
+ Message: message,
+ RequestID: "minio",
+ }
+}
+
+// errInvalidObjectName - Invalid object name response.
+func errInvalidObjectName(message string) error {
+ return ErrorResponse{
+ StatusCode: http.StatusNotFound,
+ Code: "NoSuchKey",
+ Message: message,
+ RequestID: "minio",
+ }
+}
+
+// errInvalidArgument - Invalid argument response.
+func errInvalidArgument(message string) error {
+ return ErrorResponse{
+ StatusCode: http.StatusBadRequest,
+ Code: "InvalidArgument",
+ Message: message,
+ RequestID: "minio",
+ }
+}
+
+// errAPINotSupported - API not supported response
+// The specified API call is not supported
+func errAPINotSupported(message string) error {
+ return ErrorResponse{
+ StatusCode: http.StatusNotImplemented,
+ Code: "APINotSupported",
+ Message: message,
+ RequestID: "minio",
+ }
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-get-object-acl.go b/vendor/github.com/minio/minio-go/v7/api-get-object-acl.go
new file mode 100644
index 00000000..afa53079
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-get-object-acl.go
@@ -0,0 +1,140 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2018 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+)
+
+type accessControlPolicy struct {
+ Owner struct {
+ ID string `xml:"ID"`
+ DisplayName string `xml:"DisplayName"`
+ } `xml:"Owner"`
+ AccessControlList struct {
+ Grant []struct {
+ Grantee struct {
+ ID string `xml:"ID"`
+ DisplayName string `xml:"DisplayName"`
+ URI string `xml:"URI"`
+ } `xml:"Grantee"`
+ Permission string `xml:"Permission"`
+ } `xml:"Grant"`
+ } `xml:"AccessControlList"`
+}
+
+// GetObjectACL get object ACLs
+func (c Client) GetObjectACL(ctx context.Context, bucketName, objectName string) (*ObjectInfo, error) {
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: url.Values{
+ "acl": []string{""},
+ },
+ })
+ if err != nil {
+ return nil, err
+ }
+ defer closeResponse(resp)
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+
+ res := &accessControlPolicy{}
+
+ if err := xmlDecoder(resp.Body, res); err != nil {
+ return nil, err
+ }
+
+ objInfo, err := c.statObject(ctx, bucketName, objectName, StatObjectOptions{})
+ if err != nil {
+ return nil, err
+ }
+
+ objInfo.Owner.DisplayName = res.Owner.DisplayName
+ objInfo.Owner.ID = res.Owner.ID
+
+ objInfo.Grant = append(objInfo.Grant, res.AccessControlList.Grant...)
+
+ cannedACL := getCannedACL(res)
+ if cannedACL != "" {
+ objInfo.Metadata.Add("X-Amz-Acl", cannedACL)
+ return &objInfo, nil
+ }
+
+ grantACL := getAmzGrantACL(res)
+ for k, v := range grantACL {
+ objInfo.Metadata[k] = v
+ }
+
+ return &objInfo, nil
+}
+
+func getCannedACL(aCPolicy *accessControlPolicy) string {
+ grants := aCPolicy.AccessControlList.Grant
+
+ switch {
+ case len(grants) == 1:
+ if grants[0].Grantee.URI == "" && grants[0].Permission == "FULL_CONTROL" {
+ return "private"
+ }
+ case len(grants) == 2:
+ for _, g := range grants {
+ if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" && g.Permission == "READ" {
+ return "authenticated-read"
+ }
+ if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "READ" {
+ return "public-read"
+ }
+ if g.Permission == "READ" && g.Grantee.ID == aCPolicy.Owner.ID {
+ return "bucket-owner-read"
+ }
+ }
+ case len(grants) == 3:
+ for _, g := range grants {
+ if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "WRITE" {
+ return "public-read-write"
+ }
+ }
+ }
+ return ""
+}
+
+func getAmzGrantACL(aCPolicy *accessControlPolicy) map[string][]string {
+ grants := aCPolicy.AccessControlList.Grant
+ res := map[string][]string{}
+
+ for _, g := range grants {
+ switch {
+ case g.Permission == "READ":
+ res["X-Amz-Grant-Read"] = append(res["X-Amz-Grant-Read"], "id="+g.Grantee.ID)
+ case g.Permission == "WRITE":
+ res["X-Amz-Grant-Write"] = append(res["X-Amz-Grant-Write"], "id="+g.Grantee.ID)
+ case g.Permission == "READ_ACP":
+ res["X-Amz-Grant-Read-Acp"] = append(res["X-Amz-Grant-Read-Acp"], "id="+g.Grantee.ID)
+ case g.Permission == "WRITE_ACP":
+ res["X-Amz-Grant-Write-Acp"] = append(res["X-Amz-Grant-Write-Acp"], "id="+g.Grantee.ID)
+ case g.Permission == "FULL_CONTROL":
+ res["X-Amz-Grant-Full-Control"] = append(res["X-Amz-Grant-Full-Control"], "id="+g.Grantee.ID)
+ }
+ }
+ return res
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-get-object-file.go b/vendor/github.com/minio/minio-go/v7/api-get-object-file.go
new file mode 100644
index 00000000..bccff457
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-get-object-file.go
@@ -0,0 +1,127 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "io"
+ "os"
+ "path/filepath"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// FGetObject - download contents of an object to a local file.
+// The options can be used to specify the GET request further.
+func (c Client) FGetObject(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return err
+ }
+
+ // Verify if destination already exists.
+ st, err := os.Stat(filePath)
+ if err == nil {
+ // If the destination exists and is a directory.
+ if st.IsDir() {
+ return errInvalidArgument("fileName is a directory.")
+ }
+ }
+
+ // Proceed if file does not exist. return for all other errors.
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ }
+
+ // Extract top level directory.
+ objectDir, _ := filepath.Split(filePath)
+ if objectDir != "" {
+ // Create any missing top level directories.
+ if err := os.MkdirAll(objectDir, 0700); err != nil {
+ return err
+ }
+ }
+
+ // Gather md5sum.
+ objectStat, err := c.StatObject(ctx, bucketName, objectName, StatObjectOptions(opts))
+ if err != nil {
+ return err
+ }
+
+ // Write to a temporary file "fileName.part.minio" before saving.
+ filePartPath := filePath + objectStat.ETag + ".part.minio"
+
+ // If exists, open in append mode. If not create it as a part file.
+ filePart, err := os.OpenFile(filePartPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ return err
+ }
+
+ // If we return early with an error, be sure to close and delete
+ // filePart. If we have an error along the way there is a chance
+ // that filePart is somehow damaged, and we should discard it.
+ closeAndRemove := true
+ defer func() {
+ if closeAndRemove {
+ _ = filePart.Close()
+ _ = os.Remove(filePartPath)
+ }
+ }()
+
+ // Issue Stat to get the current offset.
+ st, err = filePart.Stat()
+ if err != nil {
+ return err
+ }
+
+ // Initialize get object request headers to set the
+ // appropriate range offsets to read from.
+ if st.Size() > 0 {
+ opts.SetRange(st.Size(), 0)
+ }
+
+ // Seek to current position for incoming reader.
+ objectReader, objectStat, _, err := c.getObject(ctx, bucketName, objectName, opts)
+ if err != nil {
+ return err
+ }
+
+ // Write to the part file.
+ if _, err = io.CopyN(filePart, objectReader, objectStat.Size); err != nil {
+ return err
+ }
+
+ // Close the file before rename, this is specifically needed for Windows users.
+ closeAndRemove = false
+ if err = filePart.Close(); err != nil {
+ return err
+ }
+
+ // Safely completed. Now commit by renaming to actual filename.
+ if err = os.Rename(filePartPath, filePath); err != nil {
+ return err
+ }
+
+ // Return.
+ return nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-get-object.go b/vendor/github.com/minio/minio-go/v7/api-get-object.go
new file mode 100644
index 00000000..ef9dd45d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-get-object.go
@@ -0,0 +1,681 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "sync"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// GetObject wrapper function that accepts a request context
+func (c Client) GetObject(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (*Object, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, err
+ }
+
+ // Detect if snowball is server location we are talking to.
+ var snowball bool
+ if location, ok := c.bucketLocCache.Get(bucketName); ok {
+ if location == "snowball" {
+ snowball = true
+ }
+ }
+
+ var (
+ err error
+ httpReader io.ReadCloser
+ objectInfo ObjectInfo
+ totalRead int
+ )
+
+ // Create request channel.
+ reqCh := make(chan getRequest)
+ // Create response channel.
+ resCh := make(chan getResponse)
+ // Create done channel.
+ doneCh := make(chan struct{})
+
+ // This routine feeds partial object data as and when the caller reads.
+ go func() {
+ defer close(reqCh)
+ defer close(resCh)
+
+ // Used to verify if etag of object has changed since last read.
+ var etag string
+
+ // Loop through the incoming control messages and read data.
+ for {
+ select {
+ // When the done channel is closed exit our routine.
+ case <-doneCh:
+ // Close the http response body before returning.
+ // This ends the connection with the server.
+ if httpReader != nil {
+ httpReader.Close()
+ }
+ return
+
+ // Gather incoming request.
+ case req := <-reqCh:
+ // If this is the first request we may not need to do a getObject request yet.
+ if req.isFirstReq {
+ // First request is a Read/ReadAt.
+ if req.isReadOp {
+ // Differentiate between wanting the whole object and just a range.
+ if req.isReadAt {
+ // If this is a ReadAt request only get the specified range.
+ // Range is set with respect to the offset and length of the buffer requested.
+ // Do not set objectInfo from the first readAt request because it will not get
+ // the whole object.
+ opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1)
+ } else if req.Offset > 0 {
+ opts.SetRange(req.Offset, 0)
+ }
+ httpReader, objectInfo, _, err = c.getObject(ctx, bucketName, objectName, opts)
+ if err != nil {
+ resCh <- getResponse{Error: err}
+ return
+ }
+ etag = objectInfo.ETag
+ // Read at least firstReq.Buffer bytes, if not we have
+ // reached our EOF.
+ size, err := readFull(httpReader, req.Buffer)
+ totalRead += size
+ if size > 0 && err == io.ErrUnexpectedEOF {
+ if int64(size) < objectInfo.Size {
+ // In situations when returned size
+ // is less than the expected content
+ // length set by the server, make sure
+ // we return io.ErrUnexpectedEOF
+ err = io.ErrUnexpectedEOF
+ } else {
+ // If an EOF happens after reading some but not
+ // all the bytes ReadFull returns ErrUnexpectedEOF
+ err = io.EOF
+ }
+ } else if size == 0 && err == io.EOF && objectInfo.Size > 0 {
+ // Special cases when server writes more data
+ // than the content-length, net/http response
+ // body returns an error, instead of converting
+ // it to io.EOF - return unexpected EOF.
+ err = io.ErrUnexpectedEOF
+ }
+ // Send back the first response.
+ resCh <- getResponse{
+ objectInfo: objectInfo,
+ Size: size,
+ Error: err,
+ didRead: true,
+ }
+ } else {
+ // First request is a Stat or Seek call.
+ // Only need to run a StatObject until an actual Read or ReadAt request comes through.
+
+ // Remove range header if already set, for stat Operations to get original file size.
+ delete(opts.headers, "Range")
+ objectInfo, err = c.statObject(ctx, bucketName, objectName, StatObjectOptions(opts))
+ if err != nil {
+ resCh <- getResponse{
+ Error: err,
+ }
+ // Exit the go-routine.
+ return
+ }
+ etag = objectInfo.ETag
+ // Send back the first response.
+ resCh <- getResponse{
+ objectInfo: objectInfo,
+ }
+ }
+ } else if req.settingObjectInfo { // Request is just to get objectInfo.
+ // Remove range header if already set, for stat Operations to get original file size.
+ delete(opts.headers, "Range")
+ // Check whether this is snowball
+ // if yes do not use If-Match feature
+ // it doesn't work.
+ if etag != "" && !snowball {
+ opts.SetMatchETag(etag)
+ }
+ objectInfo, err := c.statObject(ctx, bucketName, objectName, StatObjectOptions(opts))
+ if err != nil {
+ resCh <- getResponse{
+ Error: err,
+ }
+ // Exit the goroutine.
+ return
+ }
+ // Send back the objectInfo.
+ resCh <- getResponse{
+ objectInfo: objectInfo,
+ }
+ } else {
+ // Offset changes fetch the new object at an Offset.
+ // Because the httpReader may not be set by the first
+ // request if it was a stat or seek it must be checked
+ // if the object has been read or not to only initialize
+ // new ones when they haven't been already.
+ // All readAt requests are new requests.
+ if req.DidOffsetChange || !req.beenRead {
+ // Check whether this is snowball
+ // if yes do not use If-Match feature
+ // it doesn't work.
+ if etag != "" && !snowball {
+ opts.SetMatchETag(etag)
+ }
+ if httpReader != nil {
+ // Close previously opened http reader.
+ httpReader.Close()
+ }
+ // If this request is a readAt only get the specified range.
+ if req.isReadAt {
+ // Range is set with respect to the offset and length of the buffer requested.
+ opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1)
+ } else if req.Offset > 0 { // Range is set with respect to the offset.
+ opts.SetRange(req.Offset, 0)
+ }
+ httpReader, objectInfo, _, err = c.getObject(ctx, bucketName, objectName, opts)
+ if err != nil {
+ resCh <- getResponse{
+ Error: err,
+ }
+ return
+ }
+ totalRead = 0
+ }
+
+ // Read at least req.Buffer bytes, if not we have
+ // reached our EOF.
+ size, err := readFull(httpReader, req.Buffer)
+ totalRead += size
+ if size > 0 && err == io.ErrUnexpectedEOF {
+ if int64(totalRead) < objectInfo.Size {
+ // In situations when returned size
+ // is less than the expected content
+ // length set by the server, make sure
+ // we return io.ErrUnexpectedEOF
+ err = io.ErrUnexpectedEOF
+ } else {
+ // If an EOF happens after reading some but not
+ // all the bytes ReadFull returns ErrUnexpectedEOF
+ err = io.EOF
+ }
+ } else if size == 0 && err == io.EOF && objectInfo.Size > 0 {
+ // Special cases when server writes more data
+ // than the content-length, net/http response
+ // body returns an error, instead of converting
+ // it to io.EOF - return unexpected EOF.
+ err = io.ErrUnexpectedEOF
+ }
+
+ // Reply back how much was read.
+ resCh <- getResponse{
+ Size: size,
+ Error: err,
+ didRead: true,
+ objectInfo: objectInfo,
+ }
+ }
+ }
+ }
+ }()
+
+ // Create a newObject through the information sent back by reqCh.
+ return newObject(reqCh, resCh, doneCh), nil
+}
+
+// get request message container to communicate with internal
+// go-routine.
+type getRequest struct {
+ Buffer []byte
+ Offset int64 // readAt offset.
+ DidOffsetChange bool // Tracks the offset changes for Seek requests.
+ beenRead bool // Determines if this is the first time an object is being read.
+ isReadAt bool // Determines if this request is a request to a specific range
+ isReadOp bool // Determines if this request is a Read or Read/At request.
+ isFirstReq bool // Determines if this request is the first time an object is being accessed.
+ settingObjectInfo bool // Determines if this request is to set the objectInfo of an object.
+}
+
+// get response message container to reply back for the request.
+type getResponse struct {
+ Size int
+ Error error
+ didRead bool // Lets subsequent calls know whether or not httpReader has been initiated.
+ objectInfo ObjectInfo // Used for the first request.
+}
+
+// Object represents an open object. It implements
+// Reader, ReaderAt, Seeker, Closer for a HTTP stream.
+type Object struct {
+ // Mutex.
+ mutex *sync.Mutex
+
+ // User allocated and defined.
+ reqCh chan<- getRequest
+ resCh <-chan getResponse
+ doneCh chan<- struct{}
+ currOffset int64
+ objectInfo ObjectInfo
+
+ // Ask lower level to initiate data fetching based on currOffset
+ seekData bool
+
+ // Keeps track of closed call.
+ isClosed bool
+
+ // Keeps track of if this is the first call.
+ isStarted bool
+
+ // Previous error saved for future calls.
+ prevErr error
+
+ // Keeps track of if this object has been read yet.
+ beenRead bool
+
+ // Keeps track of if objectInfo has been set yet.
+ objectInfoSet bool
+}
+
+// doGetRequest - sends and blocks on the firstReqCh and reqCh of an object.
+// Returns back the size of the buffer read, if anything was read, as well
+// as any error encountered. For all first requests sent on the object
+// it is also responsible for sending back the objectInfo.
+func (o *Object) doGetRequest(request getRequest) (getResponse, error) {
+ o.reqCh <- request
+ response := <-o.resCh
+
+ // Return any error to the top level.
+ if response.Error != nil {
+ return response, response.Error
+ }
+
+ // This was the first request.
+ if !o.isStarted {
+ // The object has been operated on.
+ o.isStarted = true
+ }
+ // Set the objectInfo if the request was not readAt
+ // and it hasn't been set before.
+ if !o.objectInfoSet && !request.isReadAt {
+ o.objectInfo = response.objectInfo
+ o.objectInfoSet = true
+ }
+ // Set beenRead only if it has not been set before.
+ if !o.beenRead {
+ o.beenRead = response.didRead
+ }
+ // Data are ready on the wire, no need to reinitiate connection in lower level
+ o.seekData = false
+
+ return response, nil
+}
+
+// setOffset - handles the setting of offsets for
+// Read/ReadAt/Seek requests.
+func (o *Object) setOffset(bytesRead int64) error {
+ // Update the currentOffset.
+ o.currOffset += bytesRead
+
+ if o.objectInfo.Size > -1 && o.currOffset >= o.objectInfo.Size {
+ return io.EOF
+ }
+ return nil
+}
+
+// Read reads up to len(b) bytes into b. It returns the number of
+// bytes read (0 <= n <= len(b)) and any error encountered. Returns
+// io.EOF upon end of file.
+func (o *Object) Read(b []byte) (n int, err error) {
+ if o == nil {
+ return 0, errInvalidArgument("Object is nil")
+ }
+
+ // Locking.
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ // prevErr is previous error saved from previous operation.
+ if o.prevErr != nil || o.isClosed {
+ return 0, o.prevErr
+ }
+
+ // Create a new request.
+ readReq := getRequest{
+ isReadOp: true,
+ beenRead: o.beenRead,
+ Buffer: b,
+ }
+
+ // Alert that this is the first request.
+ if !o.isStarted {
+ readReq.isFirstReq = true
+ }
+
+ // Ask to establish a new data fetch routine based on seekData flag
+ readReq.DidOffsetChange = o.seekData
+ readReq.Offset = o.currOffset
+
+ // Send and receive from the first request.
+ response, err := o.doGetRequest(readReq)
+ if err != nil && err != io.EOF {
+ // Save the error for future calls.
+ o.prevErr = err
+ return response.Size, err
+ }
+
+ // Bytes read.
+ bytesRead := int64(response.Size)
+
+ // Set the new offset.
+ oerr := o.setOffset(bytesRead)
+ if oerr != nil {
+ // Save the error for future calls.
+ o.prevErr = oerr
+ return response.Size, oerr
+ }
+
+ // Return the response.
+ return response.Size, err
+}
+
+// Stat returns the ObjectInfo structure describing Object.
+func (o *Object) Stat() (ObjectInfo, error) {
+ if o == nil {
+ return ObjectInfo{}, errInvalidArgument("Object is nil")
+ }
+ // Locking.
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ if o.prevErr != nil && o.prevErr != io.EOF || o.isClosed {
+ return ObjectInfo{}, o.prevErr
+ }
+
+ // This is the first request.
+ if !o.isStarted || !o.objectInfoSet {
+ // Send the request and get the response.
+ _, err := o.doGetRequest(getRequest{
+ isFirstReq: !o.isStarted,
+ settingObjectInfo: !o.objectInfoSet,
+ })
+ if err != nil {
+ o.prevErr = err
+ return ObjectInfo{}, err
+ }
+ }
+
+ return o.objectInfo, nil
+}
+
+// ReadAt reads len(b) bytes from the File starting at byte offset
+// off. It returns the number of bytes read and the error, if any.
+// ReadAt always returns a non-nil error when n < len(b). At end of
+// file, that error is io.EOF.
+func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) {
+ if o == nil {
+ return 0, errInvalidArgument("Object is nil")
+ }
+
+ // Locking.
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ // prevErr is error which was saved in previous operation.
+ if o.prevErr != nil && o.prevErr != io.EOF || o.isClosed {
+ return 0, o.prevErr
+ }
+
+ // Set the current offset to ReadAt offset, because the current offset will be shifted at the end of this method.
+ o.currOffset = offset
+
+ // Can only compare offsets to size when size has been set.
+ if o.objectInfoSet {
+ // If offset is negative than we return io.EOF.
+ // If offset is greater than or equal to object size we return io.EOF.
+ if (o.objectInfo.Size > -1 && offset >= o.objectInfo.Size) || offset < 0 {
+ return 0, io.EOF
+ }
+ }
+
+ // Create the new readAt request.
+ readAtReq := getRequest{
+ isReadOp: true,
+ isReadAt: true,
+ DidOffsetChange: true, // Offset always changes.
+ beenRead: o.beenRead, // Set if this is the first request to try and read.
+ Offset: offset, // Set the offset.
+ Buffer: b,
+ }
+
+ // Alert that this is the first request.
+ if !o.isStarted {
+ readAtReq.isFirstReq = true
+ }
+
+ // Send and receive from the first request.
+ response, err := o.doGetRequest(readAtReq)
+ if err != nil && err != io.EOF {
+ // Save the error.
+ o.prevErr = err
+ return response.Size, err
+ }
+ // Bytes read.
+ bytesRead := int64(response.Size)
+ // There is no valid objectInfo yet
+ // to compare against for EOF.
+ if !o.objectInfoSet {
+ // Update the currentOffset.
+ o.currOffset += bytesRead
+ } else {
+ // If this was not the first request update
+ // the offsets and compare against objectInfo
+ // for EOF.
+ oerr := o.setOffset(bytesRead)
+ if oerr != nil {
+ o.prevErr = oerr
+ return response.Size, oerr
+ }
+ }
+ return response.Size, err
+}
+
+// Seek sets the offset for the next Read or Write to offset,
+// interpreted according to whence: 0 means relative to the
+// origin of the file, 1 means relative to the current offset,
+// and 2 means relative to the end.
+// Seek returns the new offset and an error, if any.
+//
+// Seeking to a negative offset is an error. Seeking to any positive
+// offset is legal, subsequent io operations succeed until the
+// underlying object is not closed.
+func (o *Object) Seek(offset int64, whence int) (n int64, err error) {
+ if o == nil {
+ return 0, errInvalidArgument("Object is nil")
+ }
+
+ // Locking.
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ // At EOF seeking is legal allow only io.EOF, for any other errors we return.
+ if o.prevErr != nil && o.prevErr != io.EOF {
+ return 0, o.prevErr
+ }
+
+ // Negative offset is valid for whence of '2'.
+ if offset < 0 && whence != 2 {
+ return 0, errInvalidArgument(fmt.Sprintf("Negative position not allowed for %d", whence))
+ }
+
+ // This is the first request. So before anything else
+ // get the ObjectInfo.
+ if !o.isStarted || !o.objectInfoSet {
+ // Create the new Seek request.
+ seekReq := getRequest{
+ isReadOp: false,
+ Offset: offset,
+ isFirstReq: true,
+ }
+ // Send and receive from the seek request.
+ _, err := o.doGetRequest(seekReq)
+ if err != nil {
+ // Save the error.
+ o.prevErr = err
+ return 0, err
+ }
+ }
+
+ // Switch through whence.
+ switch whence {
+ default:
+ return 0, errInvalidArgument(fmt.Sprintf("Invalid whence %d", whence))
+ case 0:
+ if o.objectInfo.Size > -1 && offset > o.objectInfo.Size {
+ return 0, io.EOF
+ }
+ o.currOffset = offset
+ case 1:
+ if o.objectInfo.Size > -1 && o.currOffset+offset > o.objectInfo.Size {
+ return 0, io.EOF
+ }
+ o.currOffset += offset
+ case 2:
+ // If we don't know the object size return an error for io.SeekEnd
+ if o.objectInfo.Size < 0 {
+ return 0, errInvalidArgument("Whence END is not supported when the object size is unknown")
+ }
+ // Seeking to positive offset is valid for whence '2', but
+ // since we are backing a Reader we have reached 'EOF' if
+ // offset is positive.
+ if offset > 0 {
+ return 0, io.EOF
+ }
+ // Seeking to negative position not allowed for whence.
+ if o.objectInfo.Size+offset < 0 {
+ return 0, errInvalidArgument(fmt.Sprintf("Seeking at negative offset not allowed for %d", whence))
+ }
+ o.currOffset = o.objectInfo.Size + offset
+ }
+ // Reset the saved error since we successfully seeked, let the Read
+ // and ReadAt decide.
+ if o.prevErr == io.EOF {
+ o.prevErr = nil
+ }
+
+ // Ask lower level to fetch again from source
+ o.seekData = true
+
+ // Return the effective offset.
+ return o.currOffset, nil
+}
+
+// Close - The behavior of Close after the first call returns error
+// for subsequent Close() calls.
+func (o *Object) Close() (err error) {
+ if o == nil {
+ return errInvalidArgument("Object is nil")
+ }
+ // Locking.
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ // if already closed return an error.
+ if o.isClosed {
+ return o.prevErr
+ }
+
+ // Close successfully.
+ close(o.doneCh)
+
+ // Save for future operations.
+ errMsg := "Object is already closed. Bad file descriptor."
+ o.prevErr = errors.New(errMsg)
+ // Save here that we closed done channel successfully.
+ o.isClosed = true
+ return nil
+}
+
+// newObject instantiates a new *minio.Object*
+// ObjectInfo will be set by setObjectInfo
+func newObject(reqCh chan<- getRequest, resCh <-chan getResponse, doneCh chan<- struct{}) *Object {
+ return &Object{
+ mutex: &sync.Mutex{},
+ reqCh: reqCh,
+ resCh: resCh,
+ doneCh: doneCh,
+ }
+}
+
+// getObject - retrieve object from Object Storage.
+//
+// Additionally this function also takes range arguments to download the specified
+// range bytes of an object. Setting offset and length = 0 will download the full object.
+//
+// For more information about the HTTP Range header.
+// go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.
+func (c Client) getObject(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, http.Header, error) {
+ // Validate input arguments.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, ObjectInfo{}, nil, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, ObjectInfo{}, nil, err
+ }
+
+ urlValues := make(url.Values)
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ // Execute GET on objectName.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ customHeader: opts.Header(),
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ if err != nil {
+ return nil, ObjectInfo{}, nil, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
+ return nil, ObjectInfo{}, nil, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+
+ objectStat, err := ToObjectInfo(bucketName, objectName, resp.Header)
+ if err != nil {
+ closeResponse(resp)
+ return nil, ObjectInfo{}, nil, err
+ }
+
+ // do not close body here, caller will close
+ return resp.Body, objectStat, resp.Header, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-get-options.go b/vendor/github.com/minio/minio-go/v7/api-get-options.go
new file mode 100644
index 00000000..9e0cb214
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-get-options.go
@@ -0,0 +1,140 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+)
+
+//AdvancedGetOptions for internal use by MinIO server - not intended for client use.
+type AdvancedGetOptions struct {
+ ReplicationDeleteMarker bool
+ ReplicationProxyRequest string
+}
+
+// GetObjectOptions are used to specify additional headers or options
+// during GET requests.
+type GetObjectOptions struct {
+ headers map[string]string
+ ServerSideEncryption encrypt.ServerSide
+ VersionID string
+ // To be not used by external applications
+ Internal AdvancedGetOptions
+}
+
+// StatObjectOptions are used to specify additional headers or options
+// during GET info/stat requests.
+type StatObjectOptions = GetObjectOptions
+
+// Header returns the http.Header representation of the GET options.
+func (o GetObjectOptions) Header() http.Header {
+ headers := make(http.Header, len(o.headers))
+ for k, v := range o.headers {
+ headers.Set(k, v)
+ }
+ if o.ServerSideEncryption != nil && o.ServerSideEncryption.Type() == encrypt.SSEC {
+ o.ServerSideEncryption.Marshal(headers)
+ }
+ // this header is set for active-active replication scenario where GET/HEAD
+ // to site A is proxy'd to site B if object/version missing on site A.
+ if o.Internal.ReplicationProxyRequest != "" {
+ headers.Set(minIOBucketReplicationProxyRequest, o.Internal.ReplicationProxyRequest)
+ }
+ return headers
+}
+
+// Set adds a key value pair to the options. The
+// key-value pair will be part of the HTTP GET request
+// headers.
+func (o *GetObjectOptions) Set(key, value string) {
+ if o.headers == nil {
+ o.headers = make(map[string]string)
+ }
+ o.headers[http.CanonicalHeaderKey(key)] = value
+}
+
+// SetMatchETag - set match etag.
+func (o *GetObjectOptions) SetMatchETag(etag string) error {
+ if etag == "" {
+ return errInvalidArgument("ETag cannot be empty.")
+ }
+ o.Set("If-Match", "\""+etag+"\"")
+ return nil
+}
+
+// SetMatchETagExcept - set match etag except.
+func (o *GetObjectOptions) SetMatchETagExcept(etag string) error {
+ if etag == "" {
+ return errInvalidArgument("ETag cannot be empty.")
+ }
+ o.Set("If-None-Match", "\""+etag+"\"")
+ return nil
+}
+
+// SetUnmodified - set unmodified time since.
+func (o *GetObjectOptions) SetUnmodified(modTime time.Time) error {
+ if modTime.IsZero() {
+ return errInvalidArgument("Modified since cannot be empty.")
+ }
+ o.Set("If-Unmodified-Since", modTime.Format(http.TimeFormat))
+ return nil
+}
+
+// SetModified - set modified time since.
+func (o *GetObjectOptions) SetModified(modTime time.Time) error {
+ if modTime.IsZero() {
+ return errInvalidArgument("Modified since cannot be empty.")
+ }
+ o.Set("If-Modified-Since", modTime.Format(http.TimeFormat))
+ return nil
+}
+
+// SetRange - set the start and end offset of the object to be read.
+// See https://tools.ietf.org/html/rfc7233#section-3.1 for reference.
+func (o *GetObjectOptions) SetRange(start, end int64) error {
+ switch {
+ case start == 0 && end < 0:
+ // Read last '-end' bytes. `bytes=-N`.
+ o.Set("Range", fmt.Sprintf("bytes=%d", end))
+ case 0 < start && end == 0:
+ // Read everything starting from offset
+ // 'start'. `bytes=N-`.
+ o.Set("Range", fmt.Sprintf("bytes=%d-", start))
+ case 0 <= start && start <= end:
+ // Read everything starting at 'start' till the
+ // 'end'. `bytes=N-M`
+ o.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
+ default:
+ // All other cases such as
+ // bytes=-3-
+ // bytes=5-3
+ // bytes=-2-4
+ // bytes=-3-0
+ // bytes=-3--2
+ // are invalid.
+ return errInvalidArgument(
+ fmt.Sprintf(
+ "Invalid range specified: start=%d end=%d",
+ start, end))
+ }
+ return nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-list.go b/vendor/github.com/minio/minio-go/v7/api-list.go
new file mode 100644
index 00000000..7996c11e
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-list.go
@@ -0,0 +1,950 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// ListBuckets list all buckets owned by this authenticated user.
+//
+// This call requires explicit authentication, no anonymous requests are
+// allowed for listing buckets.
+//
+// api := client.New(....)
+// for message := range api.ListBuckets(context.Background()) {
+// fmt.Println(message)
+// }
+//
+func (c Client) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
+ // Execute GET on service.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{contentSHA256Hex: emptySHA256Hex})
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, "", "")
+ }
+ }
+ listAllMyBucketsResult := listAllMyBucketsResult{}
+ err = xmlDecoder(resp.Body, &listAllMyBucketsResult)
+ if err != nil {
+ return nil, err
+ }
+ return listAllMyBucketsResult.Buckets.Bucket, nil
+}
+
+/// Bucket Read Operations.
+
+func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix string, recursive, metadata bool, maxKeys int) <-chan ObjectInfo {
+ // Allocate new list objects channel.
+ objectStatCh := make(chan ObjectInfo, 1)
+ // Default listing is delimited at "/"
+ delimiter := "/"
+ if recursive {
+ // If recursive we do not delimit.
+ delimiter = ""
+ }
+
+ // Return object owner information by default
+ fetchOwner := true
+
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ defer close(objectStatCh)
+ objectStatCh <- ObjectInfo{
+ Err: err,
+ }
+ return objectStatCh
+ }
+
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
+ defer close(objectStatCh)
+ objectStatCh <- ObjectInfo{
+ Err: err,
+ }
+ return objectStatCh
+ }
+
+ // Initiate list objects goroutine here.
+ go func(objectStatCh chan<- ObjectInfo) {
+ defer close(objectStatCh)
+ // Save continuationToken for next request.
+ var continuationToken string
+ for {
+ // Get list of objects a maximum of 1000 per request.
+ result, err := c.listObjectsV2Query(ctx, bucketName, objectPrefix, continuationToken,
+ fetchOwner, metadata, delimiter, maxKeys)
+ if err != nil {
+ objectStatCh <- ObjectInfo{
+ Err: err,
+ }
+ return
+ }
+
+ // If contents are available loop through and send over channel.
+ for _, object := range result.Contents {
+ object.ETag = trimEtag(object.ETag)
+ select {
+ // Send object content.
+ case objectStatCh <- object:
+ // If receives done from the caller, return here.
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // Send all common prefixes if any.
+ // NOTE: prefixes are only present if the request is delimited.
+ for _, obj := range result.CommonPrefixes {
+ select {
+ // Send object prefixes.
+ case objectStatCh <- ObjectInfo{Key: obj.Prefix}:
+ // If receives done from the caller, return here.
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // If continuation token present, save it for next request.
+ if result.NextContinuationToken != "" {
+ continuationToken = result.NextContinuationToken
+ }
+
+ // Listing ends result is not truncated, return right here.
+ if !result.IsTruncated {
+ return
+ }
+ }
+ }(objectStatCh)
+ return objectStatCh
+}
+
+// listObjectsV2Query - (List Objects V2) - List some or all (up to 1000) of the objects in a bucket.
+//
+// You can use the request parameters as selection criteria to return a subset of the objects in a bucket.
+// request parameters :-
+// ---------
+// ?continuation-token - Used to continue iterating over a set of objects
+// ?delimiter - A delimiter is a character you use to group keys.
+// ?prefix - Limits the response to keys that begin with the specified prefix.
+// ?max-keys - Sets the maximum number of keys returned in the response body.
+// ?metadata - Specifies if we want metadata for the objects as part of list operation.
+func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken string, fetchOwner, metadata bool, delimiter string, maxkeys int) (ListBucketV2Result, error) {
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return ListBucketV2Result{}, err
+ }
+ // Validate object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
+ return ListBucketV2Result{}, err
+ }
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+
+ // Always set list-type in ListObjects V2
+ urlValues.Set("list-type", "2")
+
+ if metadata {
+ urlValues.Set("metadata", "true")
+ }
+
+ // Always set encoding-type in ListObjects V2
+ urlValues.Set("encoding-type", "url")
+
+ // Set object prefix, prefix value to be set to empty is okay.
+ urlValues.Set("prefix", objectPrefix)
+
+ // Set delimiter, delimiter value to be set to empty is okay.
+ urlValues.Set("delimiter", delimiter)
+
+ // Set continuation token
+ if continuationToken != "" {
+ urlValues.Set("continuation-token", continuationToken)
+ }
+
+ // Fetch owner when listing
+ if fetchOwner {
+ urlValues.Set("fetch-owner", "true")
+ }
+
+ // Set max keys.
+ if maxkeys > 0 {
+ urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys))
+ }
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return ListBucketV2Result{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return ListBucketV2Result{}, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ // Decode listBuckets XML.
+ listBucketResult := ListBucketV2Result{}
+ if err = xmlDecoder(resp.Body, &listBucketResult); err != nil {
+ return listBucketResult, err
+ }
+
+ // This is an additional verification check to make
+ // sure proper responses are received.
+ if listBucketResult.IsTruncated && listBucketResult.NextContinuationToken == "" {
+ return listBucketResult, ErrorResponse{
+ Code: "NotImplemented",
+ Message: "Truncated response should have continuation token set",
+ }
+ }
+
+ for i, obj := range listBucketResult.Contents {
+ listBucketResult.Contents[i].Key, err = decodeS3Name(obj.Key, listBucketResult.EncodingType)
+ if err != nil {
+ return listBucketResult, err
+ }
+ }
+
+ for i, obj := range listBucketResult.CommonPrefixes {
+ listBucketResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listBucketResult.EncodingType)
+ if err != nil {
+ return listBucketResult, err
+ }
+ }
+
+ // Success.
+ return listBucketResult, nil
+}
+
+func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix string, recursive bool, maxKeys int) <-chan ObjectInfo {
+ // Allocate new list objects channel.
+ objectStatCh := make(chan ObjectInfo, 1)
+ // Default listing is delimited at "/"
+ delimiter := "/"
+ if recursive {
+ // If recursive we do not delimit.
+ delimiter = ""
+ }
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ defer close(objectStatCh)
+ objectStatCh <- ObjectInfo{
+ Err: err,
+ }
+ return objectStatCh
+ }
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
+ defer close(objectStatCh)
+ objectStatCh <- ObjectInfo{
+ Err: err,
+ }
+ return objectStatCh
+ }
+
+ // Initiate list objects goroutine here.
+ go func(objectStatCh chan<- ObjectInfo) {
+ defer close(objectStatCh)
+
+ marker := ""
+ for {
+ // Get list of objects a maximum of 1000 per request.
+ result, err := c.listObjectsQuery(ctx, bucketName, objectPrefix, marker, delimiter, maxKeys)
+ if err != nil {
+ objectStatCh <- ObjectInfo{
+ Err: err,
+ }
+ return
+ }
+
+ // If contents are available loop through and send over channel.
+ for _, object := range result.Contents {
+ // Save the marker.
+ marker = object.Key
+ select {
+ // Send object content.
+ case objectStatCh <- object:
+ // If receives done from the caller, return here.
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // Send all common prefixes if any.
+ // NOTE: prefixes are only present if the request is delimited.
+ for _, obj := range result.CommonPrefixes {
+ select {
+ // Send object prefixes.
+ case objectStatCh <- ObjectInfo{Key: obj.Prefix}:
+ // If receives done from the caller, return here.
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // If next marker present, save it for next request.
+ if result.NextMarker != "" {
+ marker = result.NextMarker
+ }
+
+ // Listing ends result is not truncated, return right here.
+ if !result.IsTruncated {
+ return
+ }
+ }
+ }(objectStatCh)
+ return objectStatCh
+}
+
+func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix string, recursive bool, maxKeys int) <-chan ObjectInfo {
+ // Allocate new list objects channel.
+ resultCh := make(chan ObjectInfo, 1)
+ // Default listing is delimited at "/"
+ delimiter := "/"
+ if recursive {
+ // If recursive we do not delimit.
+ delimiter = ""
+ }
+
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ defer close(resultCh)
+ resultCh <- ObjectInfo{
+ Err: err,
+ }
+ return resultCh
+ }
+
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(prefix); err != nil {
+ defer close(resultCh)
+ resultCh <- ObjectInfo{
+ Err: err,
+ }
+ return resultCh
+ }
+
+ // Initiate list objects goroutine here.
+ go func(resultCh chan<- ObjectInfo) {
+ defer close(resultCh)
+
+ var (
+ keyMarker = ""
+ versionIDMarker = ""
+ )
+
+ for {
+ // Get list of objects a maximum of 1000 per request.
+ result, err := c.listObjectVersionsQuery(ctx, bucketName, prefix, keyMarker, versionIDMarker, delimiter, maxKeys)
+ if err != nil {
+ resultCh <- ObjectInfo{
+ Err: err,
+ }
+ return
+ }
+
+ // If contents are available loop through and send over channel.
+ for _, version := range result.Versions {
+ info := ObjectInfo{
+ ETag: trimEtag(version.ETag),
+ Key: version.Key,
+ LastModified: version.LastModified,
+ Size: version.Size,
+ Owner: version.Owner,
+ StorageClass: version.StorageClass,
+ IsLatest: version.IsLatest,
+ VersionID: version.VersionID,
+
+ IsDeleteMarker: version.isDeleteMarker,
+ }
+ select {
+ // Send object version info.
+ case resultCh <- info:
+ // If receives done from the caller, return here.
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // Send all common prefixes if any.
+ // NOTE: prefixes are only present if the request is delimited.
+ for _, obj := range result.CommonPrefixes {
+ select {
+ // Send object prefixes.
+ case resultCh <- ObjectInfo{Key: obj.Prefix}:
+ // If receives done from the caller, return here.
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ // If next key marker is present, save it for next request.
+ if result.NextKeyMarker != "" {
+ keyMarker = result.NextKeyMarker
+ }
+
+ // If next version id marker is present, save it for next request.
+ if result.NextVersionIDMarker != "" {
+ versionIDMarker = result.NextVersionIDMarker
+ }
+
+ // Listing ends result is not truncated, return right here.
+ if !result.IsTruncated {
+ return
+ }
+ }
+ }(resultCh)
+ return resultCh
+}
+
+// listObjectVersions - (List Object Versions) - List some or all (up to 1000) of the existing objects
+// and their versions in a bucket.
+//
+// You can use the request parameters as selection criteria to return a subset of the objects in a bucket.
+// request parameters :-
+// ---------
+// ?key-marker - Specifies the key to start with when listing objects in a bucket.
+// ?version-id-marker - Specifies the version id marker to start with when listing objects with versions in a bucket.
+// ?delimiter - A delimiter is a character you use to group keys.
+// ?prefix - Limits the response to keys that begin with the specified prefix.
+// ?max-keys - Sets the maximum number of keys returned in the response body.
+func (c Client) listObjectVersionsQuery(ctx context.Context, bucketName, prefix, keyMarker, versionIDMarker, delimiter string, maxkeys int) (ListVersionsResult, error) {
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return ListVersionsResult{}, err
+ }
+ // Validate object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(prefix); err != nil {
+ return ListVersionsResult{}, err
+ }
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+
+ // Set versions to trigger versioning API
+ urlValues.Set("versions", "")
+
+ // Set object prefix, prefix value to be set to empty is okay.
+ urlValues.Set("prefix", prefix)
+
+ // Set delimiter, delimiter value to be set to empty is okay.
+ urlValues.Set("delimiter", delimiter)
+
+ // Set object marker.
+ if keyMarker != "" {
+ urlValues.Set("key-marker", keyMarker)
+ }
+
+ // Set max keys.
+ if maxkeys > 0 {
+ urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys))
+ }
+
+ // Set version ID marker
+ if versionIDMarker != "" {
+ urlValues.Set("version-id-marker", versionIDMarker)
+ }
+
+ // Always set encoding-type
+ urlValues.Set("encoding-type", "url")
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return ListVersionsResult{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return ListVersionsResult{}, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ // Decode ListVersionsResult XML.
+ listObjectVersionsOutput := ListVersionsResult{}
+ err = xmlDecoder(resp.Body, &listObjectVersionsOutput)
+ if err != nil {
+ return ListVersionsResult{}, err
+ }
+
+ for i, obj := range listObjectVersionsOutput.Versions {
+ listObjectVersionsOutput.Versions[i].Key, err = decodeS3Name(obj.Key, listObjectVersionsOutput.EncodingType)
+ if err != nil {
+ return listObjectVersionsOutput, err
+ }
+ }
+
+ for i, obj := range listObjectVersionsOutput.CommonPrefixes {
+ listObjectVersionsOutput.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listObjectVersionsOutput.EncodingType)
+ if err != nil {
+ return listObjectVersionsOutput, err
+ }
+ }
+
+ if listObjectVersionsOutput.NextKeyMarker != "" {
+ listObjectVersionsOutput.NextKeyMarker, err = decodeS3Name(listObjectVersionsOutput.NextKeyMarker, listObjectVersionsOutput.EncodingType)
+ if err != nil {
+ return listObjectVersionsOutput, err
+ }
+ }
+
+ return listObjectVersionsOutput, nil
+}
+
+// listObjects - (List Objects) - List some or all (up to 1000) of the objects in a bucket.
+//
+// You can use the request parameters as selection criteria to return a subset of the objects in a bucket.
+// request parameters :-
+// ---------
+// ?marker - Specifies the key to start with when listing objects in a bucket.
+// ?delimiter - A delimiter is a character you use to group keys.
+// ?prefix - Limits the response to keys that begin with the specified prefix.
+// ?max-keys - Sets the maximum number of keys returned in the response body.
+func (c Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix, objectMarker, delimiter string, maxkeys int) (ListBucketResult, error) {
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return ListBucketResult{}, err
+ }
+ // Validate object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
+ return ListBucketResult{}, err
+ }
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+
+ // Set object prefix, prefix value to be set to empty is okay.
+ urlValues.Set("prefix", objectPrefix)
+
+ // Set delimiter, delimiter value to be set to empty is okay.
+ urlValues.Set("delimiter", delimiter)
+
+ // Set object marker.
+ if objectMarker != "" {
+ urlValues.Set("marker", objectMarker)
+ }
+
+ // Set max keys.
+ if maxkeys > 0 {
+ urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys))
+ }
+
+ // Always set encoding-type
+ urlValues.Set("encoding-type", "url")
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return ListBucketResult{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return ListBucketResult{}, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ // Decode listBuckets XML.
+ listBucketResult := ListBucketResult{}
+ err = xmlDecoder(resp.Body, &listBucketResult)
+ if err != nil {
+ return listBucketResult, err
+ }
+
+ for i, obj := range listBucketResult.Contents {
+ listBucketResult.Contents[i].Key, err = decodeS3Name(obj.Key, listBucketResult.EncodingType)
+ if err != nil {
+ return listBucketResult, err
+ }
+ }
+
+ for i, obj := range listBucketResult.CommonPrefixes {
+ listBucketResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listBucketResult.EncodingType)
+ if err != nil {
+ return listBucketResult, err
+ }
+ }
+
+ if listBucketResult.NextMarker != "" {
+ listBucketResult.NextMarker, err = decodeS3Name(listBucketResult.NextMarker, listBucketResult.EncodingType)
+ if err != nil {
+ return listBucketResult, err
+ }
+ }
+
+ return listBucketResult, nil
+}
+
+// ListObjectsOptions holds all options of a list object request
+type ListObjectsOptions struct {
+ // Include objects versions in the listing
+ WithVersions bool
+ // Include objects metadata in the listing
+ WithMetadata bool
+ // Only list objects with the prefix
+ Prefix string
+ // Ignore '/' delimiter
+ Recursive bool
+ // The maximum number of objects requested per
+ // batch, advanced use-case not useful for most
+ // applications
+ MaxKeys int
+
+ // Use the deprecated list objects V1 API
+ UseV1 bool
+}
+
+// ListObjects returns objects list after evaluating the passed options.
+//
+// api := client.New(....)
+// for object := range api.ListObjects(ctx, "mytestbucket", minio.ListObjectsOptions{Prefix: "starthere", Recursive:true}) {
+// fmt.Println(object)
+// }
+//
+func (c Client) ListObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
+ if opts.WithVersions {
+ return c.listObjectVersions(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
+ }
+
+ // Use legacy list objects v1 API
+ if opts.UseV1 {
+ return c.listObjects(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
+ }
+
+ // Check whether this is snowball region, if yes ListObjectsV2 doesn't work, fallback to listObjectsV1.
+ if location, ok := c.bucketLocCache.Get(bucketName); ok {
+ if location == "snowball" {
+ return c.listObjects(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
+ }
+ }
+
+ return c.listObjectsV2(ctx, bucketName, opts.Prefix, opts.Recursive, opts.WithMetadata, opts.MaxKeys)
+}
+
+// ListIncompleteUploads - List incompletely uploaded multipart objects.
+//
+// ListIncompleteUploads lists all incompleted objects matching the
+// objectPrefix from the specified bucket. If recursion is enabled
+// it would list all subdirectories and all its contents.
+//
+// Your input parameters are just bucketName, objectPrefix, recursive.
+// If you enable recursive as 'true' this function will return back all
+// the multipart objects in a given bucket name.
+//
+// api := client.New(....)
+// // Recurively list all objects in 'mytestbucket'
+// recursive := true
+// for message := range api.ListIncompleteUploads(context.Background(), "mytestbucket", "starthere", recursive) {
+// fmt.Println(message)
+// }
+func (c Client) ListIncompleteUploads(ctx context.Context, bucketName, objectPrefix string, recursive bool) <-chan ObjectMultipartInfo {
+ return c.listIncompleteUploads(ctx, bucketName, objectPrefix, recursive)
+}
+
+// listIncompleteUploads lists all incomplete uploads.
+func (c Client) listIncompleteUploads(ctx context.Context, bucketName, objectPrefix string, recursive bool) <-chan ObjectMultipartInfo {
+ // Allocate channel for multipart uploads.
+ objectMultipartStatCh := make(chan ObjectMultipartInfo, 1)
+ // Delimiter is set to "/" by default.
+ delimiter := "/"
+ if recursive {
+ // If recursive do not delimit.
+ delimiter = ""
+ }
+ // Validate bucket name.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ defer close(objectMultipartStatCh)
+ objectMultipartStatCh <- ObjectMultipartInfo{
+ Err: err,
+ }
+ return objectMultipartStatCh
+ }
+ // Validate incoming object prefix.
+ if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil {
+ defer close(objectMultipartStatCh)
+ objectMultipartStatCh <- ObjectMultipartInfo{
+ Err: err,
+ }
+ return objectMultipartStatCh
+ }
+ go func(objectMultipartStatCh chan<- ObjectMultipartInfo) {
+ defer close(objectMultipartStatCh)
+ // object and upload ID marker for future requests.
+ var objectMarker string
+ var uploadIDMarker string
+ for {
+ // list all multipart uploads.
+ result, err := c.listMultipartUploadsQuery(ctx, bucketName, objectMarker, uploadIDMarker, objectPrefix, delimiter, 0)
+ if err != nil {
+ objectMultipartStatCh <- ObjectMultipartInfo{
+ Err: err,
+ }
+ return
+ }
+ objectMarker = result.NextKeyMarker
+ uploadIDMarker = result.NextUploadIDMarker
+
+ // Send all multipart uploads.
+ for _, obj := range result.Uploads {
+ // Calculate total size of the uploaded parts if 'aggregateSize' is enabled.
+ select {
+ // Send individual uploads here.
+ case objectMultipartStatCh <- obj:
+ // If the context is canceled
+ case <-ctx.Done():
+ return
+ }
+ }
+ // Send all common prefixes if any.
+ // NOTE: prefixes are only present if the request is delimited.
+ for _, obj := range result.CommonPrefixes {
+ select {
+ // Send delimited prefixes here.
+ case objectMultipartStatCh <- ObjectMultipartInfo{Key: obj.Prefix, Size: 0}:
+ // If context is canceled.
+ case <-ctx.Done():
+ return
+ }
+ }
+ // Listing ends if result not truncated, return right here.
+ if !result.IsTruncated {
+ return
+ }
+ }
+ }(objectMultipartStatCh)
+ // return.
+ return objectMultipartStatCh
+
+}
+
+// listMultipartUploadsQuery - (List Multipart Uploads).
+// - Lists some or all (up to 1000) in-progress multipart uploads in a bucket.
+//
+// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
+// request parameters. :-
+// ---------
+// ?key-marker - Specifies the multipart upload after which listing should begin.
+// ?upload-id-marker - Together with key-marker specifies the multipart upload after which listing should begin.
+// ?delimiter - A delimiter is a character you use to group keys.
+// ?prefix - Limits the response to keys that begin with the specified prefix.
+// ?max-uploads - Sets the maximum number of multipart uploads returned in the response body.
+func (c Client) listMultipartUploadsQuery(ctx context.Context, bucketName, keyMarker, uploadIDMarker, prefix, delimiter string, maxUploads int) (ListMultipartUploadsResult, error) {
+ // Get resources properly escaped and lined up before using them in http request.
+ urlValues := make(url.Values)
+ // Set uploads.
+ urlValues.Set("uploads", "")
+ // Set object key marker.
+ if keyMarker != "" {
+ urlValues.Set("key-marker", keyMarker)
+ }
+ // Set upload id marker.
+ if uploadIDMarker != "" {
+ urlValues.Set("upload-id-marker", uploadIDMarker)
+ }
+
+ // Set object prefix, prefix value to be set to empty is okay.
+ urlValues.Set("prefix", prefix)
+
+ // Set delimiter, delimiter value to be set to empty is okay.
+ urlValues.Set("delimiter", delimiter)
+
+ // Always set encoding-type
+ urlValues.Set("encoding-type", "url")
+
+ // maxUploads should be 1000 or less.
+ if maxUploads > 0 {
+ // Set max-uploads.
+ urlValues.Set("max-uploads", fmt.Sprintf("%d", maxUploads))
+ }
+
+ // Execute GET on bucketName to list multipart uploads.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return ListMultipartUploadsResult{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return ListMultipartUploadsResult{}, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ // Decode response body.
+ listMultipartUploadsResult := ListMultipartUploadsResult{}
+ err = xmlDecoder(resp.Body, &listMultipartUploadsResult)
+ if err != nil {
+ return listMultipartUploadsResult, err
+ }
+
+ listMultipartUploadsResult.NextKeyMarker, err = decodeS3Name(listMultipartUploadsResult.NextKeyMarker, listMultipartUploadsResult.EncodingType)
+ if err != nil {
+ return listMultipartUploadsResult, err
+ }
+
+ listMultipartUploadsResult.NextUploadIDMarker, err = decodeS3Name(listMultipartUploadsResult.NextUploadIDMarker, listMultipartUploadsResult.EncodingType)
+ if err != nil {
+ return listMultipartUploadsResult, err
+ }
+
+ for i, obj := range listMultipartUploadsResult.Uploads {
+ listMultipartUploadsResult.Uploads[i].Key, err = decodeS3Name(obj.Key, listMultipartUploadsResult.EncodingType)
+ if err != nil {
+ return listMultipartUploadsResult, err
+ }
+ }
+
+ for i, obj := range listMultipartUploadsResult.CommonPrefixes {
+ listMultipartUploadsResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listMultipartUploadsResult.EncodingType)
+ if err != nil {
+ return listMultipartUploadsResult, err
+ }
+ }
+
+ return listMultipartUploadsResult, nil
+}
+
+// listObjectParts list all object parts recursively.
+func (c Client) listObjectParts(ctx context.Context, bucketName, objectName, uploadID string) (partsInfo map[int]ObjectPart, err error) {
+ // Part number marker for the next batch of request.
+ var nextPartNumberMarker int
+ partsInfo = make(map[int]ObjectPart)
+ for {
+ // Get list of uploaded parts a maximum of 1000 per request.
+ listObjPartsResult, err := c.listObjectPartsQuery(ctx, bucketName, objectName, uploadID, nextPartNumberMarker, 1000)
+ if err != nil {
+ return nil, err
+ }
+ // Append to parts info.
+ for _, part := range listObjPartsResult.ObjectParts {
+ // Trim off the odd double quotes from ETag in the beginning and end.
+ part.ETag = trimEtag(part.ETag)
+ partsInfo[part.PartNumber] = part
+ }
+ // Keep part number marker, for the next iteration.
+ nextPartNumberMarker = listObjPartsResult.NextPartNumberMarker
+ // Listing ends result is not truncated, return right here.
+ if !listObjPartsResult.IsTruncated {
+ break
+ }
+ }
+
+ // Return all the parts.
+ return partsInfo, nil
+}
+
+// findUploadIDs lists all incomplete uploads and find the uploadIDs of the matching object name.
+func (c Client) findUploadIDs(ctx context.Context, bucketName, objectName string) ([]string, error) {
+ var uploadIDs []string
+ // Make list incomplete uploads recursive.
+ isRecursive := true
+ // List all incomplete uploads.
+ for mpUpload := range c.listIncompleteUploads(ctx, bucketName, objectName, isRecursive) {
+ if mpUpload.Err != nil {
+ return nil, mpUpload.Err
+ }
+ if objectName == mpUpload.Key {
+ uploadIDs = append(uploadIDs, mpUpload.UploadID)
+ }
+ }
+ // Return the latest upload id.
+ return uploadIDs, nil
+}
+
+// listObjectPartsQuery (List Parts query)
+// - lists some or all (up to 1000) parts that have been uploaded
+// for a specific multipart upload
+//
+// You can use the request parameters as selection criteria to return
+// a subset of the uploads in a bucket, request parameters :-
+// ---------
+// ?part-number-marker - Specifies the part after which listing should
+// begin.
+// ?max-parts - Maximum parts to be listed per request.
+func (c Client) listObjectPartsQuery(ctx context.Context, bucketName, objectName, uploadID string, partNumberMarker, maxParts int) (ListObjectPartsResult, error) {
+ // Get resources properly escaped and lined up before using them in http request.
+ urlValues := make(url.Values)
+ // Set part number marker.
+ urlValues.Set("part-number-marker", fmt.Sprintf("%d", partNumberMarker))
+ // Set upload id.
+ urlValues.Set("uploadId", uploadID)
+
+ // maxParts should be 1000 or less.
+ if maxParts > 0 {
+ // Set max parts.
+ urlValues.Set("max-parts", fmt.Sprintf("%d", maxParts))
+ }
+
+ // Execute GET on objectName to get list of parts.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return ListObjectPartsResult{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return ListObjectPartsResult{}, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ // Decode list object parts XML.
+ listObjectPartsResult := ListObjectPartsResult{}
+ err = xmlDecoder(resp.Body, &listObjectPartsResult)
+ if err != nil {
+ return listObjectPartsResult, err
+ }
+ return listObjectPartsResult, nil
+}
+
+// Decode an S3 object name according to the encoding type
+func decodeS3Name(name, encodingType string) (string, error) {
+ switch encodingType {
+ case "url":
+ return url.QueryUnescape(name)
+ default:
+ return name, nil
+ }
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-object-legal-hold.go b/vendor/github.com/minio/minio-go/v7/api-object-legal-hold.go
new file mode 100644
index 00000000..b139c168
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-object-legal-hold.go
@@ -0,0 +1,176 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// objectLegalHold - object legal hold specified in
+// https://docs.aws.amazon.com/AmazonS3/latest/API/archive-RESTObjectPUTLegalHold.html
+type objectLegalHold struct {
+ XMLNS string `xml:"xmlns,attr,omitempty"`
+ XMLName xml.Name `xml:"LegalHold"`
+ Status LegalHoldStatus `xml:"Status,omitempty"`
+}
+
+// PutObjectLegalHoldOptions represents options specified by user for PutObjectLegalHold call
+type PutObjectLegalHoldOptions struct {
+ VersionID string
+ Status *LegalHoldStatus
+}
+
+// GetObjectLegalHoldOptions represents options specified by user for GetObjectLegalHold call
+type GetObjectLegalHoldOptions struct {
+ VersionID string
+}
+
+// LegalHoldStatus - object legal hold status.
+type LegalHoldStatus string
+
+const (
+ // LegalHoldEnabled indicates legal hold is enabled
+ LegalHoldEnabled LegalHoldStatus = "ON"
+
+ // LegalHoldDisabled indicates legal hold is disabled
+ LegalHoldDisabled LegalHoldStatus = "OFF"
+)
+
+func (r LegalHoldStatus) String() string {
+ return string(r)
+}
+
+// IsValid - check whether this legal hold status is valid or not.
+func (r LegalHoldStatus) IsValid() bool {
+ return r == LegalHoldEnabled || r == LegalHoldDisabled
+}
+
+func newObjectLegalHold(status *LegalHoldStatus) (*objectLegalHold, error) {
+ if status == nil {
+ return nil, fmt.Errorf("Status not set")
+ }
+ if !status.IsValid() {
+ return nil, fmt.Errorf("invalid legal hold status `%v`", status)
+ }
+ legalHold := &objectLegalHold{
+ Status: *status,
+ }
+ return legalHold, nil
+}
+
+// PutObjectLegalHold : sets object legal hold for a given object and versionID.
+func (c Client) PutObjectLegalHold(ctx context.Context, bucketName, objectName string, opts PutObjectLegalHoldOptions) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("legal-hold", "")
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ lh, err := newObjectLegalHold(opts.Status)
+ if err != nil {
+ return err
+ }
+
+ lhData, err := xml.Marshal(lh)
+ if err != nil {
+ return err
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(lhData),
+ contentLength: int64(len(lhData)),
+ contentMD5Base64: sumMD5Base64(lhData),
+ contentSHA256Hex: sum256Hex(lhData),
+ }
+
+ // Execute PUT Object Legal Hold.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ return nil
+}
+
+// GetObjectLegalHold gets legal-hold status of given object.
+func (c Client) GetObjectLegalHold(ctx context.Context, bucketName, objectName string, opts GetObjectLegalHoldOptions) (status *LegalHoldStatus, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, err
+ }
+ urlValues := make(url.Values)
+ urlValues.Set("legal-hold", "")
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ lh := &objectLegalHold{}
+ if err = xml.NewDecoder(resp.Body).Decode(lh); err != nil {
+ return nil, err
+ }
+
+ return &lh.Status, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-object-lock.go b/vendor/github.com/minio/minio-go/v7/api-object-lock.go
new file mode 100644
index 00000000..29f52b05
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-object-lock.go
@@ -0,0 +1,241 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2019 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// RetentionMode - object retention mode.
+type RetentionMode string
+
+const (
+ // Governance - governance mode.
+ Governance RetentionMode = "GOVERNANCE"
+
+ // Compliance - compliance mode.
+ Compliance RetentionMode = "COMPLIANCE"
+)
+
+func (r RetentionMode) String() string {
+ return string(r)
+}
+
+// IsValid - check whether this retention mode is valid or not.
+func (r RetentionMode) IsValid() bool {
+ return r == Governance || r == Compliance
+}
+
+// ValidityUnit - retention validity unit.
+type ValidityUnit string
+
+const (
+ // Days - denotes no. of days.
+ Days ValidityUnit = "DAYS"
+
+ // Years - denotes no. of years.
+ Years ValidityUnit = "YEARS"
+)
+
+func (unit ValidityUnit) String() string {
+ return string(unit)
+}
+
+// IsValid - check whether this validity unit is valid or not.
+func (unit ValidityUnit) isValid() bool {
+ return unit == Days || unit == Years
+}
+
+// Retention - bucket level retention configuration.
+type Retention struct {
+ Mode RetentionMode
+ Validity time.Duration
+}
+
+func (r Retention) String() string {
+ return fmt.Sprintf("{Mode:%v, Validity:%v}", r.Mode, r.Validity)
+}
+
+// IsEmpty - returns whether retention is empty or not.
+func (r Retention) IsEmpty() bool {
+ return r.Mode == "" || r.Validity == 0
+}
+
+// objectLockConfig - object lock configuration specified in
+// https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_ObjectLockConfiguration.html
+type objectLockConfig struct {
+ XMLNS string `xml:"xmlns,attr,omitempty"`
+ XMLName xml.Name `xml:"ObjectLockConfiguration"`
+ ObjectLockEnabled string `xml:"ObjectLockEnabled"`
+ Rule *struct {
+ DefaultRetention struct {
+ Mode RetentionMode `xml:"Mode"`
+ Days *uint `xml:"Days"`
+ Years *uint `xml:"Years"`
+ } `xml:"DefaultRetention"`
+ } `xml:"Rule,omitempty"`
+}
+
+func newObjectLockConfig(mode *RetentionMode, validity *uint, unit *ValidityUnit) (*objectLockConfig, error) {
+ config := &objectLockConfig{
+ ObjectLockEnabled: "Enabled",
+ }
+
+ if mode != nil && validity != nil && unit != nil {
+ if !mode.IsValid() {
+ return nil, fmt.Errorf("invalid retention mode `%v`", mode)
+ }
+
+ if !unit.isValid() {
+ return nil, fmt.Errorf("invalid validity unit `%v`", unit)
+ }
+
+ config.Rule = &struct {
+ DefaultRetention struct {
+ Mode RetentionMode `xml:"Mode"`
+ Days *uint `xml:"Days"`
+ Years *uint `xml:"Years"`
+ } `xml:"DefaultRetention"`
+ }{}
+
+ config.Rule.DefaultRetention.Mode = *mode
+ if *unit == Days {
+ config.Rule.DefaultRetention.Days = validity
+ } else {
+ config.Rule.DefaultRetention.Years = validity
+ }
+
+ return config, nil
+ }
+
+ if mode == nil && validity == nil && unit == nil {
+ return config, nil
+ }
+
+ return nil, fmt.Errorf("all of retention mode, validity and validity unit must be passed")
+}
+
+// SetBucketObjectLockConfig sets object lock configuration in given bucket. mode, validity and unit are either all set or all nil.
+func (c Client) SetBucketObjectLockConfig(ctx context.Context, bucketName string, mode *RetentionMode, validity *uint, unit *ValidityUnit) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("object-lock", "")
+
+ config, err := newObjectLockConfig(mode, validity, unit)
+ if err != nil {
+ return err
+ }
+
+ configData, err := xml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(configData),
+ contentLength: int64(len(configData)),
+ contentMD5Base64: sumMD5Base64(configData),
+ contentSHA256Hex: sum256Hex(configData),
+ }
+
+ // Execute PUT bucket object lock configuration.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return nil
+}
+
+// GetObjectLockConfig gets object lock configuration of given bucket.
+func (c Client) GetObjectLockConfig(ctx context.Context, bucketName string) (objectLock string, mode *RetentionMode, validity *uint, unit *ValidityUnit, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return "", nil, nil, nil, err
+ }
+
+ urlValues := make(url.Values)
+ urlValues.Set("object-lock", "")
+
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return "", nil, nil, nil, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return "", nil, nil, nil, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ config := &objectLockConfig{}
+ if err = xml.NewDecoder(resp.Body).Decode(config); err != nil {
+ return "", nil, nil, nil, err
+ }
+
+ if config.Rule != nil {
+ mode = &config.Rule.DefaultRetention.Mode
+ if config.Rule.DefaultRetention.Days != nil {
+ validity = config.Rule.DefaultRetention.Days
+ days := Days
+ unit = &days
+ } else {
+ validity = config.Rule.DefaultRetention.Years
+ years := Years
+ unit = &years
+ }
+ return config.ObjectLockEnabled, mode, validity, unit, nil
+ }
+ return config.ObjectLockEnabled, nil, nil, nil, nil
+}
+
+// GetBucketObjectLockConfig gets object lock configuration of given bucket.
+func (c Client) GetBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *RetentionMode, validity *uint, unit *ValidityUnit, err error) {
+ _, mode, validity, unit, err = c.GetObjectLockConfig(ctx, bucketName)
+ return mode, validity, unit, err
+}
+
+// SetObjectLockConfig sets object lock configuration in given bucket. mode, validity and unit are either all set or all nil.
+func (c Client) SetObjectLockConfig(ctx context.Context, bucketName string, mode *RetentionMode, validity *uint, unit *ValidityUnit) error {
+ return c.SetBucketObjectLockConfig(ctx, bucketName, mode, validity, unit)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-object-retention.go b/vendor/github.com/minio/minio-go/v7/api-object-retention.go
new file mode 100644
index 00000000..54f2762d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-object-retention.go
@@ -0,0 +1,165 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2019-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// objectRetention - object retention specified in
+// https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_ObjectLockConfiguration.html
+type objectRetention struct {
+ XMLNS string `xml:"xmlns,attr,omitempty"`
+ XMLName xml.Name `xml:"Retention"`
+ Mode RetentionMode `xml:"Mode,omitempty"`
+ RetainUntilDate *time.Time `type:"timestamp" timestampFormat:"iso8601" xml:"RetainUntilDate,omitempty"`
+}
+
+func newObjectRetention(mode *RetentionMode, date *time.Time) (*objectRetention, error) {
+ objectRetention := &objectRetention{}
+
+ if date != nil && !date.IsZero() {
+ objectRetention.RetainUntilDate = date
+ }
+ if mode != nil {
+ if !mode.IsValid() {
+ return nil, fmt.Errorf("invalid retention mode `%v`", mode)
+ }
+ objectRetention.Mode = *mode
+ }
+
+ return objectRetention, nil
+}
+
+// PutObjectRetentionOptions represents options specified by user for PutObject call
+type PutObjectRetentionOptions struct {
+ GovernanceBypass bool
+ Mode *RetentionMode
+ RetainUntilDate *time.Time
+ VersionID string
+}
+
+// PutObjectRetention sets object retention for a given object and versionID.
+func (c Client) PutObjectRetention(ctx context.Context, bucketName, objectName string, opts PutObjectRetentionOptions) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("retention", "")
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ retention, err := newObjectRetention(opts.Mode, opts.RetainUntilDate)
+ if err != nil {
+ return err
+ }
+
+ retentionData, err := xml.Marshal(retention)
+ if err != nil {
+ return err
+ }
+
+ // Build headers.
+ headers := make(http.Header)
+
+ if opts.GovernanceBypass {
+ // Set the bypass goverenance retention header
+ headers.Set(amzBypassGovernance, "true")
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(retentionData),
+ contentLength: int64(len(retentionData)),
+ contentMD5Base64: sumMD5Base64(retentionData),
+ contentSHA256Hex: sum256Hex(retentionData),
+ customHeader: headers,
+ }
+
+ // Execute PUT Object Retention.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ return nil
+}
+
+// GetObjectRetention gets retention of given object.
+func (c Client) GetObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *RetentionMode, retainUntilDate *time.Time, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, nil, err
+ }
+
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, nil, err
+ }
+ urlValues := make(url.Values)
+ urlValues.Set("retention", "")
+ if versionID != "" {
+ urlValues.Set("versionId", versionID)
+ }
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, nil, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, nil, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ retention := &objectRetention{}
+ if err = xml.NewDecoder(resp.Body).Decode(retention); err != nil {
+ return nil, nil, err
+ }
+
+ return &retention.Mode, retention.RetainUntilDate, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-object-tagging.go b/vendor/github.com/minio/minio-go/v7/api-object-tagging.go
new file mode 100644
index 00000000..2709efcd
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-object-tagging.go
@@ -0,0 +1,157 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/tags"
+)
+
+// PutObjectTaggingOptions holds an object version id
+// to update tag(s) of a specific object version
+type PutObjectTaggingOptions struct {
+ VersionID string
+}
+
+// PutObjectTagging replaces or creates object tag(s) and can target
+// a specific object version in a versioned bucket.
+func (c Client) PutObjectTagging(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts PutObjectTaggingOptions) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("tagging", "")
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ reqBytes, err := xml.Marshal(otags)
+ if err != nil {
+ return err
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(reqBytes),
+ contentLength: int64(len(reqBytes)),
+ contentMD5Base64: sumMD5Base64(reqBytes),
+ }
+
+ // Execute PUT to set a object tagging.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ return nil
+}
+
+// GetObjectTaggingOptions holds the object version ID
+// to fetch the tagging key/value pairs
+type GetObjectTaggingOptions struct {
+ VersionID string
+}
+
+// GetObjectTagging fetches object tag(s) with options to target
+// a specific object version in a versioned bucket.
+func (c Client) GetObjectTagging(ctx context.Context, bucketName, objectName string, opts GetObjectTaggingOptions) (*tags.Tags, error) {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("tagging", "")
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ // Execute GET on object to get object tag(s)
+ resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+
+ return tags.ParseObjectXML(resp.Body)
+}
+
+// RemoveObjectTaggingOptions holds the version id of the object to remove
+type RemoveObjectTaggingOptions struct {
+ VersionID string
+}
+
+// RemoveObjectTagging removes object tag(s) with options to control a specific object
+// version in a versioned bucket
+func (c Client) RemoveObjectTagging(ctx context.Context, bucketName, objectName string, opts RemoveObjectTaggingOptions) error {
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+ urlValues.Set("tagging", "")
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ // Execute DELETE on object to remove object tag(s)
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ })
+
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ if resp != nil {
+ // S3 returns "204 No content" after Object tag deletion.
+ if resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ return err
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-presigned.go b/vendor/github.com/minio/minio-go/v7/api-presigned.go
new file mode 100644
index 00000000..80c363da
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-presigned.go
@@ -0,0 +1,216 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/signer"
+)
+
+// presignURL - Returns a presigned URL for an input 'method'.
+// Expires maximum is 7days - ie. 604800 and minimum is 1.
+func (c Client) presignURL(ctx context.Context, method string, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (u *url.URL, err error) {
+ // Input validation.
+ if method == "" {
+ return nil, errInvalidArgument("method cannot be empty.")
+ }
+ if err = s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+ if err = isValidExpiry(expires); err != nil {
+ return nil, err
+ }
+
+ // Convert expires into seconds.
+ expireSeconds := int64(expires / time.Second)
+ reqMetadata := requestMetadata{
+ presignURL: true,
+ bucketName: bucketName,
+ objectName: objectName,
+ expires: expireSeconds,
+ queryValues: reqParams,
+ }
+
+ // Instantiate a new request.
+ // Since expires is set newRequest will presign the request.
+ var req *http.Request
+ if req, err = c.newRequest(ctx, method, reqMetadata); err != nil {
+ return nil, err
+ }
+ return req.URL, nil
+}
+
+// PresignedGetObject - Returns a presigned URL to access an object
+// data without credentials. URL can have a maximum expiry of
+// upto 7days or a minimum of 1sec. Additionally you can override
+// a set of response headers using the query parameters.
+func (c Client) PresignedGetObject(ctx context.Context, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (u *url.URL, err error) {
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, err
+ }
+ return c.presignURL(ctx, http.MethodGet, bucketName, objectName, expires, reqParams)
+}
+
+// PresignedHeadObject - Returns a presigned URL to access
+// object metadata without credentials. URL can have a maximum expiry
+// of upto 7days or a minimum of 1sec. Additionally you can override
+// a set of response headers using the query parameters.
+func (c Client) PresignedHeadObject(ctx context.Context, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (u *url.URL, err error) {
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, err
+ }
+ return c.presignURL(ctx, http.MethodHead, bucketName, objectName, expires, reqParams)
+}
+
+// PresignedPutObject - Returns a presigned URL to upload an object
+// without credentials. URL can have a maximum expiry of upto 7days
+// or a minimum of 1sec.
+func (c Client) PresignedPutObject(ctx context.Context, bucketName string, objectName string, expires time.Duration) (u *url.URL, err error) {
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, err
+ }
+ return c.presignURL(ctx, http.MethodPut, bucketName, objectName, expires, nil)
+}
+
+// Presign - returns a presigned URL for any http method of your choice
+// along with custom request params. URL can have a maximum expiry of
+// upto 7days or a minimum of 1sec.
+func (c Client) Presign(ctx context.Context, method string, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (u *url.URL, err error) {
+ return c.presignURL(ctx, method, bucketName, objectName, expires, reqParams)
+}
+
+// PresignedPostPolicy - Returns POST urlString, form data to upload an object.
+func (c Client) PresignedPostPolicy(ctx context.Context, p *PostPolicy) (u *url.URL, formData map[string]string, err error) {
+ // Validate input arguments.
+ if p.expiration.IsZero() {
+ return nil, nil, errors.New("Expiration time must be specified")
+ }
+ if _, ok := p.formData["key"]; !ok {
+ return nil, nil, errors.New("object key must be specified")
+ }
+ if _, ok := p.formData["bucket"]; !ok {
+ return nil, nil, errors.New("bucket name must be specified")
+ }
+
+ bucketName := p.formData["bucket"]
+ // Fetch the bucket location.
+ location, err := c.getBucketLocation(ctx, bucketName)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ isVirtualHost := c.isVirtualHostStyleRequest(*c.endpointURL, bucketName)
+
+ u, err = c.makeTargetURL(bucketName, "", location, isVirtualHost, nil)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Get credentials from the configured credentials provider.
+ credValues, err := c.credsProvider.Get()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var (
+ signerType = credValues.SignerType
+ sessionToken = credValues.SessionToken
+ accessKeyID = credValues.AccessKeyID
+ secretAccessKey = credValues.SecretAccessKey
+ )
+
+ if signerType.IsAnonymous() {
+ return nil, nil, errInvalidArgument("Presigned operations are not supported for anonymous credentials")
+ }
+
+ // Keep time.
+ t := time.Now().UTC()
+ // For signature version '2' handle here.
+ if signerType.IsV2() {
+ policyBase64 := p.base64()
+ p.formData["policy"] = policyBase64
+ // For Google endpoint set this value to be 'GoogleAccessId'.
+ if s3utils.IsGoogleEndpoint(*c.endpointURL) {
+ p.formData["GoogleAccessId"] = accessKeyID
+ } else {
+ // For all other endpoints set this value to be 'AWSAccessKeyId'.
+ p.formData["AWSAccessKeyId"] = accessKeyID
+ }
+ // Sign the policy.
+ p.formData["signature"] = signer.PostPresignSignatureV2(policyBase64, secretAccessKey)
+ return u, p.formData, nil
+ }
+
+ // Add date policy.
+ if err = p.addNewPolicy(policyCondition{
+ matchType: "eq",
+ condition: "$x-amz-date",
+ value: t.Format(iso8601DateFormat),
+ }); err != nil {
+ return nil, nil, err
+ }
+
+ // Add algorithm policy.
+ if err = p.addNewPolicy(policyCondition{
+ matchType: "eq",
+ condition: "$x-amz-algorithm",
+ value: signV4Algorithm,
+ }); err != nil {
+ return nil, nil, err
+ }
+
+ // Add a credential policy.
+ credential := signer.GetCredential(accessKeyID, location, t, signer.ServiceTypeS3)
+ if err = p.addNewPolicy(policyCondition{
+ matchType: "eq",
+ condition: "$x-amz-credential",
+ value: credential,
+ }); err != nil {
+ return nil, nil, err
+ }
+
+ if sessionToken != "" {
+ if err = p.addNewPolicy(policyCondition{
+ matchType: "eq",
+ condition: "$x-amz-security-token",
+ value: sessionToken,
+ }); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Get base64 encoded policy.
+ policyBase64 := p.base64()
+
+ // Fill in the form data.
+ p.formData["policy"] = policyBase64
+ p.formData["x-amz-algorithm"] = signV4Algorithm
+ p.formData["x-amz-credential"] = credential
+ p.formData["x-amz-date"] = t.Format(iso8601DateFormat)
+ if sessionToken != "" {
+ p.formData["x-amz-security-token"] = sessionToken
+ }
+ p.formData["x-amz-signature"] = signer.PostPresignSignatureV4(policyBase64, t, secretAccessKey, location)
+ return u, p.formData, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-bucket.go b/vendor/github.com/minio/minio-go/v7/api-put-bucket.go
new file mode 100644
index 00000000..df9fe98a
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-put-bucket.go
@@ -0,0 +1,123 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "net/http"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+/// Bucket operations
+func (c Client) makeBucket(ctx context.Context, bucketName string, opts MakeBucketOptions) (err error) {
+ // Validate the input arguments.
+ if err := s3utils.CheckValidBucketNameStrict(bucketName); err != nil {
+ return err
+ }
+
+ err = c.doMakeBucket(ctx, bucketName, opts.Region, opts.ObjectLocking)
+ if err != nil && (opts.Region == "" || opts.Region == "us-east-1") {
+ if resp, ok := err.(ErrorResponse); ok && resp.Code == "AuthorizationHeaderMalformed" && resp.Region != "" {
+ err = c.doMakeBucket(ctx, bucketName, resp.Region, opts.ObjectLocking)
+ }
+ }
+ return err
+}
+
+func (c Client) doMakeBucket(ctx context.Context, bucketName string, location string, objectLockEnabled bool) (err error) {
+ defer func() {
+ // Save the location into cache on a successful makeBucket response.
+ if err == nil {
+ c.bucketLocCache.Set(bucketName, location)
+ }
+ }()
+
+ // If location is empty, treat is a default region 'us-east-1'.
+ if location == "" {
+ location = "us-east-1"
+ // For custom region clients, default
+ // to custom region instead not 'us-east-1'.
+ if c.region != "" {
+ location = c.region
+ }
+ }
+ // PUT bucket request metadata.
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ bucketLocation: location,
+ }
+
+ if objectLockEnabled {
+ headers := make(http.Header)
+ headers.Add("x-amz-bucket-object-lock-enabled", "true")
+ reqMetadata.customHeader = headers
+ }
+
+ // If location is not 'us-east-1' create bucket location config.
+ if location != "us-east-1" && location != "" {
+ createBucketConfig := createBucketConfiguration{}
+ createBucketConfig.Location = location
+ var createBucketConfigBytes []byte
+ createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
+ if err != nil {
+ return err
+ }
+ reqMetadata.contentMD5Base64 = sumMD5Base64(createBucketConfigBytes)
+ reqMetadata.contentSHA256Hex = sum256Hex(createBucketConfigBytes)
+ reqMetadata.contentBody = bytes.NewReader(createBucketConfigBytes)
+ reqMetadata.contentLength = int64(len(createBucketConfigBytes))
+ }
+
+ // Execute PUT to create a new bucket.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ // Success.
+ return nil
+}
+
+// MakeBucketOptions holds all options to tweak bucket creation
+type MakeBucketOptions struct {
+ // Bucket location
+ Region string
+ // Enable object locking
+ ObjectLocking bool
+}
+
+// MakeBucket creates a new bucket with bucketName with a context to control cancellations and timeouts.
+//
+// Location is an optional argument, by default all buckets are
+// created in US Standard Region.
+//
+// For Amazon S3 for more supported regions - http://docs.aws.amazon.com/general/latest/gr/rande.html
+// For Google Cloud Storage for more supported regions - https://cloud.google.com/storage/docs/bucket-locations
+func (c Client) MakeBucket(ctx context.Context, bucketName string, opts MakeBucketOptions) (err error) {
+ return c.makeBucket(ctx, bucketName, opts)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-common.go b/vendor/github.com/minio/minio-go/v7/api-put-object-common.go
new file mode 100644
index 00000000..f1653afe
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object-common.go
@@ -0,0 +1,148 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "io"
+ "math"
+ "os"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// Verify if reader is *minio.Object
+func isObject(reader io.Reader) (ok bool) {
+ _, ok = reader.(*Object)
+ return
+}
+
+// Verify if reader is a generic ReaderAt
+func isReadAt(reader io.Reader) (ok bool) {
+ var v *os.File
+ v, ok = reader.(*os.File)
+ if ok {
+ // Stdin, Stdout and Stderr all have *os.File type
+ // which happen to also be io.ReaderAt compatible
+ // we need to add special conditions for them to
+ // be ignored by this function.
+ for _, f := range []string{
+ "/dev/stdin",
+ "/dev/stdout",
+ "/dev/stderr",
+ } {
+ if f == v.Name() {
+ ok = false
+ break
+ }
+ }
+ } else {
+ _, ok = reader.(io.ReaderAt)
+ }
+ return
+}
+
+// OptimalPartInfo - calculate the optimal part info for a given
+// object size.
+//
+// NOTE: Assumption here is that for any object to be uploaded to any S3 compatible
+// object storage it will have the following parameters as constants.
+//
+// maxPartsCount - 10000
+// minPartSize - 16MiB
+// maxMultipartPutObjectSize - 5TiB
+//
+func OptimalPartInfo(objectSize int64, configuredPartSize uint64) (totalPartsCount int, partSize int64, lastPartSize int64, err error) {
+ // object size is '-1' set it to 5TiB.
+ var unknownSize bool
+ if objectSize == -1 {
+ unknownSize = true
+ objectSize = maxMultipartPutObjectSize
+ }
+
+ // object size is larger than supported maximum.
+ if objectSize > maxMultipartPutObjectSize {
+ err = errEntityTooLarge(objectSize, maxMultipartPutObjectSize, "", "")
+ return
+ }
+
+ var partSizeFlt float64
+ if configuredPartSize > 0 {
+ if int64(configuredPartSize) > objectSize {
+ err = errEntityTooLarge(int64(configuredPartSize), objectSize, "", "")
+ return
+ }
+
+ if !unknownSize {
+ if objectSize > (int64(configuredPartSize) * maxPartsCount) {
+ err = errInvalidArgument("Part size * max_parts(10000) is lesser than input objectSize.")
+ return
+ }
+ }
+
+ if configuredPartSize < absMinPartSize {
+ err = errInvalidArgument("Input part size is smaller than allowed minimum of 5MiB.")
+ return
+ }
+
+ if configuredPartSize > maxPartSize {
+ err = errInvalidArgument("Input part size is bigger than allowed maximum of 5GiB.")
+ return
+ }
+
+ partSizeFlt = float64(configuredPartSize)
+ if unknownSize {
+ // If input has unknown size and part size is configured
+ // keep it to maximum allowed as per 10000 parts.
+ objectSize = int64(configuredPartSize) * maxPartsCount
+ }
+ } else {
+ configuredPartSize = minPartSize
+ // Use floats for part size for all calculations to avoid
+ // overflows during float64 to int64 conversions.
+ partSizeFlt = float64(objectSize / maxPartsCount)
+ partSizeFlt = math.Ceil(partSizeFlt/float64(configuredPartSize)) * float64(configuredPartSize)
+ }
+
+ // Total parts count.
+ totalPartsCount = int(math.Ceil(float64(objectSize) / partSizeFlt))
+ // Part size.
+ partSize = int64(partSizeFlt)
+ // Last part size.
+ lastPartSize = objectSize - int64(totalPartsCount-1)*partSize
+ return totalPartsCount, partSize, lastPartSize, nil
+}
+
+// getUploadID - fetch upload id if already present for an object name
+// or initiate a new request to fetch a new upload id.
+func (c Client) newUploadID(ctx context.Context, bucketName, objectName string, opts PutObjectOptions) (uploadID string, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return "", err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return "", err
+ }
+
+ // Initiate multipart upload for an object.
+ initMultipartUploadResult, err := c.initiateMultipartUpload(ctx, bucketName, objectName, opts)
+ if err != nil {
+ return "", err
+ }
+ return initMultipartUploadResult.UploadID, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-file-context.go b/vendor/github.com/minio/minio-go/v7/api-put-object-file-context.go
new file mode 100644
index 00000000..6c0f20df
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object-file-context.go
@@ -0,0 +1,64 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "mime"
+ "os"
+ "path/filepath"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// FPutObject - Create an object in a bucket, with contents from file at filePath. Allows request cancellation.
+func (c Client) FPutObject(ctx context.Context, bucketName, objectName, filePath string, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Open the referenced file.
+ fileReader, err := os.Open(filePath)
+ // If any error fail quickly here.
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ defer fileReader.Close()
+
+ // Save the file stat.
+ fileStat, err := fileReader.Stat()
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Save the file size.
+ fileSize := fileStat.Size()
+
+ // Set contentType based on filepath extension if not given or default
+ // value of "application/octet-stream" if the extension has no associated type.
+ if opts.ContentType == "" {
+ if opts.ContentType = mime.TypeByExtension(filepath.Ext(filePath)); opts.ContentType == "" {
+ opts.ContentType = "application/octet-stream"
+ }
+ }
+ return c.PutObject(ctx, bucketName, objectName, fileReader, fileSize, opts)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go b/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go
new file mode 100644
index 00000000..a70d7054
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go
@@ -0,0 +1,393 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/google/uuid"
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+func (c Client) putObjectMultipart(ctx context.Context, bucketName, objectName string, reader io.Reader, size int64,
+ opts PutObjectOptions) (info UploadInfo, err error) {
+ info, err = c.putObjectMultipartNoStream(ctx, bucketName, objectName, reader, opts)
+ if err != nil {
+ errResp := ToErrorResponse(err)
+ // Verify if multipart functionality is not available, if not
+ // fall back to single PutObject operation.
+ if errResp.Code == "AccessDenied" && strings.Contains(errResp.Message, "Access Denied") {
+ // Verify if size of reader is greater than '5GiB'.
+ if size > maxSinglePutObjectSize {
+ return UploadInfo{}, errEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
+ }
+ // Fall back to uploading as single PutObject operation.
+ return c.putObject(ctx, bucketName, objectName, reader, size, opts)
+ }
+ }
+ return info, err
+}
+
+func (c Client) putObjectMultipartNoStream(ctx context.Context, bucketName, objectName string, reader io.Reader, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Input validation.
+ if err = s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Total data read and written to server. should be equal to
+ // 'size' at the end of the call.
+ var totalUploadedSize int64
+
+ // Complete multipart upload.
+ var complMultipartUpload completeMultipartUpload
+
+ // Calculate the optimal parts info for a given size.
+ totalPartsCount, partSize, _, err := OptimalPartInfo(-1, opts.PartSize)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Initiate a new multipart upload.
+ uploadID, err := c.newUploadID(ctx, bucketName, objectName, opts)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ defer func() {
+ if err != nil {
+ c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
+ }
+ }()
+
+ // Part number always starts with '1'.
+ partNumber := 1
+
+ // Initialize parts uploaded map.
+ partsInfo := make(map[int]ObjectPart)
+
+ // Create a buffer.
+ buf := make([]byte, partSize)
+
+ for partNumber <= totalPartsCount {
+ // Choose hash algorithms to be calculated by hashCopyN,
+ // avoid sha256 with non-v4 signature request or
+ // HTTPS connection.
+ hashAlgos, hashSums := c.hashMaterials(opts.SendContentMd5)
+
+ length, rErr := readFull(reader, buf)
+ if rErr == io.EOF && partNumber > 1 {
+ break
+ }
+
+ if rErr != nil && rErr != io.ErrUnexpectedEOF && rErr != io.EOF {
+ return UploadInfo{}, rErr
+ }
+
+ // Calculates hash sums while copying partSize bytes into cw.
+ for k, v := range hashAlgos {
+ v.Write(buf[:length])
+ hashSums[k] = v.Sum(nil)
+ v.Close()
+ }
+
+ // Update progress reader appropriately to the latest offset
+ // as we read from the source.
+ rd := newHook(bytes.NewReader(buf[:length]), opts.Progress)
+
+ // Checksums..
+ var (
+ md5Base64 string
+ sha256Hex string
+ )
+ if hashSums["md5"] != nil {
+ md5Base64 = base64.StdEncoding.EncodeToString(hashSums["md5"])
+ }
+ if hashSums["sha256"] != nil {
+ sha256Hex = hex.EncodeToString(hashSums["sha256"])
+ }
+
+ // Proceed to upload the part.
+ objPart, uerr := c.uploadPart(ctx, bucketName, objectName, uploadID, rd, partNumber,
+ md5Base64, sha256Hex, int64(length), opts.ServerSideEncryption)
+ if uerr != nil {
+ return UploadInfo{}, uerr
+ }
+
+ // Save successfully uploaded part metadata.
+ partsInfo[partNumber] = objPart
+
+ // Save successfully uploaded size.
+ totalUploadedSize += int64(length)
+
+ // Increment part number.
+ partNumber++
+
+ // For unknown size, Read EOF we break away.
+ // We do not have to upload till totalPartsCount.
+ if rErr == io.EOF {
+ break
+ }
+ }
+
+ // Loop over total uploaded parts to save them in
+ // Parts array before completing the multipart request.
+ for i := 1; i < partNumber; i++ {
+ part, ok := partsInfo[i]
+ if !ok {
+ return UploadInfo{}, errInvalidArgument(fmt.Sprintf("Missing part number %d", i))
+ }
+ complMultipartUpload.Parts = append(complMultipartUpload.Parts, CompletePart{
+ ETag: part.ETag,
+ PartNumber: part.PartNumber,
+ })
+ }
+
+ // Sort all completed parts.
+ sort.Sort(completedParts(complMultipartUpload.Parts))
+
+ uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ uploadInfo.Size = totalUploadedSize
+ return uploadInfo, nil
+}
+
+// initiateMultipartUpload - Initiates a multipart upload and returns an upload ID.
+func (c Client) initiateMultipartUpload(ctx context.Context, bucketName, objectName string, opts PutObjectOptions) (initiateMultipartUploadResult, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return initiateMultipartUploadResult{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return initiateMultipartUploadResult{}, err
+ }
+
+ // Initialize url queries.
+ urlValues := make(url.Values)
+ urlValues.Set("uploads", "")
+
+ if opts.Internal.SourceVersionID != "" {
+ if _, err := uuid.Parse(opts.Internal.SourceVersionID); err != nil {
+ return initiateMultipartUploadResult{}, errInvalidArgument(err.Error())
+ }
+ urlValues.Set("versionId", opts.Internal.SourceVersionID)
+ }
+
+ // Set ContentType header.
+ customHeader := opts.Header()
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ customHeader: customHeader,
+ }
+
+ // Execute POST on an objectName to initiate multipart upload.
+ resp, err := c.executeMethod(ctx, http.MethodPost, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return initiateMultipartUploadResult{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return initiateMultipartUploadResult{}, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ // Decode xml for new multipart upload.
+ initiateMultipartUploadResult := initiateMultipartUploadResult{}
+ err = xmlDecoder(resp.Body, &initiateMultipartUploadResult)
+ if err != nil {
+ return initiateMultipartUploadResult, err
+ }
+ return initiateMultipartUploadResult, nil
+}
+
+// uploadPart - Uploads a part in a multipart upload.
+func (c Client) uploadPart(ctx context.Context, bucketName, objectName, uploadID string, reader io.Reader,
+ partNumber int, md5Base64, sha256Hex string, size int64, sse encrypt.ServerSide) (ObjectPart, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return ObjectPart{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return ObjectPart{}, err
+ }
+ if size > maxPartSize {
+ return ObjectPart{}, errEntityTooLarge(size, maxPartSize, bucketName, objectName)
+ }
+ if size <= -1 {
+ return ObjectPart{}, errEntityTooSmall(size, bucketName, objectName)
+ }
+ if partNumber <= 0 {
+ return ObjectPart{}, errInvalidArgument("Part number cannot be negative or equal to zero.")
+ }
+ if uploadID == "" {
+ return ObjectPart{}, errInvalidArgument("UploadID cannot be empty.")
+ }
+
+ // Get resources properly escaped and lined up before using them in http request.
+ urlValues := make(url.Values)
+ // Set part number.
+ urlValues.Set("partNumber", strconv.Itoa(partNumber))
+ // Set upload id.
+ urlValues.Set("uploadId", uploadID)
+
+ // Set encryption headers, if any.
+ customHeader := make(http.Header)
+ // https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPart.html
+ // Server-side encryption is supported by the S3 Multipart Upload actions.
+ // Unless you are using a customer-provided encryption key, you don't need
+ // to specify the encryption parameters in each UploadPart request.
+ if sse != nil && sse.Type() == encrypt.SSEC {
+ sse.Marshal(customHeader)
+ }
+
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ customHeader: customHeader,
+ contentBody: reader,
+ contentLength: size,
+ contentMD5Base64: md5Base64,
+ contentSHA256Hex: sha256Hex,
+ }
+
+ // Execute PUT on each part.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return ObjectPart{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return ObjectPart{}, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+ // Once successfully uploaded, return completed part.
+ objPart := ObjectPart{}
+ objPart.Size = size
+ objPart.PartNumber = partNumber
+ // Trim off the odd double quotes from ETag in the beginning and end.
+ objPart.ETag = trimEtag(resp.Header.Get("ETag"))
+ return objPart, nil
+}
+
+// completeMultipartUpload - Completes a multipart upload by assembling previously uploaded parts.
+func (c Client) completeMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string,
+ complete completeMultipartUpload) (UploadInfo, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Initialize url queries.
+ urlValues := make(url.Values)
+ urlValues.Set("uploadId", uploadID)
+ // Marshal complete multipart body.
+ completeMultipartUploadBytes, err := xml.Marshal(complete)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Instantiate all the complete multipart buffer.
+ completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentBody: completeMultipartUploadBuffer,
+ contentLength: int64(len(completeMultipartUploadBytes)),
+ contentSHA256Hex: sum256Hex(completeMultipartUploadBytes),
+ }
+
+ // Execute POST to complete multipart upload for an objectName.
+ resp, err := c.executeMethod(ctx, http.MethodPost, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return UploadInfo{}, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+
+ // Read resp.Body into a []bytes to parse for Error response inside the body
+ var b []byte
+ b, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ // Decode completed multipart upload response on success.
+ completeMultipartUploadResult := completeMultipartUploadResult{}
+ err = xmlDecoder(bytes.NewReader(b), &completeMultipartUploadResult)
+ if err != nil {
+ // xml parsing failure due to presence an ill-formed xml fragment
+ return UploadInfo{}, err
+ } else if completeMultipartUploadResult.Bucket == "" {
+ // xml's Decode method ignores well-formed xml that don't apply to the type of value supplied.
+ // In this case, it would leave completeMultipartUploadResult with the corresponding zero-values
+ // of the members.
+
+ // Decode completed multipart upload response on failure
+ completeMultipartUploadErr := ErrorResponse{}
+ err = xmlDecoder(bytes.NewReader(b), &completeMultipartUploadErr)
+ if err != nil {
+ // xml parsing failure due to presence an ill-formed xml fragment
+ return UploadInfo{}, err
+ }
+ return UploadInfo{}, completeMultipartUploadErr
+ }
+
+ // extract lifecycle expiry date and rule ID
+ expTime, ruleID := amzExpirationToExpiryDateRuleID(resp.Header.Get(amzExpiration))
+
+ return UploadInfo{
+ Bucket: completeMultipartUploadResult.Bucket,
+ Key: completeMultipartUploadResult.Key,
+ ETag: trimEtag(completeMultipartUploadResult.ETag),
+ VersionID: resp.Header.Get(amzVersionID),
+ Location: completeMultipartUploadResult.Location,
+ Expiration: expTime,
+ ExpirationRuleID: ruleID,
+ }, nil
+
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go b/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go
new file mode 100644
index 00000000..39e381e9
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go
@@ -0,0 +1,487 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "sort"
+ "strings"
+
+ "github.com/google/uuid"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// putObjectMultipartStream - upload a large object using
+// multipart upload and streaming signature for signing payload.
+// Comprehensive put object operation involving multipart uploads.
+//
+// Following code handles these types of readers.
+//
+// - *minio.Object
+// - Any reader which has a method 'ReadAt()'
+//
+func (c Client) putObjectMultipartStream(ctx context.Context, bucketName, objectName string,
+ reader io.Reader, size int64, opts PutObjectOptions) (info UploadInfo, err error) {
+
+ if !isObject(reader) && isReadAt(reader) && !opts.SendContentMd5 {
+ // Verify if the reader implements ReadAt and it is not a *minio.Object then we will use parallel uploader.
+ info, err = c.putObjectMultipartStreamFromReadAt(ctx, bucketName, objectName, reader.(io.ReaderAt), size, opts)
+ } else {
+ info, err = c.putObjectMultipartStreamOptionalChecksum(ctx, bucketName, objectName, reader, size, opts)
+ }
+ if err != nil {
+ errResp := ToErrorResponse(err)
+ // Verify if multipart functionality is not available, if not
+ // fall back to single PutObject operation.
+ if errResp.Code == "AccessDenied" && strings.Contains(errResp.Message, "Access Denied") {
+ // Verify if size of reader is greater than '5GiB'.
+ if size > maxSinglePutObjectSize {
+ return UploadInfo{}, errEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName)
+ }
+ // Fall back to uploading as single PutObject operation.
+ return c.putObject(ctx, bucketName, objectName, reader, size, opts)
+ }
+ }
+ return info, err
+}
+
+// uploadedPartRes - the response received from a part upload.
+type uploadedPartRes struct {
+ Error error // Any error encountered while uploading the part.
+ PartNum int // Number of the part uploaded.
+ Size int64 // Size of the part uploaded.
+ Part ObjectPart
+}
+
+type uploadPartReq struct {
+ PartNum int // Number of the part uploaded.
+ Part ObjectPart // Size of the part uploaded.
+}
+
+// putObjectMultipartFromReadAt - Uploads files bigger than 128MiB.
+// Supports all readers which implements io.ReaderAt interface
+// (ReadAt method).
+//
+// NOTE: This function is meant to be used for all readers which
+// implement io.ReaderAt which allows us for resuming multipart
+// uploads but reading at an offset, which would avoid re-read the
+// data which was already uploaded. Internally this function uses
+// temporary files for staging all the data, these temporary files are
+// cleaned automatically when the caller i.e http client closes the
+// stream after uploading all the contents successfully.
+func (c Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketName, objectName string,
+ reader io.ReaderAt, size int64, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Input validation.
+ if err = s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Calculate the optimal parts info for a given size.
+ totalPartsCount, partSize, lastPartSize, err := OptimalPartInfo(size, opts.PartSize)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Initiate a new multipart upload.
+ uploadID, err := c.newUploadID(ctx, bucketName, objectName, opts)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Aborts the multipart upload in progress, if the
+ // function returns any error, since we do not resume
+ // we should purge the parts which have been uploaded
+ // to relinquish storage space.
+ defer func() {
+ if err != nil {
+ c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
+ }
+ }()
+
+ // Total data read and written to server. should be equal to 'size' at the end of the call.
+ var totalUploadedSize int64
+
+ // Complete multipart upload.
+ var complMultipartUpload completeMultipartUpload
+
+ // Declare a channel that sends the next part number to be uploaded.
+ // Buffered to 10000 because thats the maximum number of parts allowed
+ // by S3.
+ uploadPartsCh := make(chan uploadPartReq, 10000)
+
+ // Declare a channel that sends back the response of a part upload.
+ // Buffered to 10000 because thats the maximum number of parts allowed
+ // by S3.
+ uploadedPartsCh := make(chan uploadedPartRes, 10000)
+
+ // Used for readability, lastPartNumber is always totalPartsCount.
+ lastPartNumber := totalPartsCount
+
+ // Send each part number to the channel to be processed.
+ for p := 1; p <= totalPartsCount; p++ {
+ uploadPartsCh <- uploadPartReq{PartNum: p}
+ }
+ close(uploadPartsCh)
+
+ var partsBuf = make([][]byte, opts.getNumThreads())
+ for i := range partsBuf {
+ partsBuf[i] = make([]byte, 0, partSize)
+ }
+
+ // Receive each part number from the channel allowing three parallel uploads.
+ for w := 1; w <= opts.getNumThreads(); w++ {
+ go func(w int, partSize int64) {
+ // Each worker will draw from the part channel and upload in parallel.
+ for uploadReq := range uploadPartsCh {
+
+ // If partNumber was not uploaded we calculate the missing
+ // part offset and size. For all other part numbers we
+ // calculate offset based on multiples of partSize.
+ readOffset := int64(uploadReq.PartNum-1) * partSize
+
+ // As a special case if partNumber is lastPartNumber, we
+ // calculate the offset based on the last part size.
+ if uploadReq.PartNum == lastPartNumber {
+ readOffset = (size - lastPartSize)
+ partSize = lastPartSize
+ }
+
+ n, rerr := readFull(io.NewSectionReader(reader, readOffset, partSize), partsBuf[w-1][:partSize])
+ if rerr != nil && rerr != io.ErrUnexpectedEOF && err != io.EOF {
+ uploadedPartsCh <- uploadedPartRes{
+ Error: rerr,
+ }
+ // Exit the goroutine.
+ return
+ }
+
+ // Get a section reader on a particular offset.
+ hookReader := newHook(bytes.NewReader(partsBuf[w-1][:n]), opts.Progress)
+
+ // Proceed to upload the part.
+ objPart, err := c.uploadPart(ctx, bucketName, objectName,
+ uploadID, hookReader, uploadReq.PartNum,
+ "", "", partSize, opts.ServerSideEncryption)
+ if err != nil {
+ uploadedPartsCh <- uploadedPartRes{
+ Error: err,
+ }
+ // Exit the goroutine.
+ return
+ }
+
+ // Save successfully uploaded part metadata.
+ uploadReq.Part = objPart
+
+ // Send successful part info through the channel.
+ uploadedPartsCh <- uploadedPartRes{
+ Size: objPart.Size,
+ PartNum: uploadReq.PartNum,
+ Part: uploadReq.Part,
+ }
+ }
+ }(w, partSize)
+ }
+
+ // Gather the responses as they occur and update any
+ // progress bar.
+ for u := 1; u <= totalPartsCount; u++ {
+ uploadRes := <-uploadedPartsCh
+ if uploadRes.Error != nil {
+ return UploadInfo{}, uploadRes.Error
+ }
+ // Update the totalUploadedSize.
+ totalUploadedSize += uploadRes.Size
+ // Store the parts to be completed in order.
+ complMultipartUpload.Parts = append(complMultipartUpload.Parts, CompletePart{
+ ETag: uploadRes.Part.ETag,
+ PartNumber: uploadRes.Part.PartNumber,
+ })
+ }
+
+ // Verify if we uploaded all the data.
+ if totalUploadedSize != size {
+ return UploadInfo{}, errUnexpectedEOF(totalUploadedSize, size, bucketName, objectName)
+ }
+
+ // Sort all completed parts.
+ sort.Sort(completedParts(complMultipartUpload.Parts))
+
+ uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ uploadInfo.Size = totalUploadedSize
+ return uploadInfo, nil
+}
+
+func (c Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, bucketName, objectName string,
+ reader io.Reader, size int64, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Input validation.
+ if err = s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Calculate the optimal parts info for a given size.
+ totalPartsCount, partSize, lastPartSize, err := OptimalPartInfo(size, opts.PartSize)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ // Initiates a new multipart request
+ uploadID, err := c.newUploadID(ctx, bucketName, objectName, opts)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Aborts the multipart upload if the function returns
+ // any error, since we do not resume we should purge
+ // the parts which have been uploaded to relinquish
+ // storage space.
+ defer func() {
+ if err != nil {
+ c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
+ }
+ }()
+
+ // Total data read and written to server. should be equal to 'size' at the end of the call.
+ var totalUploadedSize int64
+
+ // Initialize parts uploaded map.
+ partsInfo := make(map[int]ObjectPart)
+
+ // Create a buffer.
+ buf := make([]byte, partSize)
+
+ // Avoid declaring variables in the for loop
+ var md5Base64 string
+ var hookReader io.Reader
+
+ // Part number always starts with '1'.
+ var partNumber int
+ for partNumber = 1; partNumber <= totalPartsCount; partNumber++ {
+
+ // Proceed to upload the part.
+ if partNumber == totalPartsCount {
+ partSize = lastPartSize
+ }
+
+ if opts.SendContentMd5 {
+ length, rerr := readFull(reader, buf)
+ if rerr == io.EOF && partNumber > 1 {
+ break
+ }
+
+ if rerr != nil && rerr != io.ErrUnexpectedEOF && err != io.EOF {
+ return UploadInfo{}, rerr
+ }
+
+ // Calculate md5sum.
+ hash := c.md5Hasher()
+ hash.Write(buf[:length])
+ md5Base64 = base64.StdEncoding.EncodeToString(hash.Sum(nil))
+ hash.Close()
+
+ // Update progress reader appropriately to the latest offset
+ // as we read from the source.
+ hookReader = newHook(bytes.NewReader(buf[:length]), opts.Progress)
+ } else {
+ // Update progress reader appropriately to the latest offset
+ // as we read from the source.
+ hookReader = newHook(reader, opts.Progress)
+ }
+
+ objPart, uerr := c.uploadPart(ctx, bucketName, objectName, uploadID,
+ io.LimitReader(hookReader, partSize),
+ partNumber, md5Base64, "", partSize, opts.ServerSideEncryption)
+ if uerr != nil {
+ return UploadInfo{}, uerr
+ }
+
+ // Save successfully uploaded part metadata.
+ partsInfo[partNumber] = objPart
+
+ // Save successfully uploaded size.
+ totalUploadedSize += partSize
+ }
+
+ // Verify if we uploaded all the data.
+ if size > 0 {
+ if totalUploadedSize != size {
+ return UploadInfo{}, errUnexpectedEOF(totalUploadedSize, size, bucketName, objectName)
+ }
+ }
+
+ // Complete multipart upload.
+ var complMultipartUpload completeMultipartUpload
+
+ // Loop over total uploaded parts to save them in
+ // Parts array before completing the multipart request.
+ for i := 1; i < partNumber; i++ {
+ part, ok := partsInfo[i]
+ if !ok {
+ return UploadInfo{}, errInvalidArgument(fmt.Sprintf("Missing part number %d", i))
+ }
+ complMultipartUpload.Parts = append(complMultipartUpload.Parts, CompletePart{
+ ETag: part.ETag,
+ PartNumber: part.PartNumber,
+ })
+ }
+
+ // Sort all completed parts.
+ sort.Sort(completedParts(complMultipartUpload.Parts))
+
+ uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ uploadInfo.Size = totalUploadedSize
+ return uploadInfo, nil
+}
+
+// putObject special function used Google Cloud Storage. This special function
+// is used for Google Cloud Storage since Google's multipart API is not S3 compatible.
+func (c Client) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, size int64, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Size -1 is only supported on Google Cloud Storage, we error
+ // out in all other situations.
+ if size < 0 && !s3utils.IsGoogleEndpoint(*c.endpointURL) {
+ return UploadInfo{}, errEntityTooSmall(size, bucketName, objectName)
+ }
+
+ if opts.SendContentMd5 && s3utils.IsGoogleEndpoint(*c.endpointURL) && size < 0 {
+ return UploadInfo{}, errInvalidArgument("MD5Sum cannot be calculated with size '-1'")
+ }
+
+ if size > 0 {
+ if isReadAt(reader) && !isObject(reader) {
+ seeker, ok := reader.(io.Seeker)
+ if ok {
+ offset, err := seeker.Seek(0, io.SeekCurrent)
+ if err != nil {
+ return UploadInfo{}, errInvalidArgument(err.Error())
+ }
+ reader = io.NewSectionReader(reader.(io.ReaderAt), offset, size)
+ }
+ }
+ }
+
+ var md5Base64 string
+ if opts.SendContentMd5 {
+ // Create a buffer.
+ buf := make([]byte, size)
+
+ length, rErr := readFull(reader, buf)
+ if rErr != nil && rErr != io.ErrUnexpectedEOF && rErr != io.EOF {
+ return UploadInfo{}, rErr
+ }
+
+ // Calculate md5sum.
+ hash := c.md5Hasher()
+ hash.Write(buf[:length])
+ md5Base64 = base64.StdEncoding.EncodeToString(hash.Sum(nil))
+ reader = bytes.NewReader(buf[:length])
+ hash.Close()
+ }
+
+ // Update progress reader appropriately to the latest offset as we
+ // read from the source.
+ readSeeker := newHook(reader, opts.Progress)
+
+ // This function does not calculate sha256 and md5sum for payload.
+ // Execute put object.
+ return c.putObjectDo(ctx, bucketName, objectName, readSeeker, md5Base64, "", size, opts)
+}
+
+// putObjectDo - executes the put object http operation.
+// NOTE: You must have WRITE permissions on a bucket to add an object to it.
+func (c Client) putObjectDo(ctx context.Context, bucketName, objectName string, reader io.Reader, md5Base64, sha256Hex string, size int64, opts PutObjectOptions) (UploadInfo, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+ // Set headers.
+ customHeader := opts.Header()
+
+ // Populate request metadata.
+ reqMetadata := requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ customHeader: customHeader,
+ contentBody: reader,
+ contentLength: size,
+ contentMD5Base64: md5Base64,
+ contentSHA256Hex: sha256Hex,
+ }
+ if opts.Internal.SourceVersionID != "" {
+ if _, err := uuid.Parse(opts.Internal.SourceVersionID); err != nil {
+ return UploadInfo{}, errInvalidArgument(err.Error())
+ }
+ urlValues := make(url.Values)
+ urlValues.Set("versionId", opts.Internal.SourceVersionID)
+ reqMetadata.queryValues = urlValues
+ }
+
+ // Execute PUT an objectName.
+ resp, err := c.executeMethod(ctx, http.MethodPut, reqMetadata)
+ defer closeResponse(resp)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ return UploadInfo{}, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+
+ // extract lifecycle expiry date and rule ID
+ expTime, ruleID := amzExpirationToExpiryDateRuleID(resp.Header.Get(amzExpiration))
+
+ return UploadInfo{
+ Bucket: bucketName,
+ Key: objectName,
+ ETag: trimEtag(resp.Header.Get("ETag")),
+ VersionID: resp.Header.Get(amzVersionID),
+ Size: size,
+ Expiration: expTime,
+ ExpirationRuleID: ruleID,
+ }, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object.go b/vendor/github.com/minio/minio-go/v7/api-put-object.go
new file mode 100644
index 00000000..247e40a6
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-put-object.go
@@ -0,0 +1,370 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "sort"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "golang.org/x/net/http/httpguts"
+)
+
+// ReplicationStatus represents replication status of object
+type ReplicationStatus string
+
+const (
+ // ReplicationStatusPending indicates replication is pending
+ ReplicationStatusPending ReplicationStatus = "PENDING"
+ // ReplicationStatusComplete indicates replication completed ok
+ ReplicationStatusComplete ReplicationStatus = "COMPLETE"
+ // ReplicationStatusFailed indicates replication failed
+ ReplicationStatusFailed ReplicationStatus = "FAILED"
+ // ReplicationStatusReplica indicates object is a replica of a source
+ ReplicationStatusReplica ReplicationStatus = "REPLICA"
+)
+
+// Empty returns true if no replication status set.
+func (r ReplicationStatus) Empty() bool {
+ return r == ""
+}
+
+// AdvancedPutOptions for internal use - to be utilized by replication, ILM transition
+// implementation on MinIO server
+type AdvancedPutOptions struct {
+ SourceVersionID string
+ SourceETag string
+ ReplicationStatus ReplicationStatus
+ SourceMTime time.Time
+ ReplicationRequest bool
+}
+
+// PutObjectOptions represents options specified by user for PutObject call
+type PutObjectOptions struct {
+ UserMetadata map[string]string
+ UserTags map[string]string
+ Progress io.Reader
+ ContentType string
+ ContentEncoding string
+ ContentDisposition string
+ ContentLanguage string
+ CacheControl string
+ Mode RetentionMode
+ RetainUntilDate time.Time
+ ServerSideEncryption encrypt.ServerSide
+ NumThreads uint
+ StorageClass string
+ WebsiteRedirectLocation string
+ PartSize uint64
+ LegalHold LegalHoldStatus
+ SendContentMd5 bool
+ DisableMultipart bool
+ Internal AdvancedPutOptions
+}
+
+// getNumThreads - gets the number of threads to be used in the multipart
+// put object operation
+func (opts PutObjectOptions) getNumThreads() (numThreads int) {
+ if opts.NumThreads > 0 {
+ numThreads = int(opts.NumThreads)
+ } else {
+ numThreads = totalWorkers
+ }
+ return
+}
+
+// Header - constructs the headers from metadata entered by user in
+// PutObjectOptions struct
+func (opts PutObjectOptions) Header() (header http.Header) {
+ header = make(http.Header)
+
+ contentType := opts.ContentType
+ if contentType == "" {
+ contentType = "application/octet-stream"
+ }
+ header.Set("Content-Type", contentType)
+
+ if opts.ContentEncoding != "" {
+ header.Set("Content-Encoding", opts.ContentEncoding)
+ }
+ if opts.ContentDisposition != "" {
+ header.Set("Content-Disposition", opts.ContentDisposition)
+ }
+ if opts.ContentLanguage != "" {
+ header.Set("Content-Language", opts.ContentLanguage)
+ }
+ if opts.CacheControl != "" {
+ header.Set("Cache-Control", opts.CacheControl)
+ }
+
+ if opts.Mode != "" {
+ header.Set(amzLockMode, opts.Mode.String())
+ }
+
+ if !opts.RetainUntilDate.IsZero() {
+ header.Set("X-Amz-Object-Lock-Retain-Until-Date", opts.RetainUntilDate.Format(time.RFC3339))
+ }
+
+ if opts.LegalHold != "" {
+ header.Set(amzLegalHoldHeader, opts.LegalHold.String())
+ }
+
+ if opts.ServerSideEncryption != nil {
+ opts.ServerSideEncryption.Marshal(header)
+ }
+
+ if opts.StorageClass != "" {
+ header.Set(amzStorageClass, opts.StorageClass)
+ }
+
+ if opts.WebsiteRedirectLocation != "" {
+ header.Set(amzWebsiteRedirectLocation, opts.WebsiteRedirectLocation)
+ }
+
+ if !opts.Internal.ReplicationStatus.Empty() {
+ header.Set(amzBucketReplicationStatus, string(opts.Internal.ReplicationStatus))
+ }
+ if !opts.Internal.SourceMTime.IsZero() {
+ header.Set(minIOBucketSourceMTime, opts.Internal.SourceMTime.Format(time.RFC3339Nano))
+ }
+ if opts.Internal.SourceETag != "" {
+ header.Set(minIOBucketSourceETag, opts.Internal.SourceETag)
+ }
+ if opts.Internal.ReplicationRequest {
+ header.Set(minIOBucketReplicationRequest, "")
+ }
+ if len(opts.UserTags) != 0 {
+ header.Set(amzTaggingHeader, s3utils.TagEncode(opts.UserTags))
+ }
+
+ for k, v := range opts.UserMetadata {
+ if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) {
+ header.Set(k, v)
+ } else {
+ header.Set("x-amz-meta-"+k, v)
+ }
+ }
+ return
+}
+
+// validate() checks if the UserMetadata map has standard headers or and raises an error if so.
+func (opts PutObjectOptions) validate() (err error) {
+ for k, v := range opts.UserMetadata {
+ if !httpguts.ValidHeaderFieldName(k) || isStandardHeader(k) || isSSEHeader(k) || isStorageClassHeader(k) {
+ return errInvalidArgument(k + " unsupported user defined metadata name")
+ }
+ if !httpguts.ValidHeaderFieldValue(v) {
+ return errInvalidArgument(v + " unsupported user defined metadata value")
+ }
+ }
+ if opts.Mode != "" && !opts.Mode.IsValid() {
+ return errInvalidArgument(opts.Mode.String() + " unsupported retention mode")
+ }
+ if opts.LegalHold != "" && !opts.LegalHold.IsValid() {
+ return errInvalidArgument(opts.LegalHold.String() + " unsupported legal-hold status")
+ }
+ return nil
+}
+
+// completedParts is a collection of parts sortable by their part numbers.
+// used for sorting the uploaded parts before completing the multipart request.
+type completedParts []CompletePart
+
+func (a completedParts) Len() int { return len(a) }
+func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].PartNumber }
+
+// PutObject creates an object in a bucket.
+//
+// You must have WRITE permissions on a bucket to create an object.
+//
+// - For size smaller than 128MiB PutObject automatically does a
+// single atomic Put operation.
+// - For size larger than 128MiB PutObject automatically does a
+// multipart Put operation.
+// - For size input as -1 PutObject does a multipart Put operation
+// until input stream reaches EOF. Maximum object size that can
+// be uploaded through this operation will be 5TiB.
+func (c Client) PutObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64,
+ opts PutObjectOptions) (info UploadInfo, err error) {
+ if objectSize < 0 && opts.DisableMultipart {
+ return UploadInfo{}, errors.New("object size must be provided with disable multipart upload")
+ }
+
+ err = opts.validate()
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ return c.putObjectCommon(ctx, bucketName, objectName, reader, objectSize, opts)
+}
+
+func (c Client) putObjectCommon(ctx context.Context, bucketName, objectName string, reader io.Reader, size int64, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Check for largest object size allowed.
+ if size > int64(maxMultipartPutObjectSize) {
+ return UploadInfo{}, errEntityTooLarge(size, maxMultipartPutObjectSize, bucketName, objectName)
+ }
+
+ // NOTE: Streaming signature is not supported by GCS.
+ if s3utils.IsGoogleEndpoint(*c.endpointURL) {
+ return c.putObject(ctx, bucketName, objectName, reader, size, opts)
+ }
+
+ partSize := opts.PartSize
+ if opts.PartSize == 0 {
+ partSize = minPartSize
+ }
+
+ if c.overrideSignerType.IsV2() {
+ if size >= 0 && size < int64(partSize) || opts.DisableMultipart {
+ return c.putObject(ctx, bucketName, objectName, reader, size, opts)
+ }
+ return c.putObjectMultipart(ctx, bucketName, objectName, reader, size, opts)
+ }
+
+ if size < 0 {
+ return c.putObjectMultipartStreamNoLength(ctx, bucketName, objectName, reader, opts)
+ }
+
+ if size < int64(partSize) || opts.DisableMultipart {
+ return c.putObject(ctx, bucketName, objectName, reader, size, opts)
+ }
+
+ return c.putObjectMultipartStream(ctx, bucketName, objectName, reader, size, opts)
+}
+
+func (c Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketName, objectName string, reader io.Reader, opts PutObjectOptions) (info UploadInfo, err error) {
+ // Input validation.
+ if err = s3utils.CheckValidBucketName(bucketName); err != nil {
+ return UploadInfo{}, err
+ }
+ if err = s3utils.CheckValidObjectName(objectName); err != nil {
+ return UploadInfo{}, err
+ }
+
+ // Total data read and written to server. should be equal to
+ // 'size' at the end of the call.
+ var totalUploadedSize int64
+
+ // Complete multipart upload.
+ var complMultipartUpload completeMultipartUpload
+
+ // Calculate the optimal parts info for a given size.
+ totalPartsCount, partSize, _, err := OptimalPartInfo(-1, opts.PartSize)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+ // Initiate a new multipart upload.
+ uploadID, err := c.newUploadID(ctx, bucketName, objectName, opts)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ defer func() {
+ if err != nil {
+ c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
+ }
+ }()
+
+ // Part number always starts with '1'.
+ partNumber := 1
+
+ // Initialize parts uploaded map.
+ partsInfo := make(map[int]ObjectPart)
+
+ // Create a buffer.
+ buf := make([]byte, partSize)
+
+ for partNumber <= totalPartsCount {
+ length, rerr := readFull(reader, buf)
+ if rerr == io.EOF && partNumber > 1 {
+ break
+ }
+
+ if rerr != nil && rerr != io.ErrUnexpectedEOF && rerr != io.EOF {
+ return UploadInfo{}, rerr
+ }
+
+ var md5Base64 string
+ if opts.SendContentMd5 {
+ // Calculate md5sum.
+ hash := c.md5Hasher()
+ hash.Write(buf[:length])
+ md5Base64 = base64.StdEncoding.EncodeToString(hash.Sum(nil))
+ hash.Close()
+ }
+
+ // Update progress reader appropriately to the latest offset
+ // as we read from the source.
+ rd := newHook(bytes.NewReader(buf[:length]), opts.Progress)
+
+ // Proceed to upload the part.
+ objPart, uerr := c.uploadPart(ctx, bucketName, objectName, uploadID, rd, partNumber,
+ md5Base64, "", int64(length), opts.ServerSideEncryption)
+ if uerr != nil {
+ return UploadInfo{}, uerr
+ }
+
+ // Save successfully uploaded part metadata.
+ partsInfo[partNumber] = objPart
+
+ // Save successfully uploaded size.
+ totalUploadedSize += int64(length)
+
+ // Increment part number.
+ partNumber++
+
+ // For unknown size, Read EOF we break away.
+ // We do not have to upload till totalPartsCount.
+ if rerr == io.EOF {
+ break
+ }
+ }
+
+ // Loop over total uploaded parts to save them in
+ // Parts array before completing the multipart request.
+ for i := 1; i < partNumber; i++ {
+ part, ok := partsInfo[i]
+ if !ok {
+ return UploadInfo{}, errInvalidArgument(fmt.Sprintf("Missing part number %d", i))
+ }
+ complMultipartUpload.Parts = append(complMultipartUpload.Parts, CompletePart{
+ ETag: part.ETag,
+ PartNumber: part.PartNumber,
+ })
+ }
+
+ // Sort all completed parts.
+ sort.Sort(completedParts(complMultipartUpload.Parts))
+
+ uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload)
+ if err != nil {
+ return UploadInfo{}, err
+ }
+
+ uploadInfo.Size = totalUploadedSize
+ return uploadInfo, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-remove.go b/vendor/github.com/minio/minio-go/v7/api-remove.go
new file mode 100644
index 00000000..f21a72c9
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-remove.go
@@ -0,0 +1,419 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/xml"
+ "io"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// RemoveBucket deletes the bucket name.
+//
+// All objects (including all object versions and delete markers).
+// in the bucket must be deleted before successfully attempting this request.
+func (c Client) RemoveBucket(ctx context.Context, bucketName string) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+ // Execute DELETE on bucket.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+
+ // Remove the location from cache on a successful delete.
+ c.bucketLocCache.Delete(bucketName)
+
+ return nil
+}
+
+// AdvancedRemoveOptions intended for internal use by replication
+type AdvancedRemoveOptions struct {
+ ReplicationDeleteMarker bool
+ ReplicationStatus ReplicationStatus
+ ReplicationMTime time.Time
+ ReplicationRequest bool
+}
+
+// RemoveObjectOptions represents options specified by user for RemoveObject call
+type RemoveObjectOptions struct {
+ GovernanceBypass bool
+ VersionID string
+ Internal AdvancedRemoveOptions
+}
+
+// RemoveObject removes an object from a bucket.
+func (c Client) RemoveObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return err
+ }
+
+ return c.removeObject(ctx, bucketName, objectName, opts)
+}
+
+func (c Client) removeObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) error {
+
+ // Get resources properly escaped and lined up before
+ // using them in http request.
+ urlValues := make(url.Values)
+
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+
+ // Build headers.
+ headers := make(http.Header)
+
+ if opts.GovernanceBypass {
+ // Set the bypass goverenance retention header
+ headers.Set(amzBypassGovernance, "true")
+ }
+ if opts.Internal.ReplicationDeleteMarker {
+ headers.Set(minIOBucketReplicationDeleteMarker, "true")
+ }
+ if !opts.Internal.ReplicationMTime.IsZero() {
+ headers.Set(minIOBucketSourceMTime, opts.Internal.ReplicationMTime.Format(time.RFC3339Nano))
+ }
+ if !opts.Internal.ReplicationStatus.Empty() {
+ headers.Set(amzBucketReplicationStatus, string(opts.Internal.ReplicationStatus))
+ }
+ if opts.Internal.ReplicationRequest {
+ headers.Set(minIOBucketReplicationRequest, "")
+ }
+ // Execute DELETE on objectName.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ contentSHA256Hex: emptySHA256Hex,
+ queryValues: urlValues,
+ customHeader: headers,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ // if some unexpected error happened and max retry is reached, we want to let client know
+ if resp.StatusCode != http.StatusNoContent {
+ return httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+
+ // DeleteObject always responds with http '204' even for
+ // objects which do not exist. So no need to handle them
+ // specifically.
+ return nil
+}
+
+// RemoveObjectError - container of Multi Delete S3 API error
+type RemoveObjectError struct {
+ ObjectName string
+ VersionID string
+ Err error
+}
+
+// generateRemoveMultiObjects - generate the XML request for remove multi objects request
+func generateRemoveMultiObjectsRequest(objects []ObjectInfo) []byte {
+ delObjects := []deleteObject{}
+ for _, obj := range objects {
+ delObjects = append(delObjects, deleteObject{
+ Key: obj.Key,
+ VersionID: obj.VersionID,
+ })
+ }
+ xmlBytes, _ := xml.Marshal(deleteMultiObjects{Objects: delObjects, Quiet: true})
+ return xmlBytes
+}
+
+// processRemoveMultiObjectsResponse - parse the remove multi objects web service
+// and return the success/failure result status for each object
+func processRemoveMultiObjectsResponse(body io.Reader, objects []ObjectInfo, errorCh chan<- RemoveObjectError) {
+ // Parse multi delete XML response
+ rmResult := &deleteMultiObjectsResult{}
+ err := xmlDecoder(body, rmResult)
+ if err != nil {
+ errorCh <- RemoveObjectError{ObjectName: "", Err: err}
+ return
+ }
+
+ // Fill deletion that returned an error.
+ for _, obj := range rmResult.UnDeletedObjects {
+ // Version does not exist is not an error ignore and continue.
+ switch obj.Code {
+ case "InvalidArgument", "NoSuchVersion":
+ continue
+ }
+ errorCh <- RemoveObjectError{
+ ObjectName: obj.Key,
+ VersionID: obj.VersionID,
+ Err: ErrorResponse{
+ Code: obj.Code,
+ Message: obj.Message,
+ },
+ }
+ }
+}
+
+// RemoveObjectsOptions represents options specified by user for RemoveObjects call
+type RemoveObjectsOptions struct {
+ GovernanceBypass bool
+}
+
+// RemoveObjects removes multiple objects from a bucket while
+// it is possible to specify objects versions which are received from
+// objectsCh. Remove failures are sent back via error channel.
+func (c Client) RemoveObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, opts RemoveObjectsOptions) <-chan RemoveObjectError {
+ errorCh := make(chan RemoveObjectError, 1)
+
+ // Validate if bucket name is valid.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ defer close(errorCh)
+ errorCh <- RemoveObjectError{
+ Err: err,
+ }
+ return errorCh
+ }
+ // Validate objects channel to be properly allocated.
+ if objectsCh == nil {
+ defer close(errorCh)
+ errorCh <- RemoveObjectError{
+ Err: errInvalidArgument("Objects channel cannot be nil"),
+ }
+ return errorCh
+ }
+
+ go c.removeObjects(ctx, bucketName, objectsCh, errorCh, opts)
+ return errorCh
+}
+
+// Return true if the character is within the allowed characters in an XML 1.0 document
+// The list of allowed characters can be found here: https://www.w3.org/TR/xml/#charsets
+func validXMLChar(r rune) (ok bool) {
+ return r == 0x09 ||
+ r == 0x0A ||
+ r == 0x0D ||
+ r >= 0x20 && r <= 0xD7FF ||
+ r >= 0xE000 && r <= 0xFFFD ||
+ r >= 0x10000 && r <= 0x10FFFF
+}
+
+func hasInvalidXMLChar(str string) bool {
+ for _, s := range str {
+ if !validXMLChar(s) {
+ return true
+ }
+ }
+ return false
+}
+
+// Generate and call MultiDelete S3 requests based on entries received from objectsCh
+func (c Client) removeObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, errorCh chan<- RemoveObjectError, opts RemoveObjectsOptions) {
+ maxEntries := 1000
+ finish := false
+ urlValues := make(url.Values)
+ urlValues.Set("delete", "")
+
+ // Close error channel when Multi delete finishes.
+ defer close(errorCh)
+
+ // Loop over entries by 1000 and call MultiDelete requests
+ for {
+ if finish {
+ break
+ }
+ count := 0
+ var batch []ObjectInfo
+
+ // Try to gather 1000 entries
+ for object := range objectsCh {
+ if hasInvalidXMLChar(object.Key) {
+ // Use single DELETE so the object name will be in the request URL instead of the multi-delete XML document.
+ err := c.removeObject(ctx, bucketName, object.Key, RemoveObjectOptions{
+ VersionID: object.VersionID,
+ GovernanceBypass: opts.GovernanceBypass,
+ })
+ if err != nil {
+ // Version does not exist is not an error ignore and continue.
+ switch ToErrorResponse(err).Code {
+ case "InvalidArgument", "NoSuchVersion":
+ continue
+ }
+ errorCh <- RemoveObjectError{
+ ObjectName: object.Key,
+ VersionID: object.VersionID,
+ Err: err,
+ }
+ }
+ continue
+ }
+
+ batch = append(batch, object)
+ if count++; count >= maxEntries {
+ break
+ }
+ }
+ if count == 0 {
+ // Multi Objects Delete API doesn't accept empty object list, quit immediately
+ break
+ }
+ if count < maxEntries {
+ // We didn't have 1000 entries, so this is the last batch
+ finish = true
+ }
+
+ // Build headers.
+ headers := make(http.Header)
+ if opts.GovernanceBypass {
+ // Set the bypass goverenance retention header
+ headers.Set(amzBypassGovernance, "true")
+ }
+
+ // Generate remove multi objects XML request
+ removeBytes := generateRemoveMultiObjectsRequest(batch)
+ // Execute GET on bucket to list objects.
+ resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
+ bucketName: bucketName,
+ queryValues: urlValues,
+ contentBody: bytes.NewReader(removeBytes),
+ contentLength: int64(len(removeBytes)),
+ contentMD5Base64: sumMD5Base64(removeBytes),
+ contentSHA256Hex: sum256Hex(removeBytes),
+ customHeader: headers,
+ })
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ e := httpRespToErrorResponse(resp, bucketName, "")
+ errorCh <- RemoveObjectError{ObjectName: "", Err: e}
+ }
+ }
+ if err != nil {
+ for _, b := range batch {
+ errorCh <- RemoveObjectError{
+ ObjectName: b.Key,
+ VersionID: b.VersionID,
+ Err: err,
+ }
+ }
+ continue
+ }
+
+ // Process multiobjects remove xml response
+ processRemoveMultiObjectsResponse(resp.Body, batch, errorCh)
+
+ closeResponse(resp)
+ }
+}
+
+// RemoveIncompleteUpload aborts an partially uploaded object.
+func (c Client) RemoveIncompleteUpload(ctx context.Context, bucketName, objectName string) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return err
+ }
+ // Find multipart upload ids of the object to be aborted.
+ uploadIDs, err := c.findUploadIDs(ctx, bucketName, objectName)
+ if err != nil {
+ return err
+ }
+
+ for _, uploadID := range uploadIDs {
+ // abort incomplete multipart upload, based on the upload id passed.
+ err := c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// abortMultipartUpload aborts a multipart upload for the given
+// uploadID, all previously uploaded parts are deleted.
+func (c Client) abortMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string) error {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return err
+ }
+
+ // Initialize url queries.
+ urlValues := make(url.Values)
+ urlValues.Set("uploadId", uploadID)
+
+ // Execute DELETE on multipart upload.
+ resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return err
+ }
+ if resp != nil {
+ if resp.StatusCode != http.StatusNoContent {
+ // Abort has no response body, handle it for any errors.
+ var errorResponse ErrorResponse
+ switch resp.StatusCode {
+ case http.StatusNotFound:
+ // This is needed specifically for abort and it cannot
+ // be converged into default case.
+ errorResponse = ErrorResponse{
+ Code: "NoSuchUpload",
+ Message: "The specified multipart upload does not exist.",
+ BucketName: bucketName,
+ Key: objectName,
+ RequestID: resp.Header.Get("x-amz-request-id"),
+ HostID: resp.Header.Get("x-amz-id-2"),
+ Region: resp.Header.Get("x-amz-bucket-region"),
+ }
+ default:
+ return httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ return errorResponse
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go b/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go
new file mode 100644
index 00000000..37ed97b7
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go
@@ -0,0 +1,361 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "encoding/xml"
+ "errors"
+ "io"
+ "reflect"
+ "time"
+)
+
+// listAllMyBucketsResult container for listBuckets response.
+type listAllMyBucketsResult struct {
+ // Container for one or more buckets.
+ Buckets struct {
+ Bucket []BucketInfo
+ }
+ Owner owner
+}
+
+// owner container for bucket owner information.
+type owner struct {
+ DisplayName string
+ ID string
+}
+
+// CommonPrefix container for prefix response.
+type CommonPrefix struct {
+ Prefix string
+}
+
+// ListBucketV2Result container for listObjects response version 2.
+type ListBucketV2Result struct {
+ // A response can contain CommonPrefixes only if you have
+ // specified a delimiter.
+ CommonPrefixes []CommonPrefix
+ // Metadata about each object returned.
+ Contents []ObjectInfo
+ Delimiter string
+
+ // Encoding type used to encode object keys in the response.
+ EncodingType string
+
+ // A flag that indicates whether or not ListObjects returned all of the results
+ // that satisfied the search criteria.
+ IsTruncated bool
+ MaxKeys int64
+ Name string
+
+ // Hold the token that will be sent in the next request to fetch the next group of keys
+ NextContinuationToken string
+
+ ContinuationToken string
+ Prefix string
+
+ // FetchOwner and StartAfter are currently not used
+ FetchOwner string
+ StartAfter string
+}
+
+// Version is an element in the list object versions response
+type Version struct {
+ ETag string
+ IsLatest bool
+ Key string
+ LastModified time.Time
+ Owner Owner
+ Size int64
+ StorageClass string
+ VersionID string `xml:"VersionId"`
+
+ isDeleteMarker bool
+}
+
+// ListVersionsResult is an element in the list object versions response
+// and has a special Unmarshaler because we need to preserver the order
+// of <Version> and <DeleteMarker> in ListVersionsResult.Versions slice
+type ListVersionsResult struct {
+ Versions []Version
+
+ CommonPrefixes []CommonPrefix
+ Name string
+ Prefix string
+ Delimiter string
+ MaxKeys int64
+ EncodingType string
+ IsTruncated bool
+ KeyMarker string
+ VersionIDMarker string
+ NextKeyMarker string
+ NextVersionIDMarker string
+}
+
+// UnmarshalXML is a custom unmarshal code for the response of ListObjectVersions, the custom
+// code will unmarshal <Version> and <DeleteMarker> tags and save them in Versions field to
+// preserve the lexical order of the listing.
+func (l *ListVersionsResult) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
+ for {
+ // Read tokens from the XML document in a stream.
+ t, err := d.Token()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return err
+ }
+
+ switch se := t.(type) {
+ case xml.StartElement:
+ tagName := se.Name.Local
+ switch tagName {
+ case "Name", "Prefix",
+ "Delimiter", "EncodingType",
+ "KeyMarker", "NextKeyMarker":
+ var s string
+ if err = d.DecodeElement(&s, &se); err != nil {
+ return err
+ }
+ v := reflect.ValueOf(l).Elem().FieldByName(tagName)
+ if v.IsValid() {
+ v.SetString(s)
+ }
+ case "VersionIdMarker":
+ // VersionIdMarker is a special case because of 'Id' instead of 'ID' in field name
+ var s string
+ if err = d.DecodeElement(&s, &se); err != nil {
+ return err
+ }
+ l.VersionIDMarker = s
+ case "NextVersionIdMarker":
+ // NextVersionIdMarker is a special case because of 'Id' instead of 'ID' in field name
+ var s string
+ if err = d.DecodeElement(&s, &se); err != nil {
+ return err
+ }
+ l.NextVersionIDMarker = s
+ case "IsTruncated": // bool
+ var b bool
+ if err = d.DecodeElement(&b, &se); err != nil {
+ return err
+ }
+ l.IsTruncated = b
+ case "MaxKeys": // int64
+ var i int64
+ if err = d.DecodeElement(&i, &se); err != nil {
+ return err
+ }
+ l.MaxKeys = i
+ case "CommonPrefixes":
+ var cp CommonPrefix
+ if err = d.DecodeElement(&cp, &se); err != nil {
+ return err
+ }
+ l.CommonPrefixes = append(l.CommonPrefixes, cp)
+ case "DeleteMarker", "Version":
+ var v Version
+ if err = d.DecodeElement(&v, &se); err != nil {
+ return err
+ }
+ if tagName == "DeleteMarker" {
+ v.isDeleteMarker = true
+ }
+ l.Versions = append(l.Versions, v)
+ default:
+ return errors.New("unrecognized option:" + tagName)
+ }
+
+ }
+ }
+ return nil
+}
+
+// ListBucketResult container for listObjects response.
+type ListBucketResult struct {
+ // A response can contain CommonPrefixes only if you have
+ // specified a delimiter.
+ CommonPrefixes []CommonPrefix
+ // Metadata about each object returned.
+ Contents []ObjectInfo
+ Delimiter string
+
+ // Encoding type used to encode object keys in the response.
+ EncodingType string
+
+ // A flag that indicates whether or not ListObjects returned all of the results
+ // that satisfied the search criteria.
+ IsTruncated bool
+ Marker string
+ MaxKeys int64
+ Name string
+
+ // When response is truncated (the IsTruncated element value in
+ // the response is true), you can use the key name in this field
+ // as marker in the subsequent request to get next set of objects.
+ // Object storage lists objects in alphabetical order Note: This
+ // element is returned only if you have delimiter request
+ // parameter specified. If response does not include the NextMaker
+ // and it is truncated, you can use the value of the last Key in
+ // the response as the marker in the subsequent request to get the
+ // next set of object keys.
+ NextMarker string
+ Prefix string
+}
+
+// ListMultipartUploadsResult container for ListMultipartUploads response
+type ListMultipartUploadsResult struct {
+ Bucket string
+ KeyMarker string
+ UploadIDMarker string `xml:"UploadIdMarker"`
+ NextKeyMarker string
+ NextUploadIDMarker string `xml:"NextUploadIdMarker"`
+ EncodingType string
+ MaxUploads int64
+ IsTruncated bool
+ Uploads []ObjectMultipartInfo `xml:"Upload"`
+ Prefix string
+ Delimiter string
+ // A response can contain CommonPrefixes only if you specify a delimiter.
+ CommonPrefixes []CommonPrefix
+}
+
+// initiator container for who initiated multipart upload.
+type initiator struct {
+ ID string
+ DisplayName string
+}
+
+// copyObjectResult container for copy object response.
+type copyObjectResult struct {
+ ETag string
+ LastModified time.Time // time string format "2006-01-02T15:04:05.000Z"
+}
+
+// ObjectPart container for particular part of an object.
+type ObjectPart struct {
+ // Part number identifies the part.
+ PartNumber int
+
+ // Date and time the part was uploaded.
+ LastModified time.Time
+
+ // Entity tag returned when the part was uploaded, usually md5sum
+ // of the part.
+ ETag string
+
+ // Size of the uploaded part data.
+ Size int64
+}
+
+// ListObjectPartsResult container for ListObjectParts response.
+type ListObjectPartsResult struct {
+ Bucket string
+ Key string
+ UploadID string `xml:"UploadId"`
+
+ Initiator initiator
+ Owner owner
+
+ StorageClass string
+ PartNumberMarker int
+ NextPartNumberMarker int
+ MaxParts int
+
+ // Indicates whether the returned list of parts is truncated.
+ IsTruncated bool
+ ObjectParts []ObjectPart `xml:"Part"`
+
+ EncodingType string
+}
+
+// initiateMultipartUploadResult container for InitiateMultiPartUpload
+// response.
+type initiateMultipartUploadResult struct {
+ Bucket string
+ Key string
+ UploadID string `xml:"UploadId"`
+}
+
+// completeMultipartUploadResult container for completed multipart
+// upload response.
+type completeMultipartUploadResult struct {
+ Location string
+ Bucket string
+ Key string
+ ETag string
+}
+
+// CompletePart sub container lists individual part numbers and their
+// md5sum, part of completeMultipartUpload.
+type CompletePart struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Part" json:"-"`
+
+ // Part number identifies the part.
+ PartNumber int
+ ETag string
+}
+
+// completeMultipartUpload container for completing multipart upload.
+type completeMultipartUpload struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUpload" json:"-"`
+ Parts []CompletePart `xml:"Part"`
+}
+
+// createBucketConfiguration container for bucket configuration.
+type createBucketConfiguration struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CreateBucketConfiguration" json:"-"`
+ Location string `xml:"LocationConstraint"`
+}
+
+// deleteObject container for Delete element in MultiObjects Delete XML request
+type deleteObject struct {
+ Key string
+ VersionID string `xml:"VersionId,omitempty"`
+}
+
+// deletedObject container for Deleted element in MultiObjects Delete XML response
+type deletedObject struct {
+ Key string
+ VersionID string `xml:"VersionId,omitempty"`
+ // These fields are ignored.
+ DeleteMarker bool
+ DeleteMarkerVersionID string
+}
+
+// nonDeletedObject container for Error element (failed deletion) in MultiObjects Delete XML response
+type nonDeletedObject struct {
+ Key string
+ Code string
+ Message string
+ VersionID string `xml:"VersionId"`
+}
+
+// deletedMultiObjects container for MultiObjects Delete XML request
+type deleteMultiObjects struct {
+ XMLName xml.Name `xml:"Delete"`
+ Quiet bool
+ Objects []deleteObject `xml:"Object"`
+}
+
+// deletedMultiObjectsResult container for MultiObjects Delete XML response
+type deleteMultiObjectsResult struct {
+ XMLName xml.Name `xml:"DeleteResult"`
+ DeletedObjects []deletedObject `xml:"Deleted"`
+ UnDeletedObjects []nonDeletedObject `xml:"Error"`
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-select.go b/vendor/github.com/minio/minio-go/v7/api-select.go
new file mode 100644
index 00000000..e35cf02b
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-select.go
@@ -0,0 +1,751 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * (C) 2018-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "encoding/binary"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "hash"
+ "hash/crc32"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// CSVFileHeaderInfo - is the parameter for whether to utilize headers.
+type CSVFileHeaderInfo string
+
+// Constants for file header info.
+const (
+ CSVFileHeaderInfoNone CSVFileHeaderInfo = "NONE"
+ CSVFileHeaderInfoIgnore = "IGNORE"
+ CSVFileHeaderInfoUse = "USE"
+)
+
+// SelectCompressionType - is the parameter for what type of compression is
+// present
+type SelectCompressionType string
+
+// Constants for compression types under select API.
+const (
+ SelectCompressionNONE SelectCompressionType = "NONE"
+ SelectCompressionGZIP = "GZIP"
+ SelectCompressionBZIP = "BZIP2"
+)
+
+// CSVQuoteFields - is the parameter for how CSV fields are quoted.
+type CSVQuoteFields string
+
+// Constants for csv quote styles.
+const (
+ CSVQuoteFieldsAlways CSVQuoteFields = "Always"
+ CSVQuoteFieldsAsNeeded = "AsNeeded"
+)
+
+// QueryExpressionType - is of what syntax the expression is, this should only
+// be SQL
+type QueryExpressionType string
+
+// Constants for expression type.
+const (
+ QueryExpressionTypeSQL QueryExpressionType = "SQL"
+)
+
+// JSONType determines json input serialization type.
+type JSONType string
+
+// Constants for JSONTypes.
+const (
+ JSONDocumentType JSONType = "DOCUMENT"
+ JSONLinesType = "LINES"
+)
+
+// ParquetInputOptions parquet input specific options
+type ParquetInputOptions struct{}
+
+// CSVInputOptions csv input specific options
+type CSVInputOptions struct {
+ FileHeaderInfo CSVFileHeaderInfo
+ fileHeaderInfoSet bool
+
+ RecordDelimiter string
+ recordDelimiterSet bool
+
+ FieldDelimiter string
+ fieldDelimiterSet bool
+
+ QuoteCharacter string
+ quoteCharacterSet bool
+
+ QuoteEscapeCharacter string
+ quoteEscapeCharacterSet bool
+
+ Comments string
+ commentsSet bool
+}
+
+// SetFileHeaderInfo sets the file header info in the CSV input options
+func (c *CSVInputOptions) SetFileHeaderInfo(val CSVFileHeaderInfo) {
+ c.FileHeaderInfo = val
+ c.fileHeaderInfoSet = true
+}
+
+// SetRecordDelimiter sets the record delimiter in the CSV input options
+func (c *CSVInputOptions) SetRecordDelimiter(val string) {
+ c.RecordDelimiter = val
+ c.recordDelimiterSet = true
+}
+
+// SetFieldDelimiter sets the field delimiter in the CSV input options
+func (c *CSVInputOptions) SetFieldDelimiter(val string) {
+ c.FieldDelimiter = val
+ c.fieldDelimiterSet = true
+}
+
+// SetQuoteCharacter sets the quote character in the CSV input options
+func (c *CSVInputOptions) SetQuoteCharacter(val string) {
+ c.QuoteCharacter = val
+ c.quoteCharacterSet = true
+}
+
+// SetQuoteEscapeCharacter sets the quote escape character in the CSV input options
+func (c *CSVInputOptions) SetQuoteEscapeCharacter(val string) {
+ c.QuoteEscapeCharacter = val
+ c.quoteEscapeCharacterSet = true
+}
+
+// SetComments sets the comments character in the CSV input options
+func (c *CSVInputOptions) SetComments(val string) {
+ c.Comments = val
+ c.commentsSet = true
+}
+
+// MarshalXML - produces the xml representation of the CSV input options struct
+func (c CSVInputOptions) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ if c.FileHeaderInfo != "" || c.fileHeaderInfoSet {
+ if err := e.EncodeElement(c.FileHeaderInfo, xml.StartElement{Name: xml.Name{Local: "FileHeaderInfo"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.RecordDelimiter != "" || c.recordDelimiterSet {
+ if err := e.EncodeElement(c.RecordDelimiter, xml.StartElement{Name: xml.Name{Local: "RecordDelimiter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.FieldDelimiter != "" || c.fieldDelimiterSet {
+ if err := e.EncodeElement(c.FieldDelimiter, xml.StartElement{Name: xml.Name{Local: "FieldDelimiter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.QuoteCharacter != "" || c.quoteCharacterSet {
+ if err := e.EncodeElement(c.QuoteCharacter, xml.StartElement{Name: xml.Name{Local: "QuoteCharacter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.QuoteEscapeCharacter != "" || c.quoteEscapeCharacterSet {
+ if err := e.EncodeElement(c.QuoteEscapeCharacter, xml.StartElement{Name: xml.Name{Local: "QuoteEscapeCharacter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.Comments != "" || c.commentsSet {
+ if err := e.EncodeElement(c.Comments, xml.StartElement{Name: xml.Name{Local: "Comments"}}); err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// CSVOutputOptions csv output specific options
+type CSVOutputOptions struct {
+ QuoteFields CSVQuoteFields
+ quoteFieldsSet bool
+
+ RecordDelimiter string
+ recordDelimiterSet bool
+
+ FieldDelimiter string
+ fieldDelimiterSet bool
+
+ QuoteCharacter string
+ quoteCharacterSet bool
+
+ QuoteEscapeCharacter string
+ quoteEscapeCharacterSet bool
+}
+
+// SetQuoteFields sets the quote field parameter in the CSV output options
+func (c *CSVOutputOptions) SetQuoteFields(val CSVQuoteFields) {
+ c.QuoteFields = val
+ c.quoteFieldsSet = true
+}
+
+// SetRecordDelimiter sets the record delimiter character in the CSV output options
+func (c *CSVOutputOptions) SetRecordDelimiter(val string) {
+ c.RecordDelimiter = val
+ c.recordDelimiterSet = true
+}
+
+// SetFieldDelimiter sets the field delimiter character in the CSV output options
+func (c *CSVOutputOptions) SetFieldDelimiter(val string) {
+ c.FieldDelimiter = val
+ c.fieldDelimiterSet = true
+}
+
+// SetQuoteCharacter sets the quote character in the CSV output options
+func (c *CSVOutputOptions) SetQuoteCharacter(val string) {
+ c.QuoteCharacter = val
+ c.quoteCharacterSet = true
+}
+
+// SetQuoteEscapeCharacter sets the quote escape character in the CSV output options
+func (c *CSVOutputOptions) SetQuoteEscapeCharacter(val string) {
+ c.QuoteEscapeCharacter = val
+ c.quoteEscapeCharacterSet = true
+}
+
+// MarshalXML - produces the xml representation of the CSVOutputOptions struct
+func (c CSVOutputOptions) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+
+ if c.QuoteFields != "" || c.quoteFieldsSet {
+ if err := e.EncodeElement(c.QuoteFields, xml.StartElement{Name: xml.Name{Local: "QuoteFields"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.RecordDelimiter != "" || c.recordDelimiterSet {
+ if err := e.EncodeElement(c.RecordDelimiter, xml.StartElement{Name: xml.Name{Local: "RecordDelimiter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.FieldDelimiter != "" || c.fieldDelimiterSet {
+ if err := e.EncodeElement(c.FieldDelimiter, xml.StartElement{Name: xml.Name{Local: "FieldDelimiter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.QuoteCharacter != "" || c.quoteCharacterSet {
+ if err := e.EncodeElement(c.QuoteCharacter, xml.StartElement{Name: xml.Name{Local: "QuoteCharacter"}}); err != nil {
+ return err
+ }
+ }
+
+ if c.QuoteEscapeCharacter != "" || c.quoteEscapeCharacterSet {
+ if err := e.EncodeElement(c.QuoteEscapeCharacter, xml.StartElement{Name: xml.Name{Local: "QuoteEscapeCharacter"}}); err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// JSONInputOptions json input specific options
+type JSONInputOptions struct {
+ Type JSONType
+ typeSet bool
+}
+
+// SetType sets the JSON type in the JSON input options
+func (j *JSONInputOptions) SetType(typ JSONType) {
+ j.Type = typ
+ j.typeSet = true
+}
+
+// MarshalXML - produces the xml representation of the JSONInputOptions struct
+func (j JSONInputOptions) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+
+ if j.Type != "" || j.typeSet {
+ if err := e.EncodeElement(j.Type, xml.StartElement{Name: xml.Name{Local: "Type"}}); err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// JSONOutputOptions - json output specific options
+type JSONOutputOptions struct {
+ RecordDelimiter string
+ recordDelimiterSet bool
+}
+
+// SetRecordDelimiter sets the record delimiter in the JSON output options
+func (j *JSONOutputOptions) SetRecordDelimiter(val string) {
+ j.RecordDelimiter = val
+ j.recordDelimiterSet = true
+}
+
+// MarshalXML - produces the xml representation of the JSONOutputOptions struct
+func (j JSONOutputOptions) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+
+ if j.RecordDelimiter != "" || j.recordDelimiterSet {
+ if err := e.EncodeElement(j.RecordDelimiter, xml.StartElement{Name: xml.Name{Local: "RecordDelimiter"}}); err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// SelectObjectInputSerialization - input serialization parameters
+type SelectObjectInputSerialization struct {
+ CompressionType SelectCompressionType
+ Parquet *ParquetInputOptions `xml:"Parquet,omitempty"`
+ CSV *CSVInputOptions `xml:"CSV,omitempty"`
+ JSON *JSONInputOptions `xml:"JSON,omitempty"`
+}
+
+// SelectObjectOutputSerialization - output serialization parameters.
+type SelectObjectOutputSerialization struct {
+ CSV *CSVOutputOptions `xml:"CSV,omitempty"`
+ JSON *JSONOutputOptions `xml:"JSON,omitempty"`
+}
+
+// SelectObjectOptions - represents the input select body
+type SelectObjectOptions struct {
+ XMLName xml.Name `xml:"SelectObjectContentRequest" json:"-"`
+ ServerSideEncryption encrypt.ServerSide `xml:"-"`
+ Expression string
+ ExpressionType QueryExpressionType
+ InputSerialization SelectObjectInputSerialization
+ OutputSerialization SelectObjectOutputSerialization
+ RequestProgress struct {
+ Enabled bool
+ }
+}
+
+// Header returns the http.Header representation of the SelectObject options.
+func (o SelectObjectOptions) Header() http.Header {
+ headers := make(http.Header)
+ if o.ServerSideEncryption != nil && o.ServerSideEncryption.Type() == encrypt.SSEC {
+ o.ServerSideEncryption.Marshal(headers)
+ }
+ return headers
+}
+
+// SelectObjectType - is the parameter which defines what type of object the
+// operation is being performed on.
+type SelectObjectType string
+
+// Constants for input data types.
+const (
+ SelectObjectTypeCSV SelectObjectType = "CSV"
+ SelectObjectTypeJSON = "JSON"
+ SelectObjectTypeParquet = "Parquet"
+)
+
+// preludeInfo is used for keeping track of necessary information from the
+// prelude.
+type preludeInfo struct {
+ totalLen uint32
+ headerLen uint32
+}
+
+// SelectResults is used for the streaming responses from the server.
+type SelectResults struct {
+ pipeReader *io.PipeReader
+ resp *http.Response
+ stats *StatsMessage
+ progress *ProgressMessage
+}
+
+// ProgressMessage is a struct for progress xml message.
+type ProgressMessage struct {
+ XMLName xml.Name `xml:"Progress" json:"-"`
+ StatsMessage
+}
+
+// StatsMessage is a struct for stat xml message.
+type StatsMessage struct {
+ XMLName xml.Name `xml:"Stats" json:"-"`
+ BytesScanned int64
+ BytesProcessed int64
+ BytesReturned int64
+}
+
+// messageType represents the type of message.
+type messageType string
+
+const (
+ errorMsg messageType = "error"
+ commonMsg = "event"
+)
+
+// eventType represents the type of event.
+type eventType string
+
+// list of event-types returned by Select API.
+const (
+ endEvent eventType = "End"
+ recordsEvent = "Records"
+ progressEvent = "Progress"
+ statsEvent = "Stats"
+)
+
+// contentType represents content type of event.
+type contentType string
+
+const (
+ xmlContent contentType = "text/xml"
+)
+
+// SelectObjectContent is a implementation of http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectSELECTContent.html AWS S3 API.
+func (c Client) SelectObjectContent(ctx context.Context, bucketName, objectName string, opts SelectObjectOptions) (*SelectResults, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return nil, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return nil, err
+ }
+
+ selectReqBytes, err := xml.Marshal(opts)
+ if err != nil {
+ return nil, err
+ }
+
+ urlValues := make(url.Values)
+ urlValues.Set("select", "")
+ urlValues.Set("select-type", "2")
+
+ // Execute POST on bucket/object.
+ resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ customHeader: opts.Header(),
+ contentMD5Base64: sumMD5Base64(selectReqBytes),
+ contentSHA256Hex: sum256Hex(selectReqBytes),
+ contentBody: bytes.NewReader(selectReqBytes),
+ contentLength: int64(len(selectReqBytes)),
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return NewSelectResults(resp, bucketName)
+}
+
+// NewSelectResults creates a Select Result parser that parses the response
+// and returns a Reader that will return parsed and assembled select output.
+func NewSelectResults(resp *http.Response, bucketName string) (*SelectResults, error) {
+ if resp.StatusCode != http.StatusOK {
+ return nil, httpRespToErrorResponse(resp, bucketName, "")
+ }
+
+ pipeReader, pipeWriter := io.Pipe()
+ streamer := &SelectResults{
+ resp: resp,
+ stats: &StatsMessage{},
+ progress: &ProgressMessage{},
+ pipeReader: pipeReader,
+ }
+ streamer.start(pipeWriter)
+ return streamer, nil
+}
+
+// Close - closes the underlying response body and the stream reader.
+func (s *SelectResults) Close() error {
+ defer closeResponse(s.resp)
+ return s.pipeReader.Close()
+}
+
+// Read - is a reader compatible implementation for SelectObjectContent records.
+func (s *SelectResults) Read(b []byte) (n int, err error) {
+ return s.pipeReader.Read(b)
+}
+
+// Stats - information about a request's stats when processing is complete.
+func (s *SelectResults) Stats() *StatsMessage {
+ return s.stats
+}
+
+// Progress - information about the progress of a request.
+func (s *SelectResults) Progress() *ProgressMessage {
+ return s.progress
+}
+
+// start is the main function that decodes the large byte array into
+// several events that are sent through the eventstream.
+func (s *SelectResults) start(pipeWriter *io.PipeWriter) {
+ go func() {
+ for {
+ var prelude preludeInfo
+ var headers = make(http.Header)
+ var err error
+
+ // Create CRC code
+ crc := crc32.New(crc32.IEEETable)
+ crcReader := io.TeeReader(s.resp.Body, crc)
+
+ // Extract the prelude(12 bytes) into a struct to extract relevant information.
+ prelude, err = processPrelude(crcReader, crc)
+ if err != nil {
+ pipeWriter.CloseWithError(err)
+ closeResponse(s.resp)
+ return
+ }
+
+ // Extract the headers(variable bytes) into a struct to extract relevant information
+ if prelude.headerLen > 0 {
+ if err = extractHeader(io.LimitReader(crcReader, int64(prelude.headerLen)), headers); err != nil {
+ pipeWriter.CloseWithError(err)
+ closeResponse(s.resp)
+ return
+ }
+ }
+
+ // Get the actual payload length so that the appropriate amount of
+ // bytes can be read or parsed.
+ payloadLen := prelude.PayloadLen()
+
+ m := messageType(headers.Get("message-type"))
+
+ switch m {
+ case errorMsg:
+ pipeWriter.CloseWithError(errors.New(headers.Get("error-code") + ":\"" + headers.Get("error-message") + "\""))
+ closeResponse(s.resp)
+ return
+ case commonMsg:
+ // Get content-type of the payload.
+ c := contentType(headers.Get("content-type"))
+
+ // Get event type of the payload.
+ e := eventType(headers.Get("event-type"))
+
+ // Handle all supported events.
+ switch e {
+ case endEvent:
+ pipeWriter.Close()
+ closeResponse(s.resp)
+ return
+ case recordsEvent:
+ if _, err = io.Copy(pipeWriter, io.LimitReader(crcReader, payloadLen)); err != nil {
+ pipeWriter.CloseWithError(err)
+ closeResponse(s.resp)
+ return
+ }
+ case progressEvent:
+ switch c {
+ case xmlContent:
+ if err = xmlDecoder(io.LimitReader(crcReader, payloadLen), s.progress); err != nil {
+ pipeWriter.CloseWithError(err)
+ closeResponse(s.resp)
+ return
+ }
+ default:
+ pipeWriter.CloseWithError(fmt.Errorf("Unexpected content-type %s sent for event-type %s", c, progressEvent))
+ closeResponse(s.resp)
+ return
+ }
+ case statsEvent:
+ switch c {
+ case xmlContent:
+ if err = xmlDecoder(io.LimitReader(crcReader, payloadLen), s.stats); err != nil {
+ pipeWriter.CloseWithError(err)
+ closeResponse(s.resp)
+ return
+ }
+ default:
+ pipeWriter.CloseWithError(fmt.Errorf("Unexpected content-type %s sent for event-type %s", c, statsEvent))
+ closeResponse(s.resp)
+ return
+ }
+ }
+ }
+
+ // Ensures that the full message's CRC is correct and
+ // that the message is not corrupted
+ if err := checkCRC(s.resp.Body, crc.Sum32()); err != nil {
+ pipeWriter.CloseWithError(err)
+ closeResponse(s.resp)
+ return
+ }
+
+ }
+ }()
+}
+
+// PayloadLen is a function that calculates the length of the payload.
+func (p preludeInfo) PayloadLen() int64 {
+ return int64(p.totalLen - p.headerLen - 16)
+}
+
+// processPrelude is the function that reads the 12 bytes of the prelude and
+// ensures the CRC is correct while also extracting relevant information into
+// the struct,
+func processPrelude(prelude io.Reader, crc hash.Hash32) (preludeInfo, error) {
+ var err error
+ var pInfo = preludeInfo{}
+
+ // reads total length of the message (first 4 bytes)
+ pInfo.totalLen, err = extractUint32(prelude)
+ if err != nil {
+ return pInfo, err
+ }
+
+ // reads total header length of the message (2nd 4 bytes)
+ pInfo.headerLen, err = extractUint32(prelude)
+ if err != nil {
+ return pInfo, err
+ }
+
+ // checks that the CRC is correct (3rd 4 bytes)
+ preCRC := crc.Sum32()
+ if err := checkCRC(prelude, preCRC); err != nil {
+ return pInfo, err
+ }
+
+ return pInfo, nil
+}
+
+// extracts the relevant information from the Headers.
+func extractHeader(body io.Reader, myHeaders http.Header) error {
+ for {
+ // extracts the first part of the header,
+ headerTypeName, err := extractHeaderType(body)
+ if err != nil {
+ // Since end of file, we have read all of our headers
+ if err == io.EOF {
+ break
+ }
+ return err
+ }
+
+ // reads the 7 present in the header and ignores it.
+ extractUint8(body)
+
+ headerValueName, err := extractHeaderValue(body)
+ if err != nil {
+ return err
+ }
+
+ myHeaders.Set(headerTypeName, headerValueName)
+
+ }
+ return nil
+}
+
+// extractHeaderType extracts the first half of the header message, the header type.
+func extractHeaderType(body io.Reader) (string, error) {
+ // extracts 2 bit integer
+ headerNameLen, err := extractUint8(body)
+ if err != nil {
+ return "", err
+ }
+ // extracts the string with the appropriate number of bytes
+ headerName, err := extractString(body, int(headerNameLen))
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimPrefix(headerName, ":"), nil
+}
+
+// extractsHeaderValue extracts the second half of the header message, the
+// header value
+func extractHeaderValue(body io.Reader) (string, error) {
+ bodyLen, err := extractUint16(body)
+ if err != nil {
+ return "", err
+ }
+ bodyName, err := extractString(body, int(bodyLen))
+ if err != nil {
+ return "", err
+ }
+ return bodyName, nil
+}
+
+// extracts a string from byte array of a particular number of bytes.
+func extractString(source io.Reader, lenBytes int) (string, error) {
+ myVal := make([]byte, lenBytes)
+ _, err := source.Read(myVal)
+ if err != nil {
+ return "", err
+ }
+ return string(myVal), nil
+}
+
+// extractUint32 extracts a 4 byte integer from the byte array.
+func extractUint32(r io.Reader) (uint32, error) {
+ buf := make([]byte, 4)
+ _, err := readFull(r, buf)
+ if err != nil {
+ return 0, err
+ }
+ return binary.BigEndian.Uint32(buf), nil
+}
+
+// extractUint16 extracts a 2 byte integer from the byte array.
+func extractUint16(r io.Reader) (uint16, error) {
+ buf := make([]byte, 2)
+ _, err := readFull(r, buf)
+ if err != nil {
+ return 0, err
+ }
+ return binary.BigEndian.Uint16(buf), nil
+}
+
+// extractUint8 extracts a 1 byte integer from the byte array.
+func extractUint8(r io.Reader) (uint8, error) {
+ buf := make([]byte, 1)
+ _, err := readFull(r, buf)
+ if err != nil {
+ return 0, err
+ }
+ return buf[0], nil
+}
+
+// checkCRC ensures that the CRC matches with the one from the reader.
+func checkCRC(r io.Reader, expect uint32) error {
+ msgCRC, err := extractUint32(r)
+ if err != nil {
+ return err
+ }
+
+ if msgCRC != expect {
+ return fmt.Errorf("Checksum Mismatch, MessageCRC of 0x%X does not equal expected CRC of 0x%X", msgCRC, expect)
+
+ }
+ return nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api-stat.go b/vendor/github.com/minio/minio-go/v7/api-stat.go
new file mode 100644
index 00000000..aa81cc43
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-stat.go
@@ -0,0 +1,127 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// BucketExists verifies if bucket exists and you have permission to access it. Allows for a Context to
+// control cancellations and timeouts.
+func (c Client) BucketExists(ctx context.Context, bucketName string) (bool, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return false, err
+ }
+
+ // Execute HEAD on bucketName.
+ resp, err := c.executeMethod(ctx, http.MethodHead, requestMetadata{
+ bucketName: bucketName,
+ contentSHA256Hex: emptySHA256Hex,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ if ToErrorResponse(err).Code == "NoSuchBucket" {
+ return false, nil
+ }
+ return false, err
+ }
+ if resp != nil {
+ resperr := httpRespToErrorResponse(resp, bucketName, "")
+ if ToErrorResponse(resperr).Code == "NoSuchBucket" {
+ return false, nil
+ }
+ if resp.StatusCode != http.StatusOK {
+ return false, httpRespToErrorResponse(resp, bucketName, "")
+ }
+ }
+ return true, nil
+}
+
+// StatObject verifies if object exists and you have permission to access.
+func (c Client) StatObject(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return ObjectInfo{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return ObjectInfo{}, err
+ }
+ return c.statObject(ctx, bucketName, objectName, opts)
+}
+
+// Lower level API for statObject supporting pre-conditions and range headers.
+func (c Client) statObject(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
+ // Input validation.
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return ObjectInfo{}, err
+ }
+ if err := s3utils.CheckValidObjectName(objectName); err != nil {
+ return ObjectInfo{}, err
+ }
+ headers := opts.Header()
+ if opts.Internal.ReplicationDeleteMarker {
+ headers.Set(minIOBucketReplicationDeleteMarker, "true")
+ }
+
+ urlValues := make(url.Values)
+ if opts.VersionID != "" {
+ urlValues.Set("versionId", opts.VersionID)
+ }
+ // Execute HEAD on objectName.
+ resp, err := c.executeMethod(ctx, http.MethodHead, requestMetadata{
+ bucketName: bucketName,
+ objectName: objectName,
+ queryValues: urlValues,
+ contentSHA256Hex: emptySHA256Hex,
+ customHeader: headers,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return ObjectInfo{}, err
+ }
+ deleteMarker := resp.Header.Get(amzDeleteMarker) == "true"
+
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
+ if resp.StatusCode == http.StatusBadRequest && opts.VersionID != "" && deleteMarker {
+ errResp := ErrorResponse{
+ StatusCode: resp.StatusCode,
+ Code: "MethodNotAllowed",
+ Message: "The specified method is not allowed against this resource.",
+ BucketName: bucketName,
+ Key: objectName,
+ }
+ return ObjectInfo{
+ VersionID: resp.Header.Get(amzVersionID),
+ IsDeleteMarker: deleteMarker,
+ }, errResp
+ }
+ return ObjectInfo{
+ VersionID: resp.Header.Get(amzVersionID),
+ IsDeleteMarker: deleteMarker,
+ }, httpRespToErrorResponse(resp, bucketName, objectName)
+ }
+ }
+
+ return ToObjectInfo(bucketName, objectName, resp.Header)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/api.go b/vendor/github.com/minio/minio-go/v7/api.go
new file mode 100644
index 00000000..44660ab6
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api.go
@@ -0,0 +1,896 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2018 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math/rand"
+ "net"
+ "net/http"
+ "net/http/cookiejar"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ md5simd "github.com/minio/md5-simd"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/signer"
+ "golang.org/x/net/publicsuffix"
+)
+
+// Client implements Amazon S3 compatible methods.
+type Client struct {
+ /// Standard options.
+
+ // Parsed endpoint url provided by the user.
+ endpointURL *url.URL
+
+ // Holds various credential providers.
+ credsProvider *credentials.Credentials
+
+ // Custom signerType value overrides all credentials.
+ overrideSignerType credentials.SignatureType
+
+ // User supplied.
+ appInfo struct {
+ appName string
+ appVersion string
+ }
+
+ // Indicate whether we are using https or not
+ secure bool
+
+ // Needs allocation.
+ httpClient *http.Client
+ bucketLocCache *bucketLocationCache
+
+ // Advanced functionality.
+ isTraceEnabled bool
+ traceErrorsOnly bool
+ traceOutput io.Writer
+
+ // S3 specific accelerated endpoint.
+ s3AccelerateEndpoint string
+
+ // Region endpoint
+ region string
+
+ // Random seed.
+ random *rand.Rand
+
+ // lookup indicates type of url lookup supported by server. If not specified,
+ // default to Auto.
+ lookup BucketLookupType
+
+ // Factory for MD5 hash functions.
+ md5Hasher func() md5simd.Hasher
+ sha256Hasher func() md5simd.Hasher
+}
+
+// Options for New method
+type Options struct {
+ Creds *credentials.Credentials
+ Secure bool
+ Transport http.RoundTripper
+ Region string
+ BucketLookup BucketLookupType
+
+ // Custom hash routines. Leave nil to use standard.
+ CustomMD5 func() md5simd.Hasher
+ CustomSHA256 func() md5simd.Hasher
+}
+
+// Global constants.
+const (
+ libraryName = "minio-go"
+ libraryVersion = "v7.0.11"
+)
+
+// User Agent should always following the below style.
+// Please open an issue to discuss any new changes here.
+//
+// MinIO (OS; ARCH) LIB/VER APP/VER
+const (
+ libraryUserAgentPrefix = "MinIO (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
+ libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion
+)
+
+// BucketLookupType is type of url lookup supported by server.
+type BucketLookupType int
+
+// Different types of url lookup supported by the server.Initialized to BucketLookupAuto
+const (
+ BucketLookupAuto BucketLookupType = iota
+ BucketLookupDNS
+ BucketLookupPath
+)
+
+// New - instantiate minio client with options
+func New(endpoint string, opts *Options) (*Client, error) {
+ if opts == nil {
+ return nil, errors.New("no options provided")
+ }
+ clnt, err := privateNew(endpoint, opts)
+ if err != nil {
+ return nil, err
+ }
+ // Google cloud storage should be set to signature V2, force it if not.
+ if s3utils.IsGoogleEndpoint(*clnt.endpointURL) {
+ clnt.overrideSignerType = credentials.SignatureV2
+ }
+ // If Amazon S3 set to signature v4.
+ if s3utils.IsAmazonEndpoint(*clnt.endpointURL) {
+ clnt.overrideSignerType = credentials.SignatureV4
+ }
+
+ return clnt, nil
+}
+
+// EndpointURL returns the URL of the S3 endpoint.
+func (c *Client) EndpointURL() *url.URL {
+ endpoint := *c.endpointURL // copy to prevent callers from modifying internal state
+ return &endpoint
+}
+
+// lockedRandSource provides protected rand source, implements rand.Source interface.
+type lockedRandSource struct {
+ lk sync.Mutex
+ src rand.Source
+}
+
+// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
+func (r *lockedRandSource) Int63() (n int64) {
+ r.lk.Lock()
+ n = r.src.Int63()
+ r.lk.Unlock()
+ return
+}
+
+// Seed uses the provided seed value to initialize the generator to a
+// deterministic state.
+func (r *lockedRandSource) Seed(seed int64) {
+ r.lk.Lock()
+ r.src.Seed(seed)
+ r.lk.Unlock()
+}
+
+// Redirect requests by re signing the request.
+func (c *Client) redirectHeaders(req *http.Request, via []*http.Request) error {
+ if len(via) >= 5 {
+ return errors.New("stopped after 5 redirects")
+ }
+ if len(via) == 0 {
+ return nil
+ }
+ lastRequest := via[len(via)-1]
+ var reAuth bool
+ for attr, val := range lastRequest.Header {
+ // if hosts do not match do not copy Authorization header
+ if attr == "Authorization" && req.Host != lastRequest.Host {
+ reAuth = true
+ continue
+ }
+ if _, ok := req.Header[attr]; !ok {
+ req.Header[attr] = val
+ }
+ }
+
+ *c.endpointURL = *req.URL
+
+ value, err := c.credsProvider.Get()
+ if err != nil {
+ return err
+ }
+ var (
+ signerType = value.SignerType
+ accessKeyID = value.AccessKeyID
+ secretAccessKey = value.SecretAccessKey
+ sessionToken = value.SessionToken
+ region = c.region
+ )
+
+ // Custom signer set then override the behavior.
+ if c.overrideSignerType != credentials.SignatureDefault {
+ signerType = c.overrideSignerType
+ }
+
+ // If signerType returned by credentials helper is anonymous,
+ // then do not sign regardless of signerType override.
+ if value.SignerType == credentials.SignatureAnonymous {
+ signerType = credentials.SignatureAnonymous
+ }
+
+ if reAuth {
+ // Check if there is no region override, if not get it from the URL if possible.
+ if region == "" {
+ region = s3utils.GetRegionFromURL(*c.endpointURL)
+ }
+ switch {
+ case signerType.IsV2():
+ return errors.New("signature V2 cannot support redirection")
+ case signerType.IsV4():
+ signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, getDefaultLocation(*c.endpointURL, region))
+ }
+ }
+ return nil
+}
+
+func privateNew(endpoint string, opts *Options) (*Client, error) {
+ // construct endpoint.
+ endpointURL, err := getEndpointURL(endpoint, opts.Secure)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize cookies to preserve server sent cookies if any and replay
+ // them upon each request.
+ jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ if err != nil {
+ return nil, err
+ }
+
+ // instantiate new Client.
+ clnt := new(Client)
+
+ // Save the credentials.
+ clnt.credsProvider = opts.Creds
+
+ // Remember whether we are using https or not
+ clnt.secure = opts.Secure
+
+ // Save endpoint URL, user agent for future uses.
+ clnt.endpointURL = endpointURL
+
+ transport := opts.Transport
+ if transport == nil {
+ transport, err = DefaultTransport(opts.Secure)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Instantiate http client and bucket location cache.
+ clnt.httpClient = &http.Client{
+ Jar: jar,
+ Transport: transport,
+ CheckRedirect: clnt.redirectHeaders,
+ }
+
+ // Sets custom region, if region is empty bucket location cache is used automatically.
+ if opts.Region == "" {
+ opts.Region = s3utils.GetRegionFromURL(*clnt.endpointURL)
+ }
+ clnt.region = opts.Region
+
+ // Instantiate bucket location cache.
+ clnt.bucketLocCache = newBucketLocationCache()
+
+ // Introduce a new locked random seed.
+ clnt.random = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())})
+
+ // Add default md5 hasher.
+ clnt.md5Hasher = opts.CustomMD5
+ clnt.sha256Hasher = opts.CustomSHA256
+ if clnt.md5Hasher == nil {
+ clnt.md5Hasher = newMd5Hasher
+ }
+ if clnt.sha256Hasher == nil {
+ clnt.sha256Hasher = newSHA256Hasher
+ }
+ // Sets bucket lookup style, whether server accepts DNS or Path lookup. Default is Auto - determined
+ // by the SDK. When Auto is specified, DNS lookup is used for Amazon/Google cloud endpoints and Path for all other endpoints.
+ clnt.lookup = opts.BucketLookup
+ // Return.
+ return clnt, nil
+}
+
+// SetAppInfo - add application details to user agent.
+func (c *Client) SetAppInfo(appName string, appVersion string) {
+ // if app name and version not set, we do not set a new user agent.
+ if appName != "" && appVersion != "" {
+ c.appInfo.appName = appName
+ c.appInfo.appVersion = appVersion
+ }
+}
+
+// TraceOn - enable HTTP tracing.
+func (c *Client) TraceOn(outputStream io.Writer) {
+ // if outputStream is nil then default to os.Stdout.
+ if outputStream == nil {
+ outputStream = os.Stdout
+ }
+ // Sets a new output stream.
+ c.traceOutput = outputStream
+
+ // Enable tracing.
+ c.isTraceEnabled = true
+}
+
+// TraceErrorsOnlyOn - same as TraceOn, but only errors will be traced.
+func (c *Client) TraceErrorsOnlyOn(outputStream io.Writer) {
+ c.TraceOn(outputStream)
+ c.traceErrorsOnly = true
+}
+
+// TraceErrorsOnlyOff - Turns off the errors only tracing and everything will be traced after this call.
+// If all tracing needs to be turned off, call TraceOff().
+func (c *Client) TraceErrorsOnlyOff() {
+ c.traceErrorsOnly = false
+}
+
+// TraceOff - disable HTTP tracing.
+func (c *Client) TraceOff() {
+ // Disable tracing.
+ c.isTraceEnabled = false
+ c.traceErrorsOnly = false
+}
+
+// SetS3TransferAccelerate - turns s3 accelerated endpoint on or off for all your
+// requests. This feature is only specific to S3 for all other endpoints this
+// function does nothing. To read further details on s3 transfer acceleration
+// please vist -
+// http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
+func (c *Client) SetS3TransferAccelerate(accelerateEndpoint string) {
+ if s3utils.IsAmazonEndpoint(*c.endpointURL) {
+ c.s3AccelerateEndpoint = accelerateEndpoint
+ }
+}
+
+// Hash materials provides relevant initialized hash algo writers
+// based on the expected signature type.
+//
+// - For signature v4 request if the connection is insecure compute only sha256.
+// - For signature v4 request if the connection is secure compute only md5.
+// - For anonymous request compute md5.
+func (c *Client) hashMaterials(isMd5Requested bool) (hashAlgos map[string]md5simd.Hasher, hashSums map[string][]byte) {
+ hashSums = make(map[string][]byte)
+ hashAlgos = make(map[string]md5simd.Hasher)
+ if c.overrideSignerType.IsV4() {
+ if c.secure {
+ hashAlgos["md5"] = c.md5Hasher()
+ } else {
+ hashAlgos["sha256"] = c.sha256Hasher()
+ }
+ } else {
+ if c.overrideSignerType.IsAnonymous() {
+ hashAlgos["md5"] = c.md5Hasher()
+ }
+ }
+ if isMd5Requested {
+ hashAlgos["md5"] = c.md5Hasher()
+ }
+ return hashAlgos, hashSums
+}
+
+// requestMetadata - is container for all the values to make a request.
+type requestMetadata struct {
+ // If set newRequest presigns the URL.
+ presignURL bool
+
+ // User supplied.
+ bucketName string
+ objectName string
+ queryValues url.Values
+ customHeader http.Header
+ expires int64
+
+ // Generated by our internal code.
+ bucketLocation string
+ contentBody io.Reader
+ contentLength int64
+ contentMD5Base64 string // carries base64 encoded md5sum
+ contentSHA256Hex string // carries hex encoded sha256sum
+}
+
+// dumpHTTP - dump HTTP request and response.
+func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error {
+ // Starts http dump.
+ _, err := fmt.Fprintln(c.traceOutput, "---------START-HTTP---------")
+ if err != nil {
+ return err
+ }
+
+ // Filter out Signature field from Authorization header.
+ origAuth := req.Header.Get("Authorization")
+ if origAuth != "" {
+ req.Header.Set("Authorization", redactSignature(origAuth))
+ }
+
+ // Only display request header.
+ reqTrace, err := httputil.DumpRequestOut(req, false)
+ if err != nil {
+ return err
+ }
+
+ // Write request to trace output.
+ _, err = fmt.Fprint(c.traceOutput, string(reqTrace))
+ if err != nil {
+ return err
+ }
+
+ // Only display response header.
+ var respTrace []byte
+
+ // For errors we make sure to dump response body as well.
+ if resp.StatusCode != http.StatusOK &&
+ resp.StatusCode != http.StatusPartialContent &&
+ resp.StatusCode != http.StatusNoContent {
+ respTrace, err = httputil.DumpResponse(resp, true)
+ if err != nil {
+ return err
+ }
+ } else {
+ respTrace, err = httputil.DumpResponse(resp, false)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Write response to trace output.
+ _, err = fmt.Fprint(c.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
+ if err != nil {
+ return err
+ }
+
+ // Ends the http dump.
+ _, err = fmt.Fprintln(c.traceOutput, "---------END-HTTP---------")
+ if err != nil {
+ return err
+ }
+
+ // Returns success.
+ return nil
+}
+
+// do - execute http request.
+func (c Client) do(req *http.Request) (*http.Response, error) {
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ // Handle this specifically for now until future Golang versions fix this issue properly.
+ if urlErr, ok := err.(*url.Error); ok {
+ if strings.Contains(urlErr.Err.Error(), "EOF") {
+ return nil, &url.Error{
+ Op: urlErr.Op,
+ URL: urlErr.URL,
+ Err: errors.New("Connection closed by foreign host " + urlErr.URL + ". Retry again."),
+ }
+ }
+ }
+ return nil, err
+ }
+
+ // Response cannot be non-nil, report error if thats the case.
+ if resp == nil {
+ msg := "Response is empty. " + reportIssue
+ return nil, errInvalidArgument(msg)
+ }
+
+ // If trace is enabled, dump http request and response,
+ // except when the traceErrorsOnly enabled and the response's status code is ok
+ if c.isTraceEnabled && !(c.traceErrorsOnly && resp.StatusCode == http.StatusOK) {
+ err = c.dumpHTTP(req, resp)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return resp, nil
+}
+
+// List of success status.
+var successStatus = []int{
+ http.StatusOK,
+ http.StatusNoContent,
+ http.StatusPartialContent,
+}
+
+// executeMethod - instantiates a given method, and retries the
+// request upon any error up to maxRetries attempts in a binomially
+// delayed manner using a standard back off algorithm.
+func (c Client) executeMethod(ctx context.Context, method string, metadata requestMetadata) (res *http.Response, err error) {
+ var retryable bool // Indicates if request can be retried.
+ var bodySeeker io.Seeker // Extracted seeker from io.Reader.
+ var reqRetry = MaxRetry // Indicates how many times we can retry the request
+
+ if metadata.contentBody != nil {
+ // Check if body is seekable then it is retryable.
+ bodySeeker, retryable = metadata.contentBody.(io.Seeker)
+ switch bodySeeker {
+ case os.Stdin, os.Stdout, os.Stderr:
+ retryable = false
+ }
+ // Retry only when reader is seekable
+ if !retryable {
+ reqRetry = 1
+ }
+
+ // Figure out if the body can be closed - if yes
+ // we will definitely close it upon the function
+ // return.
+ bodyCloser, ok := metadata.contentBody.(io.Closer)
+ if ok {
+ defer bodyCloser.Close()
+ }
+ }
+
+ // Create cancel context to control 'newRetryTimer' go routine.
+ retryCtx, cancel := context.WithCancel(ctx)
+
+ // Indicate to our routine to exit cleanly upon return.
+ defer cancel()
+
+ for range c.newRetryTimer(retryCtx, reqRetry, DefaultRetryUnit, DefaultRetryCap, MaxJitter) {
+ // Retry executes the following function body if request has an
+ // error until maxRetries have been exhausted, retry attempts are
+ // performed after waiting for a given period of time in a
+ // binomial fashion.
+ if retryable {
+ // Seek back to beginning for each attempt.
+ if _, err = bodySeeker.Seek(0, 0); err != nil {
+ // If seek failed, no need to retry.
+ return nil, err
+ }
+ }
+
+ // Instantiate a new request.
+ var req *http.Request
+ req, err = c.newRequest(ctx, method, metadata)
+ if err != nil {
+ errResponse := ToErrorResponse(err)
+ if isS3CodeRetryable(errResponse.Code) {
+ continue // Retry.
+ }
+ return nil, err
+ }
+
+ // Initiate the request.
+ res, err = c.do(req)
+ if err != nil {
+ if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+ return nil, err
+ }
+
+ // Retry the request
+ continue
+ }
+
+ // For any known successful http status, return quickly.
+ for _, httpStatus := range successStatus {
+ if httpStatus == res.StatusCode {
+ return res, nil
+ }
+ }
+
+ // Read the body to be saved later.
+ errBodyBytes, err := ioutil.ReadAll(res.Body)
+ // res.Body should be closed
+ closeResponse(res)
+ if err != nil {
+ return nil, err
+ }
+
+ // Save the body.
+ errBodySeeker := bytes.NewReader(errBodyBytes)
+ res.Body = ioutil.NopCloser(errBodySeeker)
+
+ // For errors verify if its retryable otherwise fail quickly.
+ errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName))
+
+ // Save the body back again.
+ errBodySeeker.Seek(0, 0) // Seek back to starting point.
+ res.Body = ioutil.NopCloser(errBodySeeker)
+
+ // Bucket region if set in error response and the error
+ // code dictates invalid region, we can retry the request
+ // with the new region.
+ //
+ // Additionally we should only retry if bucketLocation and custom
+ // region is empty.
+ if c.region == "" {
+ switch errResponse.Code {
+ case "AuthorizationHeaderMalformed":
+ fallthrough
+ case "InvalidRegion":
+ fallthrough
+ case "AccessDenied":
+ if errResponse.Region == "" {
+ // Region is empty we simply return the error.
+ return res, err
+ }
+ // Region is not empty figure out a way to
+ // handle this appropriately.
+ if metadata.bucketName != "" {
+ // Gather Cached location only if bucketName is present.
+ if location, cachedOk := c.bucketLocCache.Get(metadata.bucketName); cachedOk && location != errResponse.Region {
+ c.bucketLocCache.Set(metadata.bucketName, errResponse.Region)
+ continue // Retry.
+ }
+ } else {
+ // This is for ListBuckets() fallback.
+ if errResponse.Region != metadata.bucketLocation {
+ // Retry if the error response has a different region
+ // than the request we just made.
+ metadata.bucketLocation = errResponse.Region
+ continue // Retry
+ }
+ }
+ }
+ }
+
+ // Verify if error response code is retryable.
+ if isS3CodeRetryable(errResponse.Code) {
+ continue // Retry.
+ }
+
+ // Verify if http status code is retryable.
+ if isHTTPStatusRetryable(res.StatusCode) {
+ continue // Retry.
+ }
+
+ // For all other cases break out of the retry loop.
+ break
+ }
+
+ // Return an error when retry is canceled or deadlined
+ if e := retryCtx.Err(); e != nil {
+ return nil, e
+ }
+
+ return res, err
+}
+
+// newRequest - instantiate a new HTTP request for a given method.
+func (c Client) newRequest(ctx context.Context, method string, metadata requestMetadata) (req *http.Request, err error) {
+ // If no method is supplied default to 'POST'.
+ if method == "" {
+ method = http.MethodPost
+ }
+
+ location := metadata.bucketLocation
+ if location == "" {
+ if metadata.bucketName != "" {
+ // Gather location only if bucketName is present.
+ location, err = c.getBucketLocation(ctx, metadata.bucketName)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if location == "" {
+ location = getDefaultLocation(*c.endpointURL, c.region)
+ }
+ }
+
+ // Look if target url supports virtual host.
+ // We explicitly disallow MakeBucket calls to not use virtual DNS style,
+ // since the resolution may fail.
+ isMakeBucket := (metadata.objectName == "" && method == http.MethodPut && len(metadata.queryValues) == 0)
+ isVirtualHost := c.isVirtualHostStyleRequest(*c.endpointURL, metadata.bucketName) && !isMakeBucket
+
+ // Construct a new target URL.
+ targetURL, err := c.makeTargetURL(metadata.bucketName, metadata.objectName, location,
+ isVirtualHost, metadata.queryValues)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize a new HTTP request for the method.
+ req, err = http.NewRequestWithContext(ctx, method, targetURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get credentials from the configured credentials provider.
+ value, err := c.credsProvider.Get()
+ if err != nil {
+ return nil, err
+ }
+
+ var (
+ signerType = value.SignerType
+ accessKeyID = value.AccessKeyID
+ secretAccessKey = value.SecretAccessKey
+ sessionToken = value.SessionToken
+ )
+
+ // Custom signer set then override the behavior.
+ if c.overrideSignerType != credentials.SignatureDefault {
+ signerType = c.overrideSignerType
+ }
+
+ // If signerType returned by credentials helper is anonymous,
+ // then do not sign regardless of signerType override.
+ if value.SignerType == credentials.SignatureAnonymous {
+ signerType = credentials.SignatureAnonymous
+ }
+
+ // Generate presign url if needed, return right here.
+ if metadata.expires != 0 && metadata.presignURL {
+ if signerType.IsAnonymous() {
+ return nil, errInvalidArgument("Presigned URLs cannot be generated with anonymous credentials.")
+ }
+ if signerType.IsV2() {
+ // Presign URL with signature v2.
+ req = signer.PreSignV2(*req, accessKeyID, secretAccessKey, metadata.expires, isVirtualHost)
+ } else if signerType.IsV4() {
+ // Presign URL with signature v4.
+ req = signer.PreSignV4(*req, accessKeyID, secretAccessKey, sessionToken, location, metadata.expires)
+ }
+ return req, nil
+ }
+
+ // Set 'User-Agent' header for the request.
+ c.setUserAgent(req)
+
+ // Set all headers.
+ for k, v := range metadata.customHeader {
+ req.Header.Set(k, v[0])
+ }
+
+ // Go net/http notoriously closes the request body.
+ // - The request Body, if non-nil, will be closed by the underlying Transport, even on errors.
+ // This can cause underlying *os.File seekers to fail, avoid that
+ // by making sure to wrap the closer as a nop.
+ if metadata.contentLength == 0 {
+ req.Body = nil
+ } else {
+ req.Body = ioutil.NopCloser(metadata.contentBody)
+ }
+
+ // Set incoming content-length.
+ req.ContentLength = metadata.contentLength
+ if req.ContentLength <= -1 {
+ // For unknown content length, we upload using transfer-encoding: chunked.
+ req.TransferEncoding = []string{"chunked"}
+ }
+
+ // set md5Sum for content protection.
+ if len(metadata.contentMD5Base64) > 0 {
+ req.Header.Set("Content-Md5", metadata.contentMD5Base64)
+ }
+
+ // For anonymous requests just return.
+ if signerType.IsAnonymous() {
+ return req, nil
+ }
+
+ switch {
+ case signerType.IsV2():
+ // Add signature version '2' authorization header.
+ req = signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualHost)
+ case metadata.objectName != "" && metadata.queryValues == nil && method == http.MethodPut && metadata.customHeader.Get("X-Amz-Copy-Source") == "" && !c.secure:
+ // Streaming signature is used by default for a PUT object request. Additionally we also
+ // look if the initialized client is secure, if yes then we don't need to perform
+ // streaming signature.
+ req = signer.StreamingSignV4(req, accessKeyID,
+ secretAccessKey, sessionToken, location, metadata.contentLength, time.Now().UTC())
+ default:
+ // Set sha256 sum for signature calculation only with signature version '4'.
+ shaHeader := unsignedPayload
+ if metadata.contentSHA256Hex != "" {
+ shaHeader = metadata.contentSHA256Hex
+ }
+ req.Header.Set("X-Amz-Content-Sha256", shaHeader)
+
+ // Add signature version '4' authorization header.
+ req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location)
+ }
+
+ // Return request.
+ return req, nil
+}
+
+// set User agent.
+func (c Client) setUserAgent(req *http.Request) {
+ req.Header.Set("User-Agent", libraryUserAgent)
+ if c.appInfo.appName != "" && c.appInfo.appVersion != "" {
+ req.Header.Set("User-Agent", libraryUserAgent+" "+c.appInfo.appName+"/"+c.appInfo.appVersion)
+ }
+}
+
+// makeTargetURL make a new target url.
+func (c Client) makeTargetURL(bucketName, objectName, bucketLocation string, isVirtualHostStyle bool, queryValues url.Values) (*url.URL, error) {
+ host := c.endpointURL.Host
+ // For Amazon S3 endpoint, try to fetch location based endpoint.
+ if s3utils.IsAmazonEndpoint(*c.endpointURL) {
+ if c.s3AccelerateEndpoint != "" && bucketName != "" {
+ // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
+ // Disable transfer acceleration for non-compliant bucket names.
+ if strings.Contains(bucketName, ".") {
+ return nil, errTransferAccelerationBucket(bucketName)
+ }
+ // If transfer acceleration is requested set new host.
+ // For more details about enabling transfer acceleration read here.
+ // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
+ host = c.s3AccelerateEndpoint
+ } else {
+ // Do not change the host if the endpoint URL is a FIPS S3 endpoint.
+ if !s3utils.IsAmazonFIPSEndpoint(*c.endpointURL) {
+ // Fetch new host based on the bucket location.
+ host = getS3Endpoint(bucketLocation)
+ }
+ }
+ }
+
+ // Save scheme.
+ scheme := c.endpointURL.Scheme
+
+ // Strip port 80 and 443 so we won't send these ports in Host header.
+ // The reason is that browsers and curl automatically remove :80 and :443
+ // with the generated presigned urls, then a signature mismatch error.
+ if h, p, err := net.SplitHostPort(host); err == nil {
+ if scheme == "http" && p == "80" || scheme == "https" && p == "443" {
+ host = h
+ }
+ }
+
+ urlStr := scheme + "://" + host + "/"
+ // Make URL only if bucketName is available, otherwise use the
+ // endpoint URL.
+ if bucketName != "" {
+ // If endpoint supports virtual host style use that always.
+ // Currently only S3 and Google Cloud Storage would support
+ // virtual host style.
+ if isVirtualHostStyle {
+ urlStr = scheme + "://" + bucketName + "." + host + "/"
+ if objectName != "" {
+ urlStr = urlStr + s3utils.EncodePath(objectName)
+ }
+ } else {
+ // If not fall back to using path style.
+ urlStr = urlStr + bucketName + "/"
+ if objectName != "" {
+ urlStr = urlStr + s3utils.EncodePath(objectName)
+ }
+ }
+ }
+
+ // If there are any query values, add them to the end.
+ if len(queryValues) > 0 {
+ urlStr = urlStr + "?" + s3utils.QueryEncode(queryValues)
+ }
+
+ return url.Parse(urlStr)
+}
+
+// returns true if virtual hosted style requests are to be used.
+func (c *Client) isVirtualHostStyleRequest(url url.URL, bucketName string) bool {
+ if bucketName == "" {
+ return false
+ }
+
+ if c.lookup == BucketLookupDNS {
+ return true
+ }
+ if c.lookup == BucketLookupPath {
+ return false
+ }
+
+ // default to virtual only for Amazon/Google storage. In all other cases use
+ // path style requests
+ return s3utils.IsVirtualHostSupported(url, bucketName)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/bucket-cache.go b/vendor/github.com/minio/minio-go/v7/bucket-cache.go
new file mode 100644
index 00000000..156150f6
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/bucket-cache.go
@@ -0,0 +1,253 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "net"
+ "net/http"
+ "net/url"
+ "path"
+ "sync"
+
+ "github.com/minio/minio-go/v7/pkg/credentials"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/signer"
+)
+
+// bucketLocationCache - Provides simple mechanism to hold bucket
+// locations in memory.
+type bucketLocationCache struct {
+ // mutex is used for handling the concurrent
+ // read/write requests for cache.
+ sync.RWMutex
+
+ // items holds the cached bucket locations.
+ items map[string]string
+}
+
+// newBucketLocationCache - Provides a new bucket location cache to be
+// used internally with the client object.
+func newBucketLocationCache() *bucketLocationCache {
+ return &bucketLocationCache{
+ items: make(map[string]string),
+ }
+}
+
+// Get - Returns a value of a given key if it exists.
+func (r *bucketLocationCache) Get(bucketName string) (location string, ok bool) {
+ r.RLock()
+ defer r.RUnlock()
+ location, ok = r.items[bucketName]
+ return
+}
+
+// Set - Will persist a value into cache.
+func (r *bucketLocationCache) Set(bucketName string, location string) {
+ r.Lock()
+ defer r.Unlock()
+ r.items[bucketName] = location
+}
+
+// Delete - Deletes a bucket name from cache.
+func (r *bucketLocationCache) Delete(bucketName string) {
+ r.Lock()
+ defer r.Unlock()
+ delete(r.items, bucketName)
+}
+
+// GetBucketLocation - get location for the bucket name from location cache, if not
+// fetch freshly by making a new request.
+func (c Client) GetBucketLocation(ctx context.Context, bucketName string) (string, error) {
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return "", err
+ }
+ return c.getBucketLocation(ctx, bucketName)
+}
+
+// getBucketLocation - Get location for the bucketName from location map cache, if not
+// fetch freshly by making a new request.
+func (c Client) getBucketLocation(ctx context.Context, bucketName string) (string, error) {
+ if err := s3utils.CheckValidBucketName(bucketName); err != nil {
+ return "", err
+ }
+
+ // Region set then no need to fetch bucket location.
+ if c.region != "" {
+ return c.region, nil
+ }
+
+ if location, ok := c.bucketLocCache.Get(bucketName); ok {
+ return location, nil
+ }
+
+ // Initialize a new request.
+ req, err := c.getBucketLocationRequest(ctx, bucketName)
+ if err != nil {
+ return "", err
+ }
+
+ // Initiate the request.
+ resp, err := c.do(req)
+ defer closeResponse(resp)
+ if err != nil {
+ return "", err
+ }
+ location, err := processBucketLocationResponse(resp, bucketName)
+ if err != nil {
+ return "", err
+ }
+ c.bucketLocCache.Set(bucketName, location)
+ return location, nil
+}
+
+// processes the getBucketLocation http response from the server.
+func processBucketLocationResponse(resp *http.Response, bucketName string) (bucketLocation string, err error) {
+ if resp != nil {
+ if resp.StatusCode != http.StatusOK {
+ err = httpRespToErrorResponse(resp, bucketName, "")
+ errResp := ToErrorResponse(err)
+ // For access denied error, it could be an anonymous
+ // request. Move forward and let the top level callers
+ // succeed if possible based on their policy.
+ switch errResp.Code {
+ case "NotImplemented":
+ if errResp.Server == "AmazonSnowball" {
+ return "snowball", nil
+ }
+ case "AuthorizationHeaderMalformed":
+ fallthrough
+ case "InvalidRegion":
+ fallthrough
+ case "AccessDenied":
+ if errResp.Region == "" {
+ return "us-east-1", nil
+ }
+ return errResp.Region, nil
+ }
+ return "", err
+ }
+ }
+
+ // Extract location.
+ var locationConstraint string
+ err = xmlDecoder(resp.Body, &locationConstraint)
+ if err != nil {
+ return "", err
+ }
+
+ location := locationConstraint
+ // Location is empty will be 'us-east-1'.
+ if location == "" {
+ location = "us-east-1"
+ }
+
+ // Location can be 'EU' convert it to meaningful 'eu-west-1'.
+ if location == "EU" {
+ location = "eu-west-1"
+ }
+
+ // Save the location into cache.
+
+ // Return.
+ return location, nil
+}
+
+// getBucketLocationRequest - Wrapper creates a new getBucketLocation request.
+func (c Client) getBucketLocationRequest(ctx context.Context, bucketName string) (*http.Request, error) {
+ // Set location query.
+ urlValues := make(url.Values)
+ urlValues.Set("location", "")
+
+ // Set get bucket location always as path style.
+ targetURL := *c.endpointURL
+
+ // as it works in makeTargetURL method from api.go file
+ if h, p, err := net.SplitHostPort(targetURL.Host); err == nil {
+ if targetURL.Scheme == "http" && p == "80" || targetURL.Scheme == "https" && p == "443" {
+ targetURL.Host = h
+ }
+ }
+
+ isVirtualHost := s3utils.IsVirtualHostSupported(targetURL, bucketName)
+
+ var urlStr string
+
+ //only support Aliyun OSS for virtual hosted path, compatible Amazon & Google Endpoint
+ if isVirtualHost && s3utils.IsAliyunOSSEndpoint(targetURL) {
+ urlStr = c.endpointURL.Scheme + "://" + bucketName + "." + targetURL.Host + "/?location"
+ } else {
+ targetURL.Path = path.Join(bucketName, "") + "/"
+ targetURL.RawQuery = urlValues.Encode()
+ urlStr = targetURL.String()
+ }
+
+ // Get a new HTTP request for the method.
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set UserAgent for the request.
+ c.setUserAgent(req)
+
+ // Get credentials from the configured credentials provider.
+ value, err := c.credsProvider.Get()
+ if err != nil {
+ return nil, err
+ }
+
+ var (
+ signerType = value.SignerType
+ accessKeyID = value.AccessKeyID
+ secretAccessKey = value.SecretAccessKey
+ sessionToken = value.SessionToken
+ )
+
+ // Custom signer set then override the behavior.
+ if c.overrideSignerType != credentials.SignatureDefault {
+ signerType = c.overrideSignerType
+ }
+
+ // If signerType returned by credentials helper is anonymous,
+ // then do not sign regardless of signerType override.
+ if value.SignerType == credentials.SignatureAnonymous {
+ signerType = credentials.SignatureAnonymous
+ }
+
+ if signerType.IsAnonymous() {
+ return req, nil
+ }
+
+ if signerType.IsV2() {
+ // Get Bucket Location calls should be always path style
+ isVirtualHost := false
+ req = signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualHost)
+ return req, nil
+ }
+
+ // Set sha256 sum for signature calculation only with signature version '4'.
+ contentSha256 := emptySHA256Hex
+ if c.secure {
+ contentSha256 = unsignedPayload
+ }
+
+ req.Header.Set("X-Amz-Content-Sha256", contentSha256)
+ req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1")
+ return req, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/code_of_conduct.md b/vendor/github.com/minio/minio-go/v7/code_of_conduct.md
new file mode 100644
index 00000000..cb232c3c
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/code_of_conduct.md
@@ -0,0 +1,80 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior, in compliance with the
+licensing terms applying to the Project developments.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful. However, these actions shall respect the
+licensing terms of the Project Developments that will always supersede such
+Code of Conduct.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at dev@min.io. The project team
+will review and investigate all complaints, and will respond in a way that it deems
+appropriate to the circumstances. The project team is obligated to maintain
+confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+This version includes a clarification to ensure that the code of conduct is in
+compliance with the free software licensing terms of the project.
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/vendor/github.com/minio/minio-go/v7/constants.go b/vendor/github.com/minio/minio-go/v7/constants.go
new file mode 100644
index 00000000..2a2e6a0d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/constants.go
@@ -0,0 +1,92 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+/// Multipart upload defaults.
+
+// absMinPartSize - absolute minimum part size (5 MiB) below which
+// a part in a multipart upload may not be uploaded.
+const absMinPartSize = 1024 * 1024 * 5
+
+// minPartSize - minimum part size 16MiB per object after which
+// putObject behaves internally as multipart.
+const minPartSize = 1024 * 1024 * 16
+
+// maxPartsCount - maximum number of parts for a single multipart session.
+const maxPartsCount = 10000
+
+// maxPartSize - maximum part size 5GiB for a single multipart upload
+// operation.
+const maxPartSize = 1024 * 1024 * 1024 * 5
+
+// maxSinglePutObjectSize - maximum size 5GiB of object per PUT
+// operation.
+const maxSinglePutObjectSize = 1024 * 1024 * 1024 * 5
+
+// maxMultipartPutObjectSize - maximum size 5TiB of object for
+// Multipart operation.
+const maxMultipartPutObjectSize = 1024 * 1024 * 1024 * 1024 * 5
+
+// unsignedPayload - value to be set to X-Amz-Content-Sha256 header when
+// we don't want to sign the request payload
+const unsignedPayload = "UNSIGNED-PAYLOAD"
+
+// Total number of parallel workers used for multipart operation.
+const totalWorkers = 4
+
+// Signature related constants.
+const (
+ signV4Algorithm = "AWS4-HMAC-SHA256"
+ iso8601DateFormat = "20060102T150405Z"
+)
+
+const (
+ // Storage class header.
+ amzStorageClass = "X-Amz-Storage-Class"
+
+ // Website redirect location header
+ amzWebsiteRedirectLocation = "X-Amz-Website-Redirect-Location"
+
+ // Object Tagging headers
+ amzTaggingHeader = "X-Amz-Tagging"
+ amzTaggingHeaderDirective = "X-Amz-Tagging-Directive"
+
+ amzVersionID = "X-Amz-Version-Id"
+ amzTaggingCount = "X-Amz-Tagging-Count"
+ amzExpiration = "X-Amz-Expiration"
+ amzReplicationStatus = "X-Amz-Replication-Status"
+ amzDeleteMarker = "X-Amz-Delete-Marker"
+
+ // Object legal hold header
+ amzLegalHoldHeader = "X-Amz-Object-Lock-Legal-Hold"
+
+ // Object retention header
+ amzLockMode = "X-Amz-Object-Lock-Mode"
+ amzLockRetainUntil = "X-Amz-Object-Lock-Retain-Until-Date"
+ amzBypassGovernance = "X-Amz-Bypass-Governance-Retention"
+
+ // Replication status
+ amzBucketReplicationStatus = "X-Amz-Replication-Status"
+ // Minio specific Replication/lifecycle transition extension
+ minIOBucketSourceMTime = "X-Minio-Source-Mtime"
+
+ minIOBucketSourceETag = "X-Minio-Source-Etag"
+ minIOBucketReplicationDeleteMarker = "X-Minio-Source-DeleteMarker"
+ minIOBucketReplicationProxyRequest = "X-Minio-Source-Proxy-Request"
+ minIOBucketReplicationRequest = "X-Minio-Source-Replication-Request"
+)
diff --git a/vendor/github.com/minio/minio-go/v7/core.go b/vendor/github.com/minio/minio-go/v7/core.go
new file mode 100644
index 00000000..2bf4edf0
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/core.go
@@ -0,0 +1,133 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "io"
+ "net/http"
+
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+)
+
+// Core - Inherits Client and adds new methods to expose the low level S3 APIs.
+type Core struct {
+ *Client
+}
+
+// NewCore - Returns new initialized a Core client, this CoreClient should be
+// only used under special conditions such as need to access lower primitives
+// and being able to use them to write your own wrappers.
+func NewCore(endpoint string, opts *Options) (*Core, error) {
+ var s3Client Core
+ client, err := New(endpoint, opts)
+ if err != nil {
+ return nil, err
+ }
+ s3Client.Client = client
+ return &s3Client, nil
+}
+
+// ListObjects - List all the objects at a prefix, optionally with marker and delimiter
+// you can further filter the results.
+func (c Core) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListBucketResult, err error) {
+ return c.listObjectsQuery(context.Background(), bucket, prefix, marker, delimiter, maxKeys)
+}
+
+// ListObjectsV2 - Lists all the objects at a prefix, similar to ListObjects() but uses
+// continuationToken instead of marker to support iteration over the results.
+func (c Core) ListObjectsV2(bucketName, objectPrefix, continuationToken string, fetchOwner bool, delimiter string, maxkeys int) (ListBucketV2Result, error) {
+ return c.listObjectsV2Query(context.Background(), bucketName, objectPrefix, continuationToken, fetchOwner, false, delimiter, maxkeys)
+}
+
+// CopyObject - copies an object from source object to destination object on server side.
+func (c Core) CopyObject(ctx context.Context, sourceBucket, sourceObject, destBucket, destObject string, metadata map[string]string, srcOpts CopySrcOptions, dstOpts PutObjectOptions) (ObjectInfo, error) {
+ return c.copyObjectDo(ctx, sourceBucket, sourceObject, destBucket, destObject, metadata, srcOpts, dstOpts)
+}
+
+// CopyObjectPart - creates a part in a multipart upload by copying (a
+// part of) an existing object.
+func (c Core) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string,
+ partID int, startOffset, length int64, metadata map[string]string) (p CompletePart, err error) {
+
+ return c.copyObjectPartDo(ctx, srcBucket, srcObject, destBucket, destObject, uploadID,
+ partID, startOffset, length, metadata)
+}
+
+// PutObject - Upload object. Uploads using single PUT call.
+func (c Core) PutObject(ctx context.Context, bucket, object string, data io.Reader, size int64, md5Base64, sha256Hex string, opts PutObjectOptions) (UploadInfo, error) {
+ hookReader := newHook(data, opts.Progress)
+ return c.putObjectDo(ctx, bucket, object, hookReader, md5Base64, sha256Hex, size, opts)
+}
+
+// NewMultipartUpload - Initiates new multipart upload and returns the new uploadID.
+func (c Core) NewMultipartUpload(ctx context.Context, bucket, object string, opts PutObjectOptions) (uploadID string, err error) {
+ result, err := c.initiateMultipartUpload(ctx, bucket, object, opts)
+ return result.UploadID, err
+}
+
+// ListMultipartUploads - List incomplete uploads.
+func (c Core) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartUploadsResult, err error) {
+ return c.listMultipartUploadsQuery(ctx, bucket, keyMarker, uploadIDMarker, prefix, delimiter, maxUploads)
+}
+
+// PutObjectPart - Upload an object part.
+func (c Core) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data io.Reader, size int64, md5Base64, sha256Hex string, sse encrypt.ServerSide) (ObjectPart, error) {
+ return c.uploadPart(ctx, bucket, object, uploadID, data, partID, md5Base64, sha256Hex, size, sse)
+}
+
+// ListObjectParts - List uploaded parts of an incomplete upload.x
+func (c Core) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListObjectPartsResult, err error) {
+ return c.listObjectPartsQuery(ctx, bucket, object, uploadID, partNumberMarker, maxParts)
+}
+
+// CompleteMultipartUpload - Concatenate uploaded parts and commit to an object.
+func (c Core) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, parts []CompletePart) (string, error) {
+ res, err := c.completeMultipartUpload(ctx, bucket, object, uploadID, completeMultipartUpload{
+ Parts: parts,
+ })
+ return res.ETag, err
+}
+
+// AbortMultipartUpload - Abort an incomplete upload.
+func (c Core) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string) error {
+ return c.abortMultipartUpload(ctx, bucket, object, uploadID)
+}
+
+// GetBucketPolicy - fetches bucket access policy for a given bucket.
+func (c Core) GetBucketPolicy(ctx context.Context, bucket string) (string, error) {
+ return c.getBucketPolicy(ctx, bucket)
+}
+
+// PutBucketPolicy - applies a new bucket access policy for a given bucket.
+func (c Core) PutBucketPolicy(ctx context.Context, bucket, bucketPolicy string) error {
+ return c.putBucketPolicy(ctx, bucket, bucketPolicy)
+}
+
+// GetObject is a lower level API implemented to support reading
+// partial objects and also downloading objects with special conditions
+// matching etag, modtime etc.
+func (c Core) GetObject(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, http.Header, error) {
+ return c.getObject(ctx, bucketName, objectName, opts)
+}
+
+// StatObject is a lower level API implemented to support special
+// conditions matching etag, modtime on a request.
+func (c Core) StatObject(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
+ return c.statObject(ctx, bucketName, objectName, opts)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/functional_tests.go b/vendor/github.com/minio/minio-go/v7/functional_tests.go
new file mode 100644
index 00000000..ba7ff577
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/functional_tests.go
@@ -0,0 +1,11502 @@
+// +build mint
+
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "hash/crc32"
+ "io"
+ "io/ioutil"
+ "math/rand"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/dustin/go-humanize"
+ jsoniter "github.com/json-iterator/go"
+ log "github.com/sirupsen/logrus"
+
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+ "github.com/minio/minio-go/v7/pkg/encrypt"
+ "github.com/minio/minio-go/v7/pkg/notification"
+ "github.com/minio/minio-go/v7/pkg/tags"
+)
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyz01234569"
+const (
+ letterIdxBits = 6 // 6 bits to represent a letter index
+ letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
+ letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
+)
+const (
+ serverEndpoint = "SERVER_ENDPOINT"
+ accessKey = "ACCESS_KEY"
+ secretKey = "SECRET_KEY"
+ enableHTTPS = "ENABLE_HTTPS"
+ enableKMS = "ENABLE_KMS"
+)
+
+type mintJSONFormatter struct {
+}
+
+func (f *mintJSONFormatter) Format(entry *log.Entry) ([]byte, error) {
+ data := make(log.Fields, len(entry.Data))
+ for k, v := range entry.Data {
+ switch v := v.(type) {
+ case error:
+ // Otherwise errors are ignored by `encoding/json`
+ // https://github.com/sirupsen/logrus/issues/137
+ data[k] = v.Error()
+ default:
+ data[k] = v
+ }
+ }
+ var json = jsoniter.ConfigCompatibleWithStandardLibrary
+ serialized, err := json.Marshal(data)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ }
+ return append(serialized, '\n'), nil
+}
+
+var readFull = func(r io.Reader, buf []byte) (n int, err error) {
+ // ReadFull reads exactly len(buf) bytes from r into buf.
+ // It returns the number of bytes copied and an error if
+ // fewer bytes were read. The error is EOF only if no bytes
+ // were read. If an EOF happens after reading some but not
+ // all the bytes, ReadFull returns ErrUnexpectedEOF.
+ // On return, n == len(buf) if and only if err == nil.
+ // If r returns an error having read at least len(buf) bytes,
+ // the error is dropped.
+ for n < len(buf) && err == nil {
+ var nn int
+ nn, err = r.Read(buf[n:])
+ // Some spurious io.Reader's return
+ // io.ErrUnexpectedEOF when nn == 0
+ // this behavior is undocumented
+ // so we are on purpose not using io.ReadFull
+ // implementation because this can lead
+ // to custom handling, to avoid that
+ // we simply modify the original io.ReadFull
+ // implementation to avoid this issue.
+ // io.ErrUnexpectedEOF with nn == 0 really
+ // means that io.EOF
+ if err == io.ErrUnexpectedEOF && nn == 0 {
+ err = io.EOF
+ }
+ n += nn
+ }
+ if n >= len(buf) {
+ err = nil
+ } else if n > 0 && err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+func cleanEmptyEntries(fields log.Fields) log.Fields {
+ cleanFields := log.Fields{}
+ for k, v := range fields {
+ if v != "" {
+ cleanFields[k] = v
+ }
+ }
+ return cleanFields
+}
+
+// log successful test runs
+func successLogger(testName string, function string, args map[string]interface{}, startTime time.Time) *log.Entry {
+ // calculate the test case duration
+ duration := time.Since(startTime)
+ // log with the fields as per mint
+ fields := log.Fields{"name": "minio-go: " + testName, "function": function, "args": args, "duration": duration.Nanoseconds() / 1000000, "status": "PASS"}
+ return log.WithFields(cleanEmptyEntries(fields))
+}
+
+// As few of the features are not available in Gateway(s) currently, Check if err value is NotImplemented,
+// and log as NA in that case and continue execution. Otherwise log as failure and return
+func logError(testName string, function string, args map[string]interface{}, startTime time.Time, alert string, message string, err error) {
+ // If server returns NotImplemented we assume it is gateway mode and hence log it as info and move on to next tests
+ // Special case for ComposeObject API as it is implemented on client side and adds specific error details like `Error in upload-part-copy` in
+ // addition to NotImplemented error returned from server
+ if isErrNotImplemented(err) {
+ ignoredLog(testName, function, args, startTime, message).Info()
+ } else {
+ failureLog(testName, function, args, startTime, alert, message, err).Fatal()
+ }
+}
+
+// log failed test runs
+func failureLog(testName string, function string, args map[string]interface{}, startTime time.Time, alert string, message string, err error) *log.Entry {
+ // calculate the test case duration
+ duration := time.Since(startTime)
+ var fields log.Fields
+ // log with the fields as per mint
+ if err != nil {
+ fields = log.Fields{"name": "minio-go: " + testName, "function": function, "args": args,
+ "duration": duration.Nanoseconds() / 1000000, "status": "FAIL", "alert": alert, "message": message, "error": err}
+ } else {
+ fields = log.Fields{"name": "minio-go: " + testName, "function": function, "args": args,
+ "duration": duration.Nanoseconds() / 1000000, "status": "FAIL", "alert": alert, "message": message}
+ }
+ return log.WithFields(cleanEmptyEntries(fields))
+}
+
+// log not applicable test runs
+func ignoredLog(testName string, function string, args map[string]interface{}, startTime time.Time, alert string) *log.Entry {
+ // calculate the test case duration
+ duration := time.Since(startTime)
+ // log with the fields as per mint
+ fields := log.Fields{"name": "minio-go: " + testName, "function": function, "args": args,
+ "duration": duration.Nanoseconds() / 1000000, "status": "NA", "alert": strings.Split(alert, " ")[0] + " is NotImplemented"}
+ return log.WithFields(cleanEmptyEntries(fields))
+}
+
+// Delete objects in given bucket, recursively
+func cleanupBucket(bucketName string, c *minio.Client) error {
+ // Create a done channel to control 'ListObjectsV2' go routine.
+ doneCh := make(chan struct{})
+ // Exit cleanly upon return.
+ defer close(doneCh)
+ // Iterate over all objects in the bucket via listObjectsV2 and delete
+ for objCh := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{Recursive: true}) {
+ if objCh.Err != nil {
+ return objCh.Err
+ }
+ if objCh.Key != "" {
+ err := c.RemoveObject(context.Background(), bucketName, objCh.Key, minio.RemoveObjectOptions{})
+ if err != nil {
+ return err
+ }
+ }
+ }
+ for objPartInfo := range c.ListIncompleteUploads(context.Background(), bucketName, "", true) {
+ if objPartInfo.Err != nil {
+ return objPartInfo.Err
+ }
+ if objPartInfo.Key != "" {
+ err := c.RemoveIncompleteUpload(context.Background(), bucketName, objPartInfo.Key)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // objects are already deleted, clear the buckets now
+ err := c.RemoveBucket(context.Background(), bucketName)
+ if err != nil {
+ return err
+ }
+ return err
+}
+
+func cleanupVersionedBucket(bucketName string, c *minio.Client) error {
+ doneCh := make(chan struct{})
+ defer close(doneCh)
+ for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true}) {
+ if obj.Err != nil {
+ return obj.Err
+ }
+ if obj.Key != "" {
+ err := c.RemoveObject(context.Background(), bucketName, obj.Key,
+ minio.RemoveObjectOptions{VersionID: obj.VersionID, GovernanceBypass: true})
+ if err != nil {
+ return err
+ }
+ }
+ }
+ for objPartInfo := range c.ListIncompleteUploads(context.Background(), bucketName, "", true) {
+ if objPartInfo.Err != nil {
+ return objPartInfo.Err
+ }
+ if objPartInfo.Key != "" {
+ err := c.RemoveIncompleteUpload(context.Background(), bucketName, objPartInfo.Key)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // objects are already deleted, clear the buckets now
+ err := c.RemoveBucket(context.Background(), bucketName)
+ if err != nil {
+ for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true}) {
+ log.Println("found", obj.Key, obj.VersionID)
+ }
+ return err
+ }
+ return err
+}
+
+func isErrNotImplemented(err error) bool {
+ return minio.ToErrorResponse(err).Code == "NotImplemented"
+}
+
+func init() {
+ // If server endpoint is not set, all tests default to
+ // using https://play.min.io
+ if os.Getenv(serverEndpoint) == "" {
+ os.Setenv(serverEndpoint, "play.min.io")
+ os.Setenv(accessKey, "Q3AM3UQ867SPQQA43P2F")
+ os.Setenv(secretKey, "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG")
+ os.Setenv(enableHTTPS, "1")
+ }
+}
+
+var mintDataDir = os.Getenv("MINT_DATA_DIR")
+
+func getMintDataDirFilePath(filename string) (fp string) {
+ if mintDataDir == "" {
+ return
+ }
+ return filepath.Join(mintDataDir, filename)
+}
+
+func newRandomReader(seed, size int64) io.Reader {
+ return io.LimitReader(rand.New(rand.NewSource(seed)), size)
+}
+
+func mustCrcReader(r io.Reader) uint32 {
+ crc := crc32.NewIEEE()
+ _, err := io.Copy(crc, r)
+ if err != nil {
+ panic(err)
+ }
+ return crc.Sum32()
+}
+
+func crcMatches(r io.Reader, want uint32) error {
+ crc := crc32.NewIEEE()
+ _, err := io.Copy(crc, r)
+ if err != nil {
+ panic(err)
+ }
+ got := crc.Sum32()
+ if got != want {
+ return fmt.Errorf("crc mismatch, want %x, got %x", want, got)
+ }
+ return nil
+}
+
+func crcMatchesName(r io.Reader, name string) error {
+ want := dataFileCRC32[name]
+ crc := crc32.NewIEEE()
+ _, err := io.Copy(crc, r)
+ if err != nil {
+ panic(err)
+ }
+ got := crc.Sum32()
+ if got != want {
+ return fmt.Errorf("crc mismatch, want %x, got %x", want, got)
+ }
+ return nil
+}
+
+// read data from file if it exists or optionally create a buffer of particular size
+func getDataReader(fileName string) io.ReadCloser {
+ if mintDataDir == "" {
+ size := int64(dataFileMap[fileName])
+ if _, ok := dataFileCRC32[fileName]; !ok {
+ dataFileCRC32[fileName] = mustCrcReader(newRandomReader(size, size))
+ }
+ return ioutil.NopCloser(newRandomReader(size, size))
+ }
+ reader, _ := os.Open(getMintDataDirFilePath(fileName))
+ if _, ok := dataFileCRC32[fileName]; !ok {
+ dataFileCRC32[fileName] = mustCrcReader(reader)
+ reader.Close()
+ reader, _ = os.Open(getMintDataDirFilePath(fileName))
+ }
+ return reader
+}
+
+// randString generates random names and prepends them with a known prefix.
+func randString(n int, src rand.Source, prefix string) string {
+ b := make([]byte, n)
+ // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
+ for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
+ if remain == 0 {
+ cache, remain = src.Int63(), letterIdxMax
+ }
+ if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+ b[i] = letterBytes[idx]
+ i--
+ }
+ cache >>= letterIdxBits
+ remain--
+ }
+ return prefix + string(b[0:30-len(prefix)])
+}
+
+var dataFileMap = map[string]int{
+ "datafile-0-b": 0,
+ "datafile-1-b": 1,
+ "datafile-1-kB": 1 * humanize.KiByte,
+ "datafile-10-kB": 10 * humanize.KiByte,
+ "datafile-33-kB": 33 * humanize.KiByte,
+ "datafile-100-kB": 100 * humanize.KiByte,
+ "datafile-1.03-MB": 1056 * humanize.KiByte,
+ "datafile-1-MB": 1 * humanize.MiByte,
+ "datafile-5-MB": 5 * humanize.MiByte,
+ "datafile-6-MB": 6 * humanize.MiByte,
+ "datafile-11-MB": 11 * humanize.MiByte,
+ "datafile-65-MB": 65 * humanize.MiByte,
+ "datafile-129-MB": 129 * humanize.MiByte,
+}
+
+var dataFileCRC32 = map[string]uint32{}
+
+func isFullMode() bool {
+ return os.Getenv("MINT_MODE") == "full"
+}
+
+func getFuncName() string {
+ return getFuncNameLoc(2)
+}
+
+func getFuncNameLoc(caller int) string {
+ pc, _, _, _ := runtime.Caller(caller)
+ return strings.TrimPrefix(runtime.FuncForPC(pc).Name(), "main.")
+}
+
+// Tests bucket re-create errors.
+func testMakeBucketError() {
+ region := "eu-central-1"
+
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "MakeBucket(bucketName, region)"
+ // initialize logging params
+ args := map[string]interface{}{
+ "bucketName": "",
+ "region": region,
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket in 'eu-central-1'.
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket Failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, c)
+
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err == nil {
+ logError(testName, function, args, startTime, "", "Bucket already exists", err)
+ return
+ }
+ // Verify valid error response from server.
+ if minio.ToErrorResponse(err).Code != "BucketAlreadyExists" &&
+ minio.ToErrorResponse(err).Code != "BucketAlreadyOwnedByYou" {
+ logError(testName, function, args, startTime, "", "Invalid error returned by server", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testMetadataSizeLimit() {
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader, objectSize, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "opts.UserMetadata": "",
+ }
+ rand.Seed(startTime.Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client creation failed", err)
+ return
+ }
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ const HeaderSizeLimit = 8 * 1024
+ const UserMetadataLimit = 2 * 1024
+
+ // Meta-data greater than the 2 KB limit of AWS - PUT calls with this meta-data should fail
+ metadata := make(map[string]string)
+ metadata["X-Amz-Meta-Mint-Test"] = string(bytes.Repeat([]byte("m"), 1+UserMetadataLimit-len("X-Amz-Meta-Mint-Test")))
+ args["metadata"] = fmt.Sprint(metadata)
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(nil), 0, minio.PutObjectOptions{UserMetadata: metadata})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "Created object with user-defined metadata exceeding metadata size limits", nil)
+ return
+ }
+
+ // Meta-data (headers) greater than the 8 KB limit of AWS - PUT calls with this meta-data should fail
+ metadata = make(map[string]string)
+ metadata["X-Amz-Mint-Test"] = string(bytes.Repeat([]byte("m"), 1+HeaderSizeLimit-len("X-Amz-Mint-Test")))
+ args["metadata"] = fmt.Sprint(metadata)
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(nil), 0, minio.PutObjectOptions{UserMetadata: metadata})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "Created object with headers exceeding header size limits", nil)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests various bucket supported formats.
+func testMakeBucketRegions() {
+ region := "eu-central-1"
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "MakeBucket(bucketName, region)"
+ // initialize logging params
+ args := map[string]interface{}{
+ "bucketName": "",
+ "region": region,
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket in 'eu-central-1'.
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ // Make a new bucket with '.' in its name, in 'us-west-2'. This
+ // request is internally staged into a path style instead of
+ // virtual host style.
+ region = "us-west-2"
+ args["region"] = region
+ if err = c.MakeBucket(context.Background(), bucketName+".withperiod", minio.MakeBucketOptions{Region: region}); err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName+".withperiod", c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test PutObject using a large data to trigger multipart readat
+func testPutObjectReadAt() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "opts": "objectContentType",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Object content type
+ objectContentType := "binary/octet-stream"
+ args["objectContentType"] = objectContentType
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: objectContentType})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Get Object failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat Object failed", err)
+ return
+ }
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Number of bytes in stat does not match, expected %d got %d", bufSize, st.Size), err)
+ return
+ }
+ if st.ContentType != objectContentType && st.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "Content types don't match", err)
+ return
+ }
+ if err := crcMatchesName(r, "datafile-129-MB"); err != nil {
+ logError(testName, function, args, startTime, "", "data CRC check failed", err)
+ return
+ }
+ if err := r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "Object Close failed", err)
+ return
+ }
+ if err := r.Close(); err == nil {
+ logError(testName, function, args, startTime, "", "Object is already closed, didn't return error on Close", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testListObjectVersions() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ListObjectVersions(bucketName, prefix, recursive)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "prefix": "",
+ "recursive": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ bufSize := dataFileMap["datafile-10-kB"]
+ var reader = getDataReader("datafile-10-kB")
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ reader.Close()
+
+ bufSize = dataFileMap["datafile-1-b"]
+ reader = getDataReader("datafile-1-b")
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ reader.Close()
+
+ err = c.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected object deletion", err)
+ return
+ }
+
+ var deleteMarkers, versions int
+
+ objectsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+ for info := range objectsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ if info.Key != objectName {
+ logError(testName, function, args, startTime, "", "Unexpected object name in listing objects", nil)
+ return
+ }
+ if info.VersionID == "" {
+ logError(testName, function, args, startTime, "", "Unexpected version id in listing objects", nil)
+ return
+ }
+ if info.IsDeleteMarker {
+ deleteMarkers++
+ if !info.IsLatest {
+ logError(testName, function, args, startTime, "", "Unexpected IsLatest field in listing objects", nil)
+ return
+ }
+ } else {
+ versions++
+ }
+ }
+
+ if deleteMarkers != 1 {
+ logError(testName, function, args, startTime, "", "Unexpected number of DeleteMarker elements in listing objects", nil)
+ return
+ }
+
+ if versions != 2 {
+ logError(testName, function, args, startTime, "", "Unexpected number of Version elements in listing objects", nil)
+ return
+ }
+
+ // Delete all objects and their versions as long as the bucket itself
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testStatObjectWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "StatObject"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ bufSize := dataFileMap["datafile-10-kB"]
+ var reader = getDataReader("datafile-10-kB")
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ reader.Close()
+
+ bufSize = dataFileMap["datafile-1-b"]
+ reader = getDataReader("datafile-1-b")
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ reader.Close()
+
+ objectsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+
+ var results []minio.ObjectInfo
+ for info := range objectsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ results = append(results, info)
+ }
+
+ if len(results) != 2 {
+ logError(testName, function, args, startTime, "", "Unexpected number of Version elements in listing objects", nil)
+ return
+ }
+
+ for i := 0; i < len(results); i++ {
+ opts := minio.StatObjectOptions{VersionID: results[i].VersionID}
+ statInfo, err := c.StatObject(context.Background(), bucketName, objectName, opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "error during HEAD object", err)
+ return
+ }
+ if statInfo.VersionID == "" || statInfo.VersionID != results[i].VersionID {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected version id", err)
+ return
+ }
+ if statInfo.ETag != results[i].ETag {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected ETag", err)
+ return
+ }
+ if statInfo.LastModified.Unix() != results[i].LastModified.Unix() {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected Last-Modified", err)
+ return
+ }
+ if statInfo.Size != results[i].Size {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected Content-Length", err)
+ return
+ }
+ }
+
+ // Delete all objects and their versions as long as the bucket itself
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testGetObjectWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject()"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Save the contents of datafiles to check with GetObject() reader output later
+ var buffers [][]byte
+ var testFiles = []string{"datafile-1-b", "datafile-10-kB"}
+
+ for _, testFile := range testFiles {
+ r := getDataReader(testFile)
+ buf, err := ioutil.ReadAll(r)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "unexpected failure", err)
+ return
+ }
+ r.Close()
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ buffers = append(buffers, buf)
+ }
+
+ objectsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+
+ var results []minio.ObjectInfo
+ for info := range objectsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ results = append(results, info)
+ }
+
+ if len(results) != 2 {
+ logError(testName, function, args, startTime, "", "Unexpected number of Version elements in listing objects", nil)
+ return
+ }
+
+ sort.SliceStable(results, func(i, j int) bool {
+ return results[i].Size < results[j].Size
+ })
+
+ sort.SliceStable(buffers, func(i, j int) bool {
+ return len(buffers[i]) < len(buffers[j])
+ })
+
+ for i := 0; i < len(results); i++ {
+ opts := minio.GetObjectOptions{VersionID: results[i].VersionID}
+ reader, err := c.GetObject(context.Background(), bucketName, objectName, opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "error during GET object", err)
+ return
+ }
+ statInfo, err := reader.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "error during calling reader.Stat()", err)
+ return
+ }
+ if statInfo.ETag != results[i].ETag {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected ETag", err)
+ return
+ }
+ if statInfo.LastModified.Unix() != results[i].LastModified.Unix() {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected Last-Modified", err)
+ return
+ }
+ if statInfo.Size != results[i].Size {
+ logError(testName, function, args, startTime, "", "error during HEAD object, unexpected Content-Length", err)
+ return
+ }
+
+ tmpBuffer := bytes.NewBuffer([]byte{})
+ _, err = io.Copy(tmpBuffer, reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "unexpected io.Copy()", err)
+ return
+ }
+
+ if !bytes.Equal(tmpBuffer.Bytes(), buffers[i]) {
+ logError(testName, function, args, startTime, "", "unexpected content of GetObject()", err)
+ return
+ }
+ }
+
+ // Delete all objects and their versions as long as the bucket itself
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testCopyObjectWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject()"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ var testFiles = []string{"datafile-1-b", "datafile-10-kB"}
+ for _, testFile := range testFiles {
+ r := getDataReader(testFile)
+ buf, err := ioutil.ReadAll(r)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "unexpected failure", err)
+ return
+ }
+ r.Close()
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ }
+
+ objectsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+ var infos []minio.ObjectInfo
+ for info := range objectsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ infos = append(infos, info)
+ }
+
+ sort.Slice(infos, func(i, j int) bool {
+ return infos[i].Size < infos[j].Size
+ })
+
+ reader, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{VersionID: infos[0].VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject of the oldest version content failed", err)
+ return
+ }
+
+ oldestContent, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Reading the oldest object version failed", err)
+ return
+ }
+
+ // Copy Source
+ srcOpts := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ VersionID: infos[0].VersionID,
+ }
+ args["src"] = srcOpts
+
+ dstOpts := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: objectName + "-copy",
+ }
+ args["dst"] = dstOpts
+
+ // Perform the Copy
+ if _, err = c.CopyObject(context.Background(), dstOpts, srcOpts); err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ // Destination object
+ readerCopy, err := c.GetObject(context.Background(), bucketName, objectName+"-copy", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer readerCopy.Close()
+
+ newestContent, err := ioutil.ReadAll(readerCopy)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Reading from GetObject reader failed", err)
+ return
+ }
+
+ if len(newestContent) == 0 || !bytes.Equal(oldestContent, newestContent) {
+ logError(testName, function, args, startTime, "", "Unexpected destination object content", err)
+ return
+ }
+
+ // Delete all objects and their versions as long as the bucket itself
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testComposeObjectWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject()"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // var testFiles = []string{"datafile-5-MB", "datafile-10-kB"}
+ var testFiles = []string{"datafile-5-MB", "datafile-10-kB"}
+ var testFilesBytes [][]byte
+
+ for _, testFile := range testFiles {
+ r := getDataReader(testFile)
+ buf, err := ioutil.ReadAll(r)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "unexpected failure", err)
+ return
+ }
+ r.Close()
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ testFilesBytes = append(testFilesBytes, buf)
+ }
+
+ objectsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+
+ var results []minio.ObjectInfo
+ for info := range objectsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ results = append(results, info)
+ }
+
+ sort.SliceStable(results, func(i, j int) bool {
+ return results[i].Size > results[j].Size
+ })
+
+ // Source objects to concatenate. We also specify decryption
+ // key for each
+ src1 := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ VersionID: results[0].VersionID,
+ }
+
+ src2 := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ VersionID: results[1].VersionID,
+ }
+
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: objectName + "-copy",
+ }
+
+ _, err = c.ComposeObject(context.Background(), dst, src1, src2)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ComposeObject failed", err)
+ return
+ }
+
+ // Destination object
+ readerCopy, err := c.GetObject(context.Background(), bucketName, objectName+"-copy", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject of the copy object failed", err)
+ return
+ }
+ defer readerCopy.Close()
+
+ copyContentBytes, err := ioutil.ReadAll(readerCopy)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Reading from the copy object reader failed", err)
+ return
+ }
+
+ var expectedContent []byte
+ for _, fileBytes := range testFilesBytes {
+ expectedContent = append(expectedContent, fileBytes...)
+ }
+
+ if len(copyContentBytes) == 0 || !bytes.Equal(copyContentBytes, expectedContent) {
+ logError(testName, function, args, startTime, "", "Unexpected destination object content", err)
+ return
+ }
+
+ // Delete all objects and their versions as long as the bucket itself
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testRemoveObjectWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "DeleteObject()"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, getDataReader("datafile-10-kB"), int64(dataFileMap["datafile-10-kB"]), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ objectsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+ var version minio.ObjectInfo
+ for info := range objectsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ version = info
+ break
+ }
+
+ err = c.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{VersionID: version.VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "DeleteObject failed", err)
+ return
+ }
+
+ objectsInfo = c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+ for range objectsInfo {
+ logError(testName, function, args, startTime, "", "Unexpected versioning info, should not have any one ", err)
+ return
+ }
+
+ err = c.RemoveBucket(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testRemoveObjectsWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "DeleteObjects()"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, getDataReader("datafile-10-kB"), int64(dataFileMap["datafile-10-kB"]), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ objectsVersions := make(chan minio.ObjectInfo)
+ go func() {
+ objectsVersionsInfo := c.ListObjects(context.Background(), bucketName,
+ minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+ for info := range objectsVersionsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ objectsVersions <- info
+ }
+ close(objectsVersions)
+ }()
+
+ removeErrors := c.RemoveObjects(context.Background(), bucketName, objectsVersions, minio.RemoveObjectsOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "DeleteObjects call failed", err)
+ return
+ }
+
+ for e := range removeErrors {
+ if e.Err != nil {
+ logError(testName, function, args, startTime, "", "Single delete operation failed", err)
+ return
+ }
+ }
+
+ objectsVersionsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+ for range objectsVersionsInfo {
+ logError(testName, function, args, startTime, "", "Unexpected versioning info, should not have any one ", err)
+ return
+ }
+
+ err = c.RemoveBucket(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testObjectTaggingWithVersioning() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "{Get,Set,Remove}ObjectTagging()"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ err = c.EnableVersioning(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Enable versioning failed", err)
+ return
+ }
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ for _, file := range []string{"datafile-1-b", "datafile-10-kB"} {
+ _, err = c.PutObject(context.Background(), bucketName, objectName, getDataReader(file), int64(dataFileMap[file]), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ }
+
+ versionsInfo := c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{WithVersions: true, Recursive: true})
+
+ var versions []minio.ObjectInfo
+ for info := range versionsInfo {
+ if info.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error during listing objects", err)
+ return
+ }
+ versions = append(versions, info)
+ }
+
+ sort.SliceStable(versions, func(i, j int) bool {
+ return versions[i].Size < versions[j].Size
+ })
+
+ tagsV1 := map[string]string{"key1": "val1"}
+ t1, err := tags.MapToObjectTags(tagsV1)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectTagging (1) failed", err)
+ return
+ }
+
+ err = c.PutObjectTagging(context.Background(), bucketName, objectName, t1, minio.PutObjectTaggingOptions{VersionID: versions[0].VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectTagging (1) failed", err)
+ return
+ }
+
+ tagsV2 := map[string]string{"key2": "val2"}
+ t2, err := tags.MapToObjectTags(tagsV2)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectTagging (1) failed", err)
+ return
+ }
+
+ err = c.PutObjectTagging(context.Background(), bucketName, objectName, t2, minio.PutObjectTaggingOptions{VersionID: versions[1].VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectTagging (2) failed", err)
+ return
+ }
+
+ tagsEqual := func(tags1, tags2 map[string]string) bool {
+ for k1, v1 := range tags1 {
+ v2, found := tags2[k1]
+ if found {
+ if v1 != v2 {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ gotTagsV1, err := c.GetObjectTagging(context.Background(), bucketName, objectName, minio.GetObjectTaggingOptions{VersionID: versions[0].VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObjectTagging failed", err)
+ return
+ }
+
+ if !tagsEqual(t1.ToMap(), gotTagsV1.ToMap()) {
+ logError(testName, function, args, startTime, "", "Unexpected tags content (1)", err)
+ return
+ }
+
+ gotTagsV2, err := c.GetObjectTagging(context.Background(), bucketName, objectName, minio.GetObjectTaggingOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObjectTaggingContext failed", err)
+ return
+ }
+
+ if !tagsEqual(t2.ToMap(), gotTagsV2.ToMap()) {
+ logError(testName, function, args, startTime, "", "Unexpected tags content (2)", err)
+ return
+ }
+
+ err = c.RemoveObjectTagging(context.Background(), bucketName, objectName, minio.RemoveObjectTaggingOptions{VersionID: versions[0].VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectTagging (2) failed", err)
+ return
+ }
+
+ emptyTags, err := c.GetObjectTagging(context.Background(), bucketName, objectName,
+ minio.GetObjectTaggingOptions{VersionID: versions[0].VersionID})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObjectTagging failed", err)
+ return
+ }
+
+ if len(emptyTags.ToMap()) != 0 {
+ logError(testName, function, args, startTime, "", "Unexpected tags content (2)", err)
+ return
+ }
+
+ // Delete all objects and their versions as long as the bucket itself
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test PutObject using a large data to trigger multipart readat
+func testPutObjectWithMetadata() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader,size, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "opts": "minio.PutObjectOptions{UserMetadata: metadata, Progress: progress}",
+ }
+
+ if !isFullMode() {
+ ignoredLog(testName, function, args, startTime, "Skipping functional tests for short/quick runs").Info()
+ return
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Make bucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Object custom metadata
+ customContentType := "custom/contenttype"
+
+ args["metadata"] = map[string][]string{
+ "Content-Type": {customContentType},
+ "X-Amz-Meta-CustomKey": {"extra spaces in value"},
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{
+ ContentType: customContentType})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes returned by PutObject does not match GetObject, expected "+string(bufSize)+" got "+string(st.Size), err)
+ return
+ }
+ if st.ContentType != customContentType && st.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "ContentType does not match, expected "+customContentType+" got "+st.ContentType, err)
+ return
+ }
+ if err := crcMatchesName(r, "datafile-129-MB"); err != nil {
+ logError(testName, function, args, startTime, "", "data CRC check failed", err)
+ return
+ }
+ if err := r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "Object Close failed", err)
+ return
+ }
+ if err := r.Close(); err == nil {
+ logError(testName, function, args, startTime, "", "Object already closed, should respond with error", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testPutObjectWithContentLanguage() {
+ // initialize logging params
+ objectName := "test-object"
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader, size, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": objectName,
+ "size": -1,
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ data := []byte{}
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(data), int64(0), minio.PutObjectOptions{
+ ContentLanguage: "en",
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ objInfo, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ if objInfo.Metadata.Get("Content-Language") != "en" {
+ logError(testName, function, args, startTime, "", "Expected content-language 'en' doesn't match with StatObject return value", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test put object with streaming signature.
+func testPutObjectStreaming() {
+ // initialize logging params
+ objectName := "test-object"
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader,size,opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": objectName,
+ "size": -1,
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload an object.
+ sizes := []int64{0, 64*1024 - 1, 64 * 1024}
+
+ for _, size := range sizes {
+ data := newRandomReader(size, size)
+ ui, err := c.PutObject(context.Background(), bucketName, objectName, data, int64(size), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectStreaming failed", err)
+ return
+ }
+
+ if ui.Size != size {
+ logError(testName, function, args, startTime, "", "PutObjectStreaming result has unexpected size", nil)
+ return
+ }
+
+ objInfo, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if objInfo.Size != size {
+ logError(testName, function, args, startTime, "", "Unexpected size", err)
+ return
+ }
+
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test get object seeker from the end, using whence set to '2'.
+func testGetObjectSeekEnd() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes read does not match, expected "+string(int64(bufSize))+" got "+string(st.Size), err)
+ return
+ }
+
+ pos, err := r.Seek(-100, 2)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Object Seek failed", err)
+ return
+ }
+ if pos != st.Size-100 {
+ logError(testName, function, args, startTime, "", "Incorrect position", err)
+ return
+ }
+ buf2 := make([]byte, 100)
+ m, err := readFull(r, buf2)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Error reading through readFull", err)
+ return
+ }
+ if m != len(buf2) {
+ logError(testName, function, args, startTime, "", "Number of bytes dont match, expected "+string(len(buf2))+" got "+string(m), err)
+ return
+ }
+ hexBuf1 := fmt.Sprintf("%02x", buf[len(buf)-100:])
+ hexBuf2 := fmt.Sprintf("%02x", buf2[:m])
+ if hexBuf1 != hexBuf2 {
+ logError(testName, function, args, startTime, "", "Values at same index dont match", err)
+ return
+ }
+ pos, err = r.Seek(-100, 2)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Object Seek failed", err)
+ return
+ }
+ if pos != st.Size-100 {
+ logError(testName, function, args, startTime, "", "Incorrect position", err)
+ return
+ }
+ if err = r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "ObjectClose failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test get object reader to not throw error on being closed twice.
+func testGetObjectClosedTwice() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+" got "+string(st.Size), err)
+ return
+ }
+ if err := crcMatchesName(r, "datafile-33-kB"); err != nil {
+ logError(testName, function, args, startTime, "", "data CRC check failed", err)
+ return
+ }
+ if err := r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "Object Close failed", err)
+ return
+ }
+ if err := r.Close(); err == nil {
+ logError(testName, function, args, startTime, "", "Already closed object. No error returned", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test RemoveObjects request where context cancels after timeout
+func testRemoveObjectsContext() {
+ // Initialize logging params.
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "RemoveObjects(ctx, bucketName, objectsCh)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ }
+
+ // Seed random based on current tie.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+ // Enable tracing, write to stdout.
+ // c.TraceOn(os.Stderr)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate put data.
+ r := bytes.NewReader(bytes.Repeat([]byte("a"), 8))
+
+ // Multi remove of 20 objects.
+ nrObjects := 20
+ objectsCh := make(chan minio.ObjectInfo)
+ go func() {
+ defer close(objectsCh)
+ for i := 0; i < nrObjects; i++ {
+ objectName := "sample" + strconv.Itoa(i) + ".txt"
+ info, err := c.PutObject(context.Background(), bucketName, objectName, r, 8,
+ minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ continue
+ }
+ objectsCh <- minio.ObjectInfo{
+ Key: info.Key,
+ VersionID: info.VersionID,
+ }
+ }
+ }()
+ // Set context to cancel in 1 nanosecond.
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Call RemoveObjects API with short timeout.
+ errorCh := c.RemoveObjects(ctx, bucketName, objectsCh, minio.RemoveObjectsOptions{})
+ // Check for error.
+ select {
+ case r := <-errorCh:
+ if r.Err == nil {
+ logError(testName, function, args, startTime, "", "RemoveObjects should fail on short timeout", err)
+ return
+ }
+ }
+ // Set context with longer timeout.
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ args["ctx"] = ctx
+ defer cancel()
+ // Perform RemoveObjects with the longer timeout. Expect the removals to succeed.
+ errorCh = c.RemoveObjects(ctx, bucketName, objectsCh, minio.RemoveObjectsOptions{})
+ select {
+ case r, more := <-errorCh:
+ if more || r.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error", r.Err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test removing multiple objects with Remove API
+func testRemoveMultipleObjects() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "RemoveObjects(bucketName, objectsCh)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Enable tracing, write to stdout.
+ // c.TraceOn(os.Stderr)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ r := bytes.NewReader(bytes.Repeat([]byte("a"), 8))
+
+ // Multi remove of 1100 objects
+ nrObjects := 200
+
+ objectsCh := make(chan minio.ObjectInfo)
+
+ go func() {
+ defer close(objectsCh)
+ // Upload objects and send them to objectsCh
+ for i := 0; i < nrObjects; i++ {
+ objectName := "sample" + strconv.Itoa(i) + ".txt"
+ info, err := c.PutObject(context.Background(), bucketName, objectName, r, 8,
+ minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ continue
+ }
+ objectsCh <- minio.ObjectInfo{
+ Key: info.Key,
+ VersionID: info.VersionID,
+ }
+ }
+ }()
+
+ // Call RemoveObjects API
+ errorCh := c.RemoveObjects(context.Background(), bucketName, objectsCh, minio.RemoveObjectsOptions{})
+
+ // Check if errorCh doesn't receive any error
+ select {
+ case r, more := <-errorCh:
+ if more {
+ logError(testName, function, args, startTime, "", "Unexpected error", r.Err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests FPutObject of a big file to trigger multipart
+func testFPutObjectMultipart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutObject(bucketName, objectName, fileName, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload 4 parts to utilize all 3 'workers' in multipart and still have a part to upload.
+ var fileName = getMintDataDirFilePath("datafile-129-MB")
+ if fileName == "" {
+ // Make a temp file with minPartSize bytes of data.
+ file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "TempFile creation failed", err)
+ return
+ }
+ // Upload 2 parts to utilize all 3 'workers' in multipart and still have a part to upload.
+ if _, err = io.Copy(file, getDataReader("datafile-129-MB")); err != nil {
+ logError(testName, function, args, startTime, "", "Copy failed", err)
+ return
+ }
+ if err = file.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "File Close failed", err)
+ return
+ }
+ fileName = file.Name()
+ args["fileName"] = fileName
+ }
+ totalSize := dataFileMap["datafile-129-MB"]
+ // Set base object name
+ objectName := bucketName + "FPutObject" + "-standard"
+ args["objectName"] = objectName
+
+ objectContentType := "testapplication/octet-stream"
+ args["objectContentType"] = objectContentType
+
+ // Perform standard FPutObject with contentType provided (Expecting application/octet-stream)
+ _, err = c.FPutObject(context.Background(), bucketName, objectName, fileName, minio.PutObjectOptions{ContentType: objectContentType})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject failed", err)
+ return
+ }
+
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ objInfo, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error", err)
+ return
+ }
+ if objInfo.Size != int64(totalSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(totalSize))+" got "+string(objInfo.Size), err)
+ return
+ }
+ if objInfo.ContentType != objectContentType && objInfo.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "ContentType doesn't match", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests FPutObject with null contentType (default = application/octet-stream)
+func testFPutObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutObject(bucketName, objectName, fileName, opts)"
+
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ location := "us-east-1"
+
+ // Make a new bucket.
+ args["bucketName"] = bucketName
+ args["location"] = location
+ function = "MakeBucket(bucketName, location)"
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: location})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload 3 parts worth of data to use all 3 of multiparts 'workers' and have an extra part.
+ // Use different data in part for multipart tests to check parts are uploaded in correct order.
+ var fName = getMintDataDirFilePath("datafile-129-MB")
+ if fName == "" {
+ // Make a temp file with minPartSize bytes of data.
+ file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "TempFile creation failed", err)
+ return
+ }
+
+ // Upload 3 parts to utilize all 3 'workers' in multipart and still have a part to upload.
+ if _, err = io.Copy(file, getDataReader("datafile-129-MB")); err != nil {
+ logError(testName, function, args, startTime, "", "File copy failed", err)
+ return
+ }
+ // Close the file pro-actively for windows.
+ if err = file.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "File close failed", err)
+ return
+ }
+ defer os.Remove(file.Name())
+ fName = file.Name()
+ }
+
+ // Set base object name
+ function = "FPutObject(bucketName, objectName, fileName, opts)"
+ objectName := bucketName + "FPutObject"
+ args["objectName"] = objectName + "-standard"
+ args["fileName"] = fName
+ args["opts"] = minio.PutObjectOptions{ContentType: "application/octet-stream"}
+
+ // Perform standard FPutObject with contentType provided (Expecting application/octet-stream)
+ ui, err := c.FPutObject(context.Background(), bucketName, objectName+"-standard", fName, minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject failed", err)
+ return
+ }
+
+ if ui.Size != int64(dataFileMap["datafile-129-MB"]) {
+ logError(testName, function, args, startTime, "", "FPutObject returned an unexpected upload size", err)
+ return
+ }
+
+ // Perform FPutObject with no contentType provided (Expecting application/octet-stream)
+ args["objectName"] = objectName + "-Octet"
+ _, err = c.FPutObject(context.Background(), bucketName, objectName+"-Octet", fName, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File close failed", err)
+ return
+ }
+
+ srcFile, err := os.Open(fName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File open failed", err)
+ return
+ }
+ defer srcFile.Close()
+ // Add extension to temp file name
+ tmpFile, err := os.Create(fName + ".gtar")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File create failed", err)
+ return
+ }
+ _, err = io.Copy(tmpFile, srcFile)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File copy failed", err)
+ return
+ }
+ tmpFile.Close()
+
+ // Perform FPutObject with no contentType provided (Expecting application/x-gtar)
+ args["objectName"] = objectName + "-GTar"
+ args["opts"] = minio.PutObjectOptions{}
+ _, err = c.FPutObject(context.Background(), bucketName, objectName+"-GTar", fName+".gtar", minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject failed", err)
+ return
+ }
+
+ // Check headers
+ function = "StatObject(bucketName, objectName, opts)"
+ args["objectName"] = objectName + "-standard"
+ rStandard, err := c.StatObject(context.Background(), bucketName, objectName+"-standard", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if rStandard.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "ContentType does not match, expected application/octet-stream, got "+rStandard.ContentType, err)
+ return
+ }
+
+ function = "StatObject(bucketName, objectName, opts)"
+ args["objectName"] = objectName + "-Octet"
+ rOctet, err := c.StatObject(context.Background(), bucketName, objectName+"-Octet", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if rOctet.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "ContentType does not match, expected application/octet-stream, got "+rOctet.ContentType, err)
+ return
+ }
+
+ function = "StatObject(bucketName, objectName, opts)"
+ args["objectName"] = objectName + "-GTar"
+ rGTar, err := c.StatObject(context.Background(), bucketName, objectName+"-GTar", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if rGTar.ContentType != "application/x-gtar" && rGTar.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "ContentType does not match, expected application/x-gtar or application/octet-stream, got "+rGTar.ContentType, err)
+ return
+ }
+
+ os.Remove(fName + ".gtar")
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests FPutObject request when context cancels after timeout
+func testFPutObjectContext() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutObject(bucketName, objectName, fileName, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ "opts": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload 1 parts worth of data to use multipart upload.
+ // Use different data in part for multipart tests to check parts are uploaded in correct order.
+ var fName = getMintDataDirFilePath("datafile-1-MB")
+ if fName == "" {
+ // Make a temp file with 1 MiB bytes of data.
+ file, err := ioutil.TempFile(os.TempDir(), "FPutObjectContextTest")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "TempFile creation failed", err)
+ return
+ }
+
+ // Upload 1 parts to trigger multipart upload
+ if _, err = io.Copy(file, getDataReader("datafile-1-MB")); err != nil {
+ logError(testName, function, args, startTime, "", "File copy failed", err)
+ return
+ }
+ // Close the file pro-actively for windows.
+ if err = file.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "File close failed", err)
+ return
+ }
+ defer os.Remove(file.Name())
+ fName = file.Name()
+ }
+
+ // Set base object name
+ objectName := bucketName + "FPutObjectContext"
+ args["objectName"] = objectName
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Perform FPutObject with contentType provided (Expecting application/octet-stream)
+ _, err = c.FPutObject(ctx, bucketName, objectName+"-Shorttimeout", fName, minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "FPutObject should fail on short timeout", err)
+ return
+ }
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ defer cancel()
+ // Perform FPutObject with a long timeout. Expect the put object to succeed
+ _, err = c.FPutObject(ctx, bucketName, objectName+"-Longtimeout", fName, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject shouldn't fail on long timeout", err)
+ return
+ }
+
+ _, err = c.StatObject(context.Background(), bucketName, objectName+"-Longtimeout", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Tests FPutObject request when context cancels after timeout
+func testFPutObjectContextV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutObjectContext(ctx, bucketName, objectName, fileName, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "opts": "minio.PutObjectOptions{ContentType:objectContentType}",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload 1 parts worth of data to use multipart upload.
+ // Use different data in part for multipart tests to check parts are uploaded in correct order.
+ var fName = getMintDataDirFilePath("datafile-1-MB")
+ if fName == "" {
+ // Make a temp file with 1 MiB bytes of data.
+ file, err := ioutil.TempFile(os.TempDir(), "FPutObjectContextTest")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Temp file creation failed", err)
+ return
+ }
+
+ // Upload 1 parts to trigger multipart upload
+ if _, err = io.Copy(file, getDataReader("datafile-1-MB")); err != nil {
+ logError(testName, function, args, startTime, "", "File copy failed", err)
+ return
+ }
+
+ // Close the file pro-actively for windows.
+ if err = file.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "File close failed", err)
+ return
+ }
+ defer os.Remove(file.Name())
+ fName = file.Name()
+ }
+
+ // Set base object name
+ objectName := bucketName + "FPutObjectContext"
+ args["objectName"] = objectName
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Perform FPutObject with contentType provided (Expecting application/octet-stream)
+ _, err = c.FPutObject(ctx, bucketName, objectName+"-Shorttimeout", fName, minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "FPutObject should fail on short timeout", err)
+ return
+ }
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ defer cancel()
+ // Perform FPutObject with a long timeout. Expect the put object to succeed
+ _, err = c.FPutObject(ctx, bucketName, objectName+"-Longtimeout", fName, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject shouldn't fail on longer timeout", err)
+ return
+ }
+
+ _, err = c.StatObject(context.Background(), bucketName, objectName+"-Longtimeout", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Test validates putObject with context to see if request cancellation is honored.
+func testPutObjectContext() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(ctx, bucketName, objectName, fileName, opts)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ "opts": "",
+ }
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Make a new bucket.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket call failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+ objectName := fmt.Sprintf("test-file-%v", rand.Uint32())
+ args["objectName"] = objectName
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ cancel()
+ args["ctx"] = ctx
+ args["opts"] = minio.PutObjectOptions{ContentType: "binary/octet-stream"}
+
+ _, err = c.PutObject(ctx, bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "PutObject should fail on short timeout", err)
+ return
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ args["ctx"] = ctx
+
+ defer cancel()
+ reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+ _, err = c.PutObject(ctx, bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject with long timeout failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Tests get object ReaderSeeker interface methods.
+func testGetObjectReadSeekFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer func() {
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+ }()
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat object failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ // This following function helps us to compare data from the reader after seek
+ // with the data from the original buffer
+ cmpData := func(r io.Reader, start, end int) {
+ if end-start == 0 {
+ return
+ }
+ buffer := bytes.NewBuffer([]byte{})
+ if _, err := io.CopyN(buffer, r, int64(bufSize)); err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "CopyN failed", err)
+ return
+ }
+ }
+ if !bytes.Equal(buf[start:end], buffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Incorrect read bytes v/s original buffer", err)
+ return
+ }
+ }
+
+ // Generic seek error for errors other than io.EOF
+ seekErr := errors.New("seek error")
+
+ testCases := []struct {
+ offset int64
+ whence int
+ pos int64
+ err error
+ shouldCmp bool
+ start int
+ end int
+ }{
+ // Start from offset 0, fetch data and compare
+ {0, 0, 0, nil, true, 0, 0},
+ // Start from offset 2048, fetch data and compare
+ {2048, 0, 2048, nil, true, 2048, bufSize},
+ // Start from offset larger than possible
+ {int64(bufSize) + 1024, 0, 0, seekErr, false, 0, 0},
+ // Move to offset 0 without comparing
+ {0, 0, 0, nil, false, 0, 0},
+ // Move one step forward and compare
+ {1, 1, 1, nil, true, 1, bufSize},
+ // Move larger than possible
+ {int64(bufSize), 1, 0, seekErr, false, 0, 0},
+ // Provide negative offset with CUR_SEEK
+ {int64(-1), 1, 0, seekErr, false, 0, 0},
+ // Test with whence SEEK_END and with positive offset
+ {1024, 2, int64(bufSize) - 1024, io.EOF, true, 0, 0},
+ // Test with whence SEEK_END and with negative offset
+ {-1024, 2, int64(bufSize) - 1024, nil, true, bufSize - 1024, bufSize},
+ // Test with whence SEEK_END and with large negative offset
+ {-int64(bufSize) * 2, 2, 0, seekErr, true, 0, 0},
+ }
+
+ for i, testCase := range testCases {
+ // Perform seek operation
+ n, err := r.Seek(testCase.offset, testCase.whence)
+ // We expect an error
+ if testCase.err == seekErr && err == nil {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", unexpected err value: expected: "+testCase.err.Error()+", found: "+err.Error(), err)
+ return
+ }
+ // We expect a specific error
+ if testCase.err != seekErr && testCase.err != err {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", unexpected err value: expected: "+testCase.err.Error()+", found: "+err.Error(), err)
+ return
+ }
+ // If we expect an error go to the next loop
+ if testCase.err != nil {
+ continue
+ }
+ // Check the returned seek pos
+ if n != testCase.pos {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", number of bytes seeked does not match, expected "+string(testCase.pos)+", got "+string(n), err)
+ return
+ }
+ // Compare only if shouldCmp is activated
+ if testCase.shouldCmp {
+ cmpData(r, testCase.start, testCase.end)
+ }
+ }
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests get object ReaderAt interface methods.
+func testGetObjectReadAtFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ offset := int64(2048)
+
+ // read directly
+ buf1 := make([]byte, 512)
+ buf2 := make([]byte, 512)
+ buf3 := make([]byte, 512)
+ buf4 := make([]byte, 512)
+
+ // Test readAt before stat is called such that objectInfo doesn't change.
+ m, err := r.ReadAt(buf1, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf1) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf1))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf1, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ m, err = r.ReadAt(buf2, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf2) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf2, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+
+ offset += 512
+ m, err = r.ReadAt(buf3, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf3) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf3))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf3, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf4, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf4) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf4))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf4, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+
+ buf5 := make([]byte, len(buf))
+ // Read the whole object.
+ m, err = r.ReadAt(buf5, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+ if m != len(buf5) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf5))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf, buf5) {
+ logError(testName, function, args, startTime, "", "Incorrect data read in GetObject, than what was previously uploaded", err)
+ return
+ }
+
+ buf6 := make([]byte, len(buf)+1)
+ // Read the whole object and beyond.
+ _, err = r.ReadAt(buf6, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Reproduces issue https://github.com/minio/minio-go/issues/1137
+func testGetObjectReadAtWhenEOFWasReached() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // read directly
+ buf1 := make([]byte, len(buf))
+ buf2 := make([]byte, 512)
+
+ m, err := r.Read(buf1)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "Read failed", err)
+ return
+ }
+ }
+ if m != len(buf1) {
+ logError(testName, function, args, startTime, "", "Read read shorter bytes before reaching EOF, expected "+string(len(buf1))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf1, buf) {
+ logError(testName, function, args, startTime, "", "Incorrect count of Read data", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ m, err = r.ReadAt(buf2, 512)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf2) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf2, buf[512:1024]) {
+ logError(testName, function, args, startTime, "", "Incorrect count of ReadAt data", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test Presigned Post Policy
+func testPresignedPostPolicy() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PresignedPostPolicy(policy)"
+ args := map[string]interface{}{
+ "policy": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ // Azure requires the key to not start with a number
+ metadataKey := randString(60, rand.NewSource(time.Now().UnixNano()), "user")
+ metadataValue := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ policy := minio.NewPostPolicy()
+
+ if err := policy.SetBucket(""); err == nil {
+ logError(testName, function, args, startTime, "", "SetBucket did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetKey(""); err == nil {
+ logError(testName, function, args, startTime, "", "SetKey did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetKeyStartsWith(""); err == nil {
+ logError(testName, function, args, startTime, "", "SetKeyStartsWith did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetExpires(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)); err == nil {
+ logError(testName, function, args, startTime, "", "SetExpires did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetContentType(""); err == nil {
+ logError(testName, function, args, startTime, "", "SetContentType did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetContentTypeStartsWith(""); err == nil {
+ logError(testName, function, args, startTime, "", "SetContentTypeStartsWith did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetContentLengthRange(1024*1024, 1024); err == nil {
+ logError(testName, function, args, startTime, "", "SetContentLengthRange did not fail for invalid conditions", err)
+ return
+ }
+ if err := policy.SetUserMetadata("", ""); err == nil {
+ logError(testName, function, args, startTime, "", "SetUserMetadata did not fail for invalid conditions", err)
+ return
+ }
+
+ policy.SetBucket(bucketName)
+ policy.SetKey(objectName)
+ policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days
+ policy.SetContentType("binary/octet-stream")
+ policy.SetContentLengthRange(10, 1024*1024)
+ policy.SetUserMetadata(metadataKey, metadataValue)
+ args["policy"] = policy.String()
+
+ presignedPostPolicyURL, formData, err := c.PresignedPostPolicy(context.Background(), policy)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedPostPolicy failed", err)
+ return
+ }
+
+ var formBuf bytes.Buffer
+ writer := multipart.NewWriter(&formBuf)
+ for k, v := range formData {
+ writer.WriteField(k, v)
+ }
+
+ // Get a 33KB file to upload and test if set post policy works
+ var filePath = getMintDataDirFilePath("datafile-33-kB")
+ if filePath == "" {
+ // Make a temp file with 33 KB data.
+ file, err := ioutil.TempFile(os.TempDir(), "PresignedPostPolicyTest")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "TempFile creation failed", err)
+ return
+ }
+ if _, err = io.Copy(file, getDataReader("datafile-33-kB")); err != nil {
+ logError(testName, function, args, startTime, "", "Copy failed", err)
+ return
+ }
+ if err = file.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "File Close failed", err)
+ return
+ }
+ filePath = file.Name()
+ }
+
+ // add file to post request
+ f, err := os.Open(filePath)
+ defer f.Close()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File open failed", err)
+ return
+ }
+ w, err := writer.CreateFormFile("file", filePath)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CreateFormFile failed", err)
+ return
+ }
+
+ _, err = io.Copy(w, f)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Copy failed", err)
+ return
+ }
+ writer.Close()
+
+ transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS)))
+ if err != nil {
+ logError(testName, function, args, startTime, "", "DefaultTransport failed", err)
+ return
+ }
+
+ httpClient := &http.Client{
+ // Setting a sensible time out of 30secs to wait for response
+ // headers. Request is pro-actively canceled after 30secs
+ // with no response.
+ Timeout: 30 * time.Second,
+ Transport: transport,
+ }
+
+ req, err := http.NewRequest(http.MethodPost, presignedPostPolicyURL.String(), bytes.NewReader(formBuf.Bytes()))
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Http request failed", err)
+ return
+ }
+
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ // make post request with correct form data
+ res, err := httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Http request failed", err)
+ return
+ }
+ defer res.Body.Close()
+ if res.StatusCode != http.StatusNoContent {
+ logError(testName, function, args, startTime, "", "Http request failed", errors.New(res.Status))
+ return
+ }
+
+ // expected path should be absolute path of the object
+ var scheme string
+ if mustParseBool(os.Getenv(enableHTTPS)) {
+ scheme = "https://"
+ } else {
+ scheme = "http://"
+ }
+
+ expectedLocation := scheme + os.Getenv(serverEndpoint) + "/" + bucketName + "/" + objectName
+ expectedLocationBucketDNS := scheme + bucketName + "." + os.Getenv(serverEndpoint) + "/" + objectName
+
+ if val, ok := res.Header["Location"]; ok {
+ if val[0] != expectedLocation && val[0] != expectedLocationBucketDNS {
+ logError(testName, function, args, startTime, "", "Location in header response is incorrect", err)
+ return
+ }
+ } else {
+ logError(testName, function, args, startTime, "", "Location not found in header response", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests copy object
+func testCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(dst, src)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Make a new bucket in 'us-east-1' (destination bucket).
+ err = c.MakeBucket(context.Background(), bucketName+"-copy", minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName+"-copy", c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ // Check the various fields of source object against destination object.
+ objInfo, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ // Copy Source
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ // Set copy conditions.
+ MatchETag: objInfo.ETag,
+ MatchModifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+ }
+ args["src"] = src
+
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName + "-copy",
+ Object: objectName + "-copy",
+ }
+
+ // Perform the Copy
+ if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ // Source object
+ r, err = c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ // Destination object
+ readerCopy, err := c.GetObject(context.Background(), bucketName+"-copy", objectName+"-copy", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ // Check the various fields of source object against destination object.
+ objInfo, err = r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ objInfoCopy, err := readerCopy.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ if objInfo.Size != objInfoCopy.Size {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(objInfoCopy.Size)+", got "+string(objInfo.Size), err)
+ return
+ }
+
+ if err := crcMatchesName(r, "datafile-33-kB"); err != nil {
+ logError(testName, function, args, startTime, "", "data CRC check failed", err)
+ return
+ }
+ if err := crcMatchesName(readerCopy, "datafile-33-kB"); err != nil {
+ logError(testName, function, args, startTime, "", "copy data CRC check failed", err)
+ return
+ }
+ // Close all the get readers before proceeding with CopyObject operations.
+ r.Close()
+ readerCopy.Close()
+
+ // CopyObject again but with wrong conditions
+ src = minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ MatchUnmodifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+ NoMatchETag: objInfo.ETag,
+ }
+
+ // Perform the Copy which should fail
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err == nil {
+ logError(testName, function, args, startTime, "", "CopyObject did not fail for invalid conditions", err)
+ return
+ }
+
+ src = minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ }
+
+ dst = minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ ReplaceMetadata: true,
+ UserMetadata: map[string]string{
+ "Copy": "should be same",
+ },
+ }
+ args["dst"] = dst
+ args["src"] = src
+
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject shouldn't fail", err)
+ return
+ }
+
+ oi, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ stOpts := minio.StatObjectOptions{}
+ stOpts.SetMatchETag(oi.ETag)
+ objInfo, err = c.StatObject(context.Background(), bucketName, objectName, stOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject ETag should match and not fail", err)
+ return
+ }
+
+ if objInfo.Metadata.Get("x-amz-meta-copy") != "should be same" {
+ logError(testName, function, args, startTime, "", "CopyObject modified metadata should match", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests SSE-C get object ReaderSeeker interface methods.
+func testSSECEncryptedGetObjectReadSeekFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer func() {
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+ }()
+
+ // Generate 129MiB of data.
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{
+ ContentType: "binary/octet-stream",
+ ServerSideEncryption: encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+objectName)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{
+ ServerSideEncryption: encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+objectName)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat object failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ // This following function helps us to compare data from the reader after seek
+ // with the data from the original buffer
+ cmpData := func(r io.Reader, start, end int) {
+ if end-start == 0 {
+ return
+ }
+ buffer := bytes.NewBuffer([]byte{})
+ if _, err := io.CopyN(buffer, r, int64(bufSize)); err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "CopyN failed", err)
+ return
+ }
+ }
+ if !bytes.Equal(buf[start:end], buffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Incorrect read bytes v/s original buffer", err)
+ return
+ }
+ }
+
+ testCases := []struct {
+ offset int64
+ whence int
+ pos int64
+ err error
+ shouldCmp bool
+ start int
+ end int
+ }{
+ // Start from offset 0, fetch data and compare
+ {0, 0, 0, nil, true, 0, 0},
+ // Start from offset 2048, fetch data and compare
+ {2048, 0, 2048, nil, true, 2048, bufSize},
+ // Start from offset larger than possible
+ {int64(bufSize) + 1024, 0, 0, io.EOF, false, 0, 0},
+ // Move to offset 0 without comparing
+ {0, 0, 0, nil, false, 0, 0},
+ // Move one step forward and compare
+ {1, 1, 1, nil, true, 1, bufSize},
+ // Move larger than possible
+ {int64(bufSize), 1, 0, io.EOF, false, 0, 0},
+ // Provide negative offset with CUR_SEEK
+ {int64(-1), 1, 0, fmt.Errorf("Negative position not allowed for 1"), false, 0, 0},
+ // Test with whence SEEK_END and with positive offset
+ {1024, 2, 0, io.EOF, false, 0, 0},
+ // Test with whence SEEK_END and with negative offset
+ {-1024, 2, int64(bufSize) - 1024, nil, true, bufSize - 1024, bufSize},
+ // Test with whence SEEK_END and with large negative offset
+ {-int64(bufSize) * 2, 2, 0, fmt.Errorf("Seeking at negative offset not allowed for 2"), false, 0, 0},
+ // Test with invalid whence
+ {0, 3, 0, fmt.Errorf("Invalid whence 3"), false, 0, 0},
+ }
+
+ for i, testCase := range testCases {
+ // Perform seek operation
+ n, err := r.Seek(testCase.offset, testCase.whence)
+ if err != nil && testCase.err == nil {
+ // We expected success.
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, unexpected err value: expected: %s, found: %s", i+1, testCase.err, err), err)
+ return
+ }
+ if err == nil && testCase.err != nil {
+ // We expected failure, but got success.
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, unexpected err value: expected: %s, found: %s", i+1, testCase.err, err), err)
+ return
+ }
+ if err != nil && testCase.err != nil {
+ if err.Error() != testCase.err.Error() {
+ // We expect a specific error
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, unexpected err value: expected: %s, found: %s", i+1, testCase.err, err), err)
+ return
+ }
+ }
+ // Check the returned seek pos
+ if n != testCase.pos {
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, number of bytes seeked does not match, expected %d, got %d", i+1, testCase.pos, n), err)
+ return
+ }
+ // Compare only if shouldCmp is activated
+ if testCase.shouldCmp {
+ cmpData(r, testCase.start, testCase.end)
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests SSE-S3 get object ReaderSeeker interface methods.
+func testSSES3EncryptedGetObjectReadSeekFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer func() {
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+ }()
+
+ // Generate 129MiB of data.
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{
+ ContentType: "binary/octet-stream",
+ ServerSideEncryption: encrypt.NewSSE(),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat object failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ // This following function helps us to compare data from the reader after seek
+ // with the data from the original buffer
+ cmpData := func(r io.Reader, start, end int) {
+ if end-start == 0 {
+ return
+ }
+ buffer := bytes.NewBuffer([]byte{})
+ if _, err := io.CopyN(buffer, r, int64(bufSize)); err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "CopyN failed", err)
+ return
+ }
+ }
+ if !bytes.Equal(buf[start:end], buffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Incorrect read bytes v/s original buffer", err)
+ return
+ }
+ }
+
+ testCases := []struct {
+ offset int64
+ whence int
+ pos int64
+ err error
+ shouldCmp bool
+ start int
+ end int
+ }{
+ // Start from offset 0, fetch data and compare
+ {0, 0, 0, nil, true, 0, 0},
+ // Start from offset 2048, fetch data and compare
+ {2048, 0, 2048, nil, true, 2048, bufSize},
+ // Start from offset larger than possible
+ {int64(bufSize) + 1024, 0, 0, io.EOF, false, 0, 0},
+ // Move to offset 0 without comparing
+ {0, 0, 0, nil, false, 0, 0},
+ // Move one step forward and compare
+ {1, 1, 1, nil, true, 1, bufSize},
+ // Move larger than possible
+ {int64(bufSize), 1, 0, io.EOF, false, 0, 0},
+ // Provide negative offset with CUR_SEEK
+ {int64(-1), 1, 0, fmt.Errorf("Negative position not allowed for 1"), false, 0, 0},
+ // Test with whence SEEK_END and with positive offset
+ {1024, 2, 0, io.EOF, false, 0, 0},
+ // Test with whence SEEK_END and with negative offset
+ {-1024, 2, int64(bufSize) - 1024, nil, true, bufSize - 1024, bufSize},
+ // Test with whence SEEK_END and with large negative offset
+ {-int64(bufSize) * 2, 2, 0, fmt.Errorf("Seeking at negative offset not allowed for 2"), false, 0, 0},
+ // Test with invalid whence
+ {0, 3, 0, fmt.Errorf("Invalid whence 3"), false, 0, 0},
+ }
+
+ for i, testCase := range testCases {
+ // Perform seek operation
+ n, err := r.Seek(testCase.offset, testCase.whence)
+ if err != nil && testCase.err == nil {
+ // We expected success.
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, unexpected err value: expected: %s, found: %s", i+1, testCase.err, err), err)
+ return
+ }
+ if err == nil && testCase.err != nil {
+ // We expected failure, but got success.
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, unexpected err value: expected: %s, found: %s", i+1, testCase.err, err), err)
+ return
+ }
+ if err != nil && testCase.err != nil {
+ if err.Error() != testCase.err.Error() {
+ // We expect a specific error
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, unexpected err value: expected: %s, found: %s", i+1, testCase.err, err), err)
+ return
+ }
+ }
+ // Check the returned seek pos
+ if n != testCase.pos {
+ logError(testName, function, args, startTime, "",
+ fmt.Sprintf("Test %d, number of bytes seeked does not match, expected %d, got %d", i+1, testCase.pos, n), err)
+ return
+ }
+ // Compare only if shouldCmp is activated
+ if testCase.shouldCmp {
+ cmpData(r, testCase.start, testCase.end)
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests SSE-C get object ReaderAt interface methods.
+func testSSECEncryptedGetObjectReadAtFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 129MiB of data.
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{
+ ContentType: "binary/octet-stream",
+ ServerSideEncryption: encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+objectName)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{
+ ServerSideEncryption: encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+objectName)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ offset := int64(2048)
+
+ // read directly
+ buf1 := make([]byte, 512)
+ buf2 := make([]byte, 512)
+ buf3 := make([]byte, 512)
+ buf4 := make([]byte, 512)
+
+ // Test readAt before stat is called such that objectInfo doesn't change.
+ m, err := r.ReadAt(buf1, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf1) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf1))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf1, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ m, err = r.ReadAt(buf2, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf2) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf2, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf3, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf3) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf3))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf3, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf4, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf4) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf4))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf4, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+
+ buf5 := make([]byte, len(buf))
+ // Read the whole object.
+ m, err = r.ReadAt(buf5, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+ if m != len(buf5) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf5))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf, buf5) {
+ logError(testName, function, args, startTime, "", "Incorrect data read in GetObject, than what was previously uploaded", err)
+ return
+ }
+
+ buf6 := make([]byte, len(buf)+1)
+ // Read the whole object and beyond.
+ _, err = r.ReadAt(buf6, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests SSE-S3 get object ReaderAt interface methods.
+func testSSES3EncryptedGetObjectReadAtFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 129MiB of data.
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{
+ ContentType: "binary/octet-stream",
+ ServerSideEncryption: encrypt.NewSSE(),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ offset := int64(2048)
+
+ // read directly
+ buf1 := make([]byte, 512)
+ buf2 := make([]byte, 512)
+ buf3 := make([]byte, 512)
+ buf4 := make([]byte, 512)
+
+ // Test readAt before stat is called such that objectInfo doesn't change.
+ m, err := r.ReadAt(buf1, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf1) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf1))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf1, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
+ return
+ }
+
+ m, err = r.ReadAt(buf2, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf2) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf2, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf3, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf3) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf3))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf3, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf4, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf4) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf4))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf4, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+
+ buf5 := make([]byte, len(buf))
+ // Read the whole object.
+ m, err = r.ReadAt(buf5, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+ if m != len(buf5) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf5))+", got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf, buf5) {
+ logError(testName, function, args, startTime, "", "Incorrect data read in GetObject, than what was previously uploaded", err)
+ return
+ }
+
+ buf6 := make([]byte, len(buf)+1)
+ // Read the whole object and beyond.
+ _, err = r.ReadAt(buf6, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// testSSECEncryptionPutGet tests encryption with customer provided encryption keys
+func testSSECEncryptionPutGet() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutEncryptedObject(bucketName, objectName, reader, sse)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "sse": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ testCases := []struct {
+ buf []byte
+ }{
+ {buf: bytes.Repeat([]byte("F"), 1)},
+ {buf: bytes.Repeat([]byte("F"), 15)},
+ {buf: bytes.Repeat([]byte("F"), 16)},
+ {buf: bytes.Repeat([]byte("F"), 17)},
+ {buf: bytes.Repeat([]byte("F"), 31)},
+ {buf: bytes.Repeat([]byte("F"), 32)},
+ {buf: bytes.Repeat([]byte("F"), 33)},
+ {buf: bytes.Repeat([]byte("F"), 1024)},
+ {buf: bytes.Repeat([]byte("F"), 1024*2)},
+ {buf: bytes.Repeat([]byte("F"), 1024*1024)},
+ }
+
+ const password = "correct horse battery staple" // https://xkcd.com/936/
+
+ for i, testCase := range testCases {
+ // Generate a random object name
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Secured object
+ sse := encrypt.DefaultPBKDF([]byte(password), []byte(bucketName+objectName))
+ args["sse"] = sse
+
+ // Put encrypted data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(testCase.buf), int64(len(testCase.buf)), minio.PutObjectOptions{ServerSideEncryption: sse})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutEncryptedObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{ServerSideEncryption: sse})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetEncryptedObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ // Compare the sent object with the received one
+ recvBuffer := bytes.NewBuffer([]byte{})
+ if _, err = io.Copy(recvBuffer, r); err != nil {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", error: "+err.Error(), err)
+ return
+ }
+ if recvBuffer.Len() != len(testCase.buf) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Number of bytes of received object does not match, expected "+string(len(testCase.buf))+", got "+string(recvBuffer.Len()), err)
+ return
+ }
+ if !bytes.Equal(testCase.buf, recvBuffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Encrypted sent is not equal to decrypted, expected "+string(testCase.buf)+", got "+string(recvBuffer.Bytes()), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// TestEncryptionFPut tests encryption with customer specified encryption keys
+func testSSECEncryptionFPut() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutEncryptedObject(bucketName, objectName, filePath, contentType, sse)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "filePath": "",
+ "contentType": "",
+ "sse": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Object custom metadata
+ customContentType := "custom/contenttype"
+ args["metadata"] = customContentType
+
+ testCases := []struct {
+ buf []byte
+ }{
+ {buf: bytes.Repeat([]byte("F"), 0)},
+ {buf: bytes.Repeat([]byte("F"), 1)},
+ {buf: bytes.Repeat([]byte("F"), 15)},
+ {buf: bytes.Repeat([]byte("F"), 16)},
+ {buf: bytes.Repeat([]byte("F"), 17)},
+ {buf: bytes.Repeat([]byte("F"), 31)},
+ {buf: bytes.Repeat([]byte("F"), 32)},
+ {buf: bytes.Repeat([]byte("F"), 33)},
+ {buf: bytes.Repeat([]byte("F"), 1024)},
+ {buf: bytes.Repeat([]byte("F"), 1024*2)},
+ {buf: bytes.Repeat([]byte("F"), 1024*1024)},
+ }
+
+ const password = "correct horse battery staple" // https://xkcd.com/936/
+ for i, testCase := range testCases {
+ // Generate a random object name
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Secured object
+ sse := encrypt.DefaultPBKDF([]byte(password), []byte(bucketName+objectName))
+ args["sse"] = sse
+
+ // Generate a random file name.
+ fileName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ file, err := os.Create(fileName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "file create failed", err)
+ return
+ }
+ _, err = file.Write(testCase.buf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "file write failed", err)
+ return
+ }
+ file.Close()
+ // Put encrypted data
+ if _, err = c.FPutObject(context.Background(), bucketName, objectName, fileName, minio.PutObjectOptions{ServerSideEncryption: sse}); err != nil {
+ logError(testName, function, args, startTime, "", "FPutEncryptedObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{ServerSideEncryption: sse})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetEncryptedObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ // Compare the sent object with the received one
+ recvBuffer := bytes.NewBuffer([]byte{})
+ if _, err = io.Copy(recvBuffer, r); err != nil {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", error: "+err.Error(), err)
+ return
+ }
+ if recvBuffer.Len() != len(testCase.buf) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Number of bytes of received object does not match, expected "+string(len(testCase.buf))+", got "+string(recvBuffer.Len()), err)
+ return
+ }
+ if !bytes.Equal(testCase.buf, recvBuffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Encrypted sent is not equal to decrypted, expected "+string(testCase.buf)+", got "+string(recvBuffer.Bytes()), err)
+ return
+ }
+
+ os.Remove(fileName)
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// testSSES3EncryptionPutGet tests SSE-S3 encryption
+func testSSES3EncryptionPutGet() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutEncryptedObject(bucketName, objectName, reader, sse)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "sse": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ testCases := []struct {
+ buf []byte
+ }{
+ {buf: bytes.Repeat([]byte("F"), 1)},
+ {buf: bytes.Repeat([]byte("F"), 15)},
+ {buf: bytes.Repeat([]byte("F"), 16)},
+ {buf: bytes.Repeat([]byte("F"), 17)},
+ {buf: bytes.Repeat([]byte("F"), 31)},
+ {buf: bytes.Repeat([]byte("F"), 32)},
+ {buf: bytes.Repeat([]byte("F"), 33)},
+ {buf: bytes.Repeat([]byte("F"), 1024)},
+ {buf: bytes.Repeat([]byte("F"), 1024*2)},
+ {buf: bytes.Repeat([]byte("F"), 1024*1024)},
+ }
+
+ for i, testCase := range testCases {
+ // Generate a random object name
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Secured object
+ sse := encrypt.NewSSE()
+ args["sse"] = sse
+
+ // Put encrypted data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(testCase.buf), int64(len(testCase.buf)), minio.PutObjectOptions{ServerSideEncryption: sse})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutEncryptedObject failed", err)
+ return
+ }
+
+ // Read the data back without any encryption headers
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetEncryptedObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ // Compare the sent object with the received one
+ recvBuffer := bytes.NewBuffer([]byte{})
+ if _, err = io.Copy(recvBuffer, r); err != nil {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", error: "+err.Error(), err)
+ return
+ }
+ if recvBuffer.Len() != len(testCase.buf) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Number of bytes of received object does not match, expected "+string(len(testCase.buf))+", got "+string(recvBuffer.Len()), err)
+ return
+ }
+ if !bytes.Equal(testCase.buf, recvBuffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Encrypted sent is not equal to decrypted, expected "+string(testCase.buf)+", got "+string(recvBuffer.Bytes()), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// TestSSES3EncryptionFPut tests server side encryption
+func testSSES3EncryptionFPut() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutEncryptedObject(bucketName, objectName, filePath, contentType, sse)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "filePath": "",
+ "contentType": "",
+ "sse": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Object custom metadata
+ customContentType := "custom/contenttype"
+ args["metadata"] = customContentType
+
+ testCases := []struct {
+ buf []byte
+ }{
+ {buf: bytes.Repeat([]byte("F"), 0)},
+ {buf: bytes.Repeat([]byte("F"), 1)},
+ {buf: bytes.Repeat([]byte("F"), 15)},
+ {buf: bytes.Repeat([]byte("F"), 16)},
+ {buf: bytes.Repeat([]byte("F"), 17)},
+ {buf: bytes.Repeat([]byte("F"), 31)},
+ {buf: bytes.Repeat([]byte("F"), 32)},
+ {buf: bytes.Repeat([]byte("F"), 33)},
+ {buf: bytes.Repeat([]byte("F"), 1024)},
+ {buf: bytes.Repeat([]byte("F"), 1024*2)},
+ {buf: bytes.Repeat([]byte("F"), 1024*1024)},
+ }
+
+ for i, testCase := range testCases {
+ // Generate a random object name
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Secured object
+ sse := encrypt.NewSSE()
+ args["sse"] = sse
+
+ // Generate a random file name.
+ fileName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ file, err := os.Create(fileName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "file create failed", err)
+ return
+ }
+ _, err = file.Write(testCase.buf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "file write failed", err)
+ return
+ }
+ file.Close()
+ // Put encrypted data
+ if _, err = c.FPutObject(context.Background(), bucketName, objectName, fileName, minio.PutObjectOptions{ServerSideEncryption: sse}); err != nil {
+ logError(testName, function, args, startTime, "", "FPutEncryptedObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetEncryptedObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ // Compare the sent object with the received one
+ recvBuffer := bytes.NewBuffer([]byte{})
+ if _, err = io.Copy(recvBuffer, r); err != nil {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", error: "+err.Error(), err)
+ return
+ }
+ if recvBuffer.Len() != len(testCase.buf) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Number of bytes of received object does not match, expected "+string(len(testCase.buf))+", got "+string(recvBuffer.Len()), err)
+ return
+ }
+ if !bytes.Equal(testCase.buf, recvBuffer.Bytes()) {
+ logError(testName, function, args, startTime, "", "Test "+string(i+1)+", Encrypted sent is not equal to decrypted, expected "+string(testCase.buf)+", got "+string(recvBuffer.Bytes()), err)
+ return
+ }
+
+ os.Remove(fileName)
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testBucketNotification() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "SetBucketNotification(bucketName)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ }
+
+ if os.Getenv("NOTIFY_BUCKET") == "" ||
+ os.Getenv("NOTIFY_SERVICE") == "" ||
+ os.Getenv("NOTIFY_REGION") == "" ||
+ os.Getenv("NOTIFY_ACCOUNTID") == "" ||
+ os.Getenv("NOTIFY_RESOURCE") == "" {
+ ignoredLog(testName, function, args, startTime, "Skipped notification test as it is not configured").Info()
+ return
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable to debug
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ bucketName := os.Getenv("NOTIFY_BUCKET")
+ args["bucketName"] = bucketName
+
+ topicArn := notification.NewArn("aws", os.Getenv("NOTIFY_SERVICE"), os.Getenv("NOTIFY_REGION"), os.Getenv("NOTIFY_ACCOUNTID"), os.Getenv("NOTIFY_RESOURCE"))
+ queueArn := notification.NewArn("aws", "dummy-service", "dummy-region", "dummy-accountid", "dummy-resource")
+
+ topicConfig := notification.NewConfig(topicArn)
+ topicConfig.AddEvents(notification.ObjectCreatedAll, notification.ObjectRemovedAll)
+ topicConfig.AddFilterSuffix("jpg")
+
+ queueConfig := notification.NewConfig(queueArn)
+ queueConfig.AddEvents(notification.ObjectCreatedAll)
+ queueConfig.AddFilterPrefix("photos/")
+
+ config := notification.Configuration{}
+ config.AddTopic(topicConfig)
+
+ // Add the same topicConfig again, should have no effect
+ // because it is duplicated
+ config.AddTopic(topicConfig)
+ if len(config.TopicConfigs) != 1 {
+ logError(testName, function, args, startTime, "", "Duplicate entry added", err)
+ return
+ }
+
+ // Add and remove a queue config
+ config.AddQueue(queueConfig)
+ config.RemoveQueueByArn(queueArn)
+
+ err = c.SetBucketNotification(context.Background(), bucketName, config)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketNotification failed", err)
+ return
+ }
+
+ config, err = c.GetBucketNotification(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketNotification failed", err)
+ return
+ }
+
+ if len(config.TopicConfigs) != 1 {
+ logError(testName, function, args, startTime, "", "Topic config is empty", err)
+ return
+ }
+
+ if config.TopicConfigs[0].Filter.S3Key.FilterRules[0].Value != "jpg" {
+ logError(testName, function, args, startTime, "", "Couldn't get the suffix", err)
+ return
+ }
+
+ err = c.RemoveAllBucketNotification(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveAllBucketNotification failed", err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests comprehensive list of all methods.
+func testFunctional() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "testFunctional()"
+ functionAll := ""
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, nil, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable to debug
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ // Make a new bucket.
+ function = "MakeBucket(bucketName, region)"
+ functionAll = "MakeBucket(bucketName, region)"
+ args["bucketName"] = bucketName
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+
+ defer cleanupBucket(bucketName, c)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ // Generate a random file name.
+ fileName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ file, err := os.Create(fileName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File creation failed", err)
+ return
+ }
+ for i := 0; i < 3; i++ {
+ buf := make([]byte, rand.Intn(1<<19))
+ _, err = file.Write(buf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File write failed", err)
+ return
+ }
+ }
+ file.Close()
+
+ // Verify if bucket exits and you have access.
+ var exists bool
+ function = "BucketExists(bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+ exists, err = c.BucketExists(context.Background(), bucketName)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "BucketExists failed", err)
+ return
+ }
+ if !exists {
+ logError(testName, function, args, startTime, "", "Could not find the bucket", err)
+ return
+ }
+
+ // Asserting the default bucket policy.
+ function = "GetBucketPolicy(ctx, bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+ nilPolicy, err := c.GetBucketPolicy(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketPolicy failed", err)
+ return
+ }
+ if nilPolicy != "" {
+ logError(testName, function, args, startTime, "", "policy should be set to nil", err)
+ return
+ }
+
+ // Set the bucket policy to 'public readonly'.
+ function = "SetBucketPolicy(bucketName, readOnlyPolicy)"
+ functionAll += ", " + function
+
+ readOnlyPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:ListBucket"],"Resource":["arn:aws:s3:::` + bucketName + `"]}]}`
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "bucketPolicy": readOnlyPolicy,
+ }
+
+ err = c.SetBucketPolicy(context.Background(), bucketName, readOnlyPolicy)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
+ return
+ }
+ // should return policy `readonly`.
+ function = "GetBucketPolicy(ctx, bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+ _, err = c.GetBucketPolicy(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketPolicy failed", err)
+ return
+ }
+
+ // Make the bucket 'public writeonly'.
+ function = "SetBucketPolicy(bucketName, writeOnlyPolicy)"
+ functionAll += ", " + function
+
+ writeOnlyPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:ListBucketMultipartUploads"],"Resource":["arn:aws:s3:::` + bucketName + `"]}]}`
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "bucketPolicy": writeOnlyPolicy,
+ }
+ err = c.SetBucketPolicy(context.Background(), bucketName, writeOnlyPolicy)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
+ return
+ }
+ // should return policy `writeonly`.
+ function = "GetBucketPolicy(ctx, bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+
+ _, err = c.GetBucketPolicy(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketPolicy failed", err)
+ return
+ }
+
+ // Make the bucket 'public read/write'.
+ function = "SetBucketPolicy(bucketName, readWritePolicy)"
+ functionAll += ", " + function
+
+ readWritePolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:ListBucket","s3:ListBucketMultipartUploads"],"Resource":["arn:aws:s3:::` + bucketName + `"]}]}`
+
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "bucketPolicy": readWritePolicy,
+ }
+ err = c.SetBucketPolicy(context.Background(), bucketName, readWritePolicy)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
+ return
+ }
+ // should return policy `readwrite`.
+ function = "GetBucketPolicy(bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+ _, err = c.GetBucketPolicy(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetBucketPolicy failed", err)
+ return
+ }
+
+ // List all buckets.
+ function = "ListBuckets()"
+ functionAll += ", " + function
+ args = nil
+ buckets, err := c.ListBuckets(context.Background())
+
+ if len(buckets) == 0 {
+ logError(testName, function, args, startTime, "", "Found bucket list to be empty", err)
+ return
+ }
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ListBuckets failed", err)
+ return
+ }
+
+ // Verify if previously created bucket is listed in list buckets.
+ bucketFound := false
+ for _, bucket := range buckets {
+ if bucket.Name == bucketName {
+ bucketFound = true
+ }
+ }
+
+ // If bucket not found error out.
+ if !bucketFound {
+ logError(testName, function, args, startTime, "", "Bucket: "+bucketName+" not found", err)
+ return
+ }
+
+ objectName := bucketName + "unique"
+
+ // Generate data
+ buf := bytes.Repeat([]byte("f"), 1<<19)
+
+ function = "PutObject(bucketName, objectName, reader, contentType)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "contentType": "",
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName + "-nolength",
+ "contentType": "binary/octet-stream",
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName+"-nolength", bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Instantiate a done channel to close all listing.
+ doneCh := make(chan struct{})
+ defer close(doneCh)
+
+ objFound := false
+ isRecursive := true // Recursive is true.
+
+ function = "ListObjects(bucketName, objectName, isRecursive, doneCh)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "isRecursive": isRecursive,
+ }
+
+ for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Prefix: objectName, Recursive: true}) {
+ if obj.Key == objectName {
+ objFound = true
+ break
+ }
+ }
+ if !objFound {
+ logError(testName, function, args, startTime, "", "Object "+objectName+" not found", err)
+ return
+ }
+
+ objFound = false
+ isRecursive = true // Recursive is true.
+ function = "ListObjects()"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "isRecursive": isRecursive,
+ }
+
+ for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{Prefix: objectName, Recursive: isRecursive}) {
+ if obj.Key == objectName {
+ objFound = true
+ break
+ }
+ }
+ if !objFound {
+ logError(testName, function, args, startTime, "", "Object "+objectName+" not found", err)
+ return
+ }
+
+ incompObjNotFound := true
+
+ function = "ListIncompleteUploads(bucketName, objectName, isRecursive, doneCh)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "isRecursive": isRecursive,
+ }
+
+ for objIncompl := range c.ListIncompleteUploads(context.Background(), bucketName, objectName, isRecursive) {
+ if objIncompl.Key != "" {
+ incompObjNotFound = false
+ break
+ }
+ }
+ if !incompObjNotFound {
+ logError(testName, function, args, startTime, "", "Unexpected dangling incomplete upload found", err)
+ return
+ }
+
+ function = "GetObject(bucketName, objectName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ }
+ newReader, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ newReadBytes, err := ioutil.ReadAll(newReader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ if !bytes.Equal(newReadBytes, buf) {
+ logError(testName, function, args, startTime, "", "GetObject bytes mismatch", err)
+ return
+ }
+ newReader.Close()
+
+ function = "FGetObject(bucketName, objectName, fileName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "fileName": fileName + "-f",
+ }
+ err = c.FGetObject(context.Background(), bucketName, objectName, fileName+"-f", minio.GetObjectOptions{})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FGetObject failed", err)
+ return
+ }
+
+ function = "PresignedHeadObject(bucketName, objectName, expires, reqParams)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": "",
+ "expires": 3600 * time.Second,
+ }
+ if _, err = c.PresignedHeadObject(context.Background(), bucketName, "", 3600*time.Second, nil); err == nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject success", err)
+ return
+ }
+
+ // Generate presigned HEAD object url.
+ function = "PresignedHeadObject(bucketName, objectName, expires, reqParams)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "expires": 3600 * time.Second,
+ }
+ presignedHeadURL, err := c.PresignedHeadObject(context.Background(), bucketName, objectName, 3600*time.Second, nil)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject failed", err)
+ return
+ }
+
+ transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS)))
+ if err != nil {
+ logError(testName, function, args, startTime, "", "DefaultTransport failed", err)
+ return
+ }
+
+ httpClient := &http.Client{
+ // Setting a sensible time out of 30secs to wait for response
+ // headers. Request is pro-actively canceled after 30secs
+ // with no response.
+ Timeout: 30 * time.Second,
+ Transport: transport,
+ }
+
+ req, err := http.NewRequest(http.MethodHead, presignedHeadURL.String(), nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject request was incorrect", err)
+ return
+ }
+
+ // Verify if presigned url works.
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject response incorrect", err)
+ return
+ }
+ if resp.StatusCode != http.StatusOK {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject response incorrect, status "+string(resp.StatusCode), err)
+ return
+ }
+ if resp.Header.Get("ETag") == "" {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject response incorrect", err)
+ return
+ }
+ resp.Body.Close()
+
+ function = "PresignedGetObject(bucketName, objectName, expires, reqParams)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": "",
+ "expires": 3600 * time.Second,
+ }
+ _, err = c.PresignedGetObject(context.Background(), bucketName, "", 3600*time.Second, nil)
+ if err == nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject success", err)
+ return
+ }
+
+ // Generate presigned GET object url.
+ function = "PresignedGetObject(bucketName, objectName, expires, reqParams)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "expires": 3600 * time.Second,
+ }
+ presignedGetURL, err := c.PresignedGetObject(context.Background(), bucketName, objectName, 3600*time.Second, nil)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject failed", err)
+ return
+ }
+
+ // Verify if presigned url works.
+ req, err = http.NewRequest(http.MethodGet, presignedGetURL.String(), nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject request incorrect", err)
+ return
+ }
+
+ resp, err = httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+ if resp.StatusCode != http.StatusOK {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect, status "+string(resp.StatusCode), err)
+ return
+ }
+ newPresignedBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+ resp.Body.Close()
+ if !bytes.Equal(newPresignedBytes, buf) {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+
+ // Set request parameters.
+ reqParams := make(url.Values)
+ reqParams.Set("response-content-disposition", "attachment; filename=\"test.txt\"")
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "expires": 3600 * time.Second,
+ "reqParams": reqParams,
+ }
+ presignedGetURL, err = c.PresignedGetObject(context.Background(), bucketName, objectName, 3600*time.Second, reqParams)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject failed", err)
+ return
+ }
+
+ // Verify if presigned url works.
+ req, err = http.NewRequest(http.MethodGet, presignedGetURL.String(), nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject request incorrect", err)
+ return
+ }
+
+ resp, err = httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+ if resp.StatusCode != http.StatusOK {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect, status "+string(resp.StatusCode), err)
+ return
+ }
+ newPresignedBytes, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+ if !bytes.Equal(newPresignedBytes, buf) {
+ logError(testName, function, args, startTime, "", "Bytes mismatch for presigned GET URL", err)
+ return
+ }
+ if resp.Header.Get("Content-Disposition") != "attachment; filename=\"test.txt\"" {
+ logError(testName, function, args, startTime, "", "wrong Content-Disposition received "+string(resp.Header.Get("Content-Disposition")), err)
+ return
+ }
+
+ function = "PresignedPutObject(bucketName, objectName, expires)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": "",
+ "expires": 3600 * time.Second,
+ }
+ _, err = c.PresignedPutObject(context.Background(), bucketName, "", 3600*time.Second)
+ if err == nil {
+ logError(testName, function, args, startTime, "", "PresignedPutObject success", err)
+ return
+ }
+
+ function = "PresignedPutObject(bucketName, objectName, expires)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName + "-presigned",
+ "expires": 3600 * time.Second,
+ }
+ presignedPutURL, err := c.PresignedPutObject(context.Background(), bucketName, objectName+"-presigned", 3600*time.Second)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedPutObject failed", err)
+ return
+ }
+
+ buf = bytes.Repeat([]byte("g"), 1<<19)
+
+ req, err = http.NewRequest(http.MethodPut, presignedPutURL.String(), bytes.NewReader(buf))
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Couldn't make HTTP request with PresignedPutObject URL", err)
+ return
+ }
+
+ resp, err = httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedPutObject failed", err)
+ return
+ }
+
+ newReader, err = c.GetObject(context.Background(), bucketName, objectName+"-presigned", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject after PresignedPutObject failed", err)
+ return
+ }
+
+ newReadBytes, err = ioutil.ReadAll(newReader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll after GetObject failed", err)
+ return
+ }
+
+ if !bytes.Equal(newReadBytes, buf) {
+ logError(testName, function, args, startTime, "", "Bytes mismatch", err)
+ return
+ }
+
+ function = "RemoveObject(bucketName, objectName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ }
+ err = c.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveObject failed", err)
+ return
+ }
+ args["objectName"] = objectName + "-f"
+ err = c.RemoveObject(context.Background(), bucketName, objectName+"-f", minio.RemoveObjectOptions{})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveObject failed", err)
+ return
+ }
+
+ args["objectName"] = objectName + "-nolength"
+ err = c.RemoveObject(context.Background(), bucketName, objectName+"-nolength", minio.RemoveObjectOptions{})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveObject failed", err)
+ return
+ }
+
+ args["objectName"] = objectName + "-presigned"
+ err = c.RemoveObject(context.Background(), bucketName, objectName+"-presigned", minio.RemoveObjectOptions{})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveObject failed", err)
+ return
+ }
+
+ function = "RemoveBucket(bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+ err = c.RemoveBucket(context.Background(), bucketName)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "RemoveBucket failed", err)
+ return
+ }
+ err = c.RemoveBucket(context.Background(), bucketName)
+ if err == nil {
+ logError(testName, function, args, startTime, "", "RemoveBucket did not fail for invalid bucket name", err)
+ return
+ }
+ if err.Error() != "The specified bucket does not exist" {
+ logError(testName, function, args, startTime, "", "RemoveBucket failed", err)
+ return
+ }
+
+ os.Remove(fileName)
+ os.Remove(fileName + "-f")
+ successLogger(testName, functionAll, args, startTime).Info()
+}
+
+// Test for validating GetObject Reader* methods functioning when the
+// object is modified in the object store.
+func testGetObjectModified() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Make a new bucket.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload an object.
+ objectName := "myobject"
+ args["objectName"] = objectName
+ content := "helloworld"
+ _, err = c.PutObject(context.Background(), bucketName, objectName, strings.NewReader(content), int64(len(content)), minio.PutObjectOptions{ContentType: "application/text"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Failed to upload "+objectName+", to bucket "+bucketName, err)
+ return
+ }
+
+ defer c.RemoveObject(context.Background(), bucketName, objectName, minio.RemoveObjectOptions{})
+
+ reader, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Failed to GetObject "+objectName+", from bucket "+bucketName, err)
+ return
+ }
+ defer reader.Close()
+
+ // Read a few bytes of the object.
+ b := make([]byte, 5)
+ n, err := reader.ReadAt(b, 0)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Failed to read object "+objectName+", from bucket "+bucketName+" at an offset", err)
+ return
+ }
+
+ // Upload different contents to the same object while object is being read.
+ newContent := "goodbyeworld"
+ _, err = c.PutObject(context.Background(), bucketName, objectName, strings.NewReader(newContent), int64(len(newContent)), minio.PutObjectOptions{ContentType: "application/text"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Failed to upload "+objectName+", to bucket "+bucketName, err)
+ return
+ }
+
+ // Confirm that a Stat() call in between doesn't change the Object's cached etag.
+ _, err = reader.Stat()
+ expectedError := "At least one of the pre-conditions you specified did not hold"
+ if err.Error() != expectedError {
+ logError(testName, function, args, startTime, "", "Expected Stat to fail with error "+expectedError+", but received "+err.Error(), err)
+ return
+ }
+
+ // Read again only to find object contents have been modified since last read.
+ _, err = reader.ReadAt(b, int64(n))
+ if err.Error() != expectedError {
+ logError(testName, function, args, startTime, "", "Expected ReadAt to fail with error "+expectedError+", but received "+err.Error(), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test validates putObject to upload a file seeked at a given offset.
+func testPutObjectUploadSeekedObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, fileToUpload, contentType)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "fileToUpload": "",
+ "contentType": "binary/octet-stream",
+ }
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Make a new bucket.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, c)
+
+ var tempfile *os.File
+
+ if fileName := getMintDataDirFilePath("datafile-100-kB"); fileName != "" {
+ tempfile, err = os.Open(fileName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File open failed", err)
+ return
+ }
+ args["fileToUpload"] = fileName
+ } else {
+ tempfile, err = ioutil.TempFile("", "minio-go-upload-test-")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "TempFile create failed", err)
+ return
+ }
+ args["fileToUpload"] = tempfile.Name()
+
+ // Generate 100kB data
+ if _, err = io.Copy(tempfile, getDataReader("datafile-100-kB")); err != nil {
+ logError(testName, function, args, startTime, "", "File copy failed", err)
+ return
+ }
+
+ defer os.Remove(tempfile.Name())
+
+ // Seek back to the beginning of the file.
+ tempfile.Seek(0, 0)
+ }
+ var length = 100 * humanize.KiByte
+ objectName := fmt.Sprintf("test-file-%v", rand.Uint32())
+ args["objectName"] = objectName
+
+ offset := length / 2
+ if _, err = tempfile.Seek(int64(offset), 0); err != nil {
+ logError(testName, function, args, startTime, "", "TempFile seek failed", err)
+ return
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, tempfile, int64(length-offset), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ tempfile.Close()
+
+ obj, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer obj.Close()
+
+ n, err := obj.Seek(int64(offset), 0)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Seek failed", err)
+ return
+ }
+ if n != int64(offset) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Invalid offset returned, expected %d got %d", int64(offset), n), err)
+ return
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName+"getobject", obj, int64(length-offset), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ st, err := c.StatObject(context.Background(), bucketName, objectName+"getobject", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if st.Size != int64(length-offset) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Invalid offset returned, expected %d got %d", int64(length-offset), n), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests bucket re-create errors.
+func testMakeBucketErrorV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "MakeBucket(bucketName, region)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "region": "eu-west-1",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ region := "eu-west-1"
+ args["bucketName"] = bucketName
+ args["region"] = region
+
+ // Make a new bucket in 'eu-west-1'.
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err == nil {
+ logError(testName, function, args, startTime, "", "MakeBucket did not fail for existing bucket name", err)
+ return
+ }
+ // Verify valid error response from server.
+ if minio.ToErrorResponse(err).Code != "BucketAlreadyExists" &&
+ minio.ToErrorResponse(err).Code != "BucketAlreadyOwnedByYou" {
+ logError(testName, function, args, startTime, "", "Invalid error returned by server", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test get object reader to not throw error on being closed twice.
+func testGetObjectClosedTwiceV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "MakeBucket(bucketName, region)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "region": "eu-west-1",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(bufSize)+" got "+string(st.Size), err)
+ return
+ }
+ if err := r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ if err := r.Close(); err == nil {
+ logError(testName, function, args, startTime, "", "Object is already closed, should return error", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests FPutObject hidden contentType setting
+func testFPutObjectV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FPutObject(bucketName, objectName, fileName, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Make a temp file with 11*1024*1024 bytes of data.
+ file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "TempFile creation failed", err)
+ return
+ }
+
+ r := bytes.NewReader(bytes.Repeat([]byte("b"), 11*1024*1024))
+ n, err := io.CopyN(file, r, 11*1024*1024)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Copy failed", err)
+ return
+ }
+ if n != int64(11*1024*1024) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(11*1024*1024))+" got "+string(n), err)
+ return
+ }
+
+ // Close the file pro-actively for windows.
+ err = file.Close()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "File close failed", err)
+ return
+ }
+
+ // Set base object name
+ objectName := bucketName + "FPutObject"
+ args["objectName"] = objectName
+ args["fileName"] = file.Name()
+
+ // Perform standard FPutObject with contentType provided (Expecting application/octet-stream)
+ _, err = c.FPutObject(context.Background(), bucketName, objectName+"-standard", file.Name(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject failed", err)
+ return
+ }
+
+ // Perform FPutObject with no contentType provided (Expecting application/octet-stream)
+ args["objectName"] = objectName + "-Octet"
+ args["contentType"] = ""
+
+ _, err = c.FPutObject(context.Background(), bucketName, objectName+"-Octet", file.Name(), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject failed", err)
+ return
+ }
+
+ // Add extension to temp file name
+ fileName := file.Name()
+ err = os.Rename(fileName, fileName+".gtar")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Rename failed", err)
+ return
+ }
+
+ // Perform FPutObject with no contentType provided (Expecting application/x-gtar)
+ args["objectName"] = objectName + "-Octet"
+ args["contentType"] = ""
+ args["fileName"] = fileName + ".gtar"
+
+ _, err = c.FPutObject(context.Background(), bucketName, objectName+"-GTar", fileName+".gtar", minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FPutObject failed", err)
+ return
+ }
+
+ // Check headers and sizes
+ rStandard, err := c.StatObject(context.Background(), bucketName, objectName+"-standard", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ if rStandard.Size != 11*1024*1024 {
+ logError(testName, function, args, startTime, "", "Unexpected size", nil)
+ return
+ }
+
+ if rStandard.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "Content-Type headers mismatched, expected: application/octet-stream , got "+rStandard.ContentType, err)
+ return
+ }
+
+ rOctet, err := c.StatObject(context.Background(), bucketName, objectName+"-Octet", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if rOctet.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "Content-Type headers mismatched, expected: application/octet-stream , got "+rOctet.ContentType, err)
+ return
+ }
+
+ if rOctet.Size != 11*1024*1024 {
+ logError(testName, function, args, startTime, "", "Unexpected size", nil)
+ return
+ }
+
+ rGTar, err := c.StatObject(context.Background(), bucketName, objectName+"-GTar", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if rGTar.Size != 11*1024*1024 {
+ logError(testName, function, args, startTime, "", "Unexpected size", nil)
+ return
+ }
+ if rGTar.ContentType != "application/x-gtar" && rGTar.ContentType != "application/octet-stream" {
+ logError(testName, function, args, startTime, "", "Content-Type headers mismatched, expected: application/x-gtar , got "+rGTar.ContentType, err)
+ return
+ }
+
+ os.Remove(fileName + ".gtar")
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests various bucket supported formats.
+func testMakeBucketRegionsV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "MakeBucket(bucketName, region)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "region": "eu-west-1",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket in 'eu-central-1'.
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "eu-west-1"}); err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed while removing bucket recursively", err)
+ return
+ }
+
+ // Make a new bucket with '.' in its name, in 'us-west-2'. This
+ // request is internally staged into a path style instead of
+ // virtual host style.
+ if err = c.MakeBucket(context.Background(), bucketName+".withperiod", minio.MakeBucketOptions{Region: "us-west-2"}); err != nil {
+ args["bucketName"] = bucketName + ".withperiod"
+ args["region"] = "us-west-2"
+ logError(testName, function, args, startTime, "", "MakeBucket test with a bucket name with period, '.', failed", err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupBucket(bucketName+".withperiod", c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed while removing bucket recursively", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests get object ReaderSeeker interface methods.
+func testGetObjectReadSeekFunctionalV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data.
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+" got "+string(st.Size), err)
+ return
+ }
+
+ offset := int64(2048)
+ n, err := r.Seek(offset, 0)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Seek failed", err)
+ return
+ }
+ if n != offset {
+ logError(testName, function, args, startTime, "", "Number of seeked bytes does not match, expected "+string(offset)+" got "+string(n), err)
+ return
+ }
+ n, err = r.Seek(0, 1)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Seek failed", err)
+ return
+ }
+ if n != offset {
+ logError(testName, function, args, startTime, "", "Number of seeked bytes does not match, expected "+string(offset)+" got "+string(n), err)
+ return
+ }
+ _, err = r.Seek(offset, 2)
+ if err == nil {
+ logError(testName, function, args, startTime, "", "Seek on positive offset for whence '2' should error out", err)
+ return
+ }
+ n, err = r.Seek(-offset, 2)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Seek failed", err)
+ return
+ }
+ if n != st.Size-offset {
+ logError(testName, function, args, startTime, "", "Number of seeked bytes does not match, expected "+string(st.Size-offset)+" got "+string(n), err)
+ return
+ }
+
+ var buffer1 bytes.Buffer
+ if _, err = io.CopyN(&buffer1, r, st.Size); err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "Copy failed", err)
+ return
+ }
+ }
+ if !bytes.Equal(buf[len(buf)-int(offset):], buffer1.Bytes()) {
+ logError(testName, function, args, startTime, "", "Incorrect read bytes v/s original buffer", err)
+ return
+ }
+
+ // Seek again and read again.
+ n, err = r.Seek(offset-1, 0)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Seek failed", err)
+ return
+ }
+ if n != (offset - 1) {
+ logError(testName, function, args, startTime, "", "Number of seeked bytes does not match, expected "+string(offset-1)+" got "+string(n), err)
+ return
+ }
+
+ var buffer2 bytes.Buffer
+ if _, err = io.CopyN(&buffer2, r, st.Size); err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "Copy failed", err)
+ return
+ }
+ }
+ // Verify now lesser bytes.
+ if !bytes.Equal(buf[2047:], buffer2.Bytes()) {
+ logError(testName, function, args, startTime, "", "Incorrect read bytes v/s original buffer", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests get object ReaderAt interface methods.
+func testGetObjectReadAtFunctionalV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(bucketName, objectName)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ buf, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+
+ // Save the data
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer r.Close()
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(bufSize)+" got "+string(st.Size), err)
+ return
+ }
+
+ offset := int64(2048)
+
+ // Read directly
+ buf2 := make([]byte, 512)
+ buf3 := make([]byte, 512)
+ buf4 := make([]byte, 512)
+
+ m, err := r.ReadAt(buf2, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf2) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+" got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf2, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf3, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf3) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf3))+" got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf3, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+ offset += 512
+ m, err = r.ReadAt(buf4, offset)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ if m != len(buf4) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf4))+" got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf4, buf[offset:offset+512]) {
+ logError(testName, function, args, startTime, "", "Incorrect read between two ReadAt from same offset", err)
+ return
+ }
+
+ buf5 := make([]byte, bufSize)
+ // Read the whole object.
+ m, err = r.ReadAt(buf5, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+ if m != len(buf5) {
+ logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf5))+" got "+string(m), err)
+ return
+ }
+ if !bytes.Equal(buf, buf5) {
+ logError(testName, function, args, startTime, "", "Incorrect data read in GetObject, than what was previously uploaded", err)
+ return
+ }
+
+ buf6 := make([]byte, bufSize+1)
+ // Read the whole object and beyond.
+ _, err = r.ReadAt(buf6, 0)
+ if err != nil {
+ if err != io.EOF {
+ logError(testName, function, args, startTime, "", "ReadAt failed", err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Tests copy object
+func testCopyObjectV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, c)
+
+ // Make a new bucket in 'us-east-1' (destination bucket).
+ err = c.MakeBucket(context.Background(), bucketName+"-copy", minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName+"-copy", c)
+
+ // Generate 33K of data.
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ r, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ // Check the various fields of source object against destination object.
+ objInfo, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ r.Close()
+
+ // Copy Source
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ MatchModifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+ MatchETag: objInfo.ETag,
+ }
+ args["source"] = src
+
+ // Set copy conditions.
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName + "-copy",
+ Object: objectName + "-copy",
+ }
+ args["destination"] = dst
+
+ // Perform the Copy
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ // Source object
+ r, err = c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ // Destination object
+ readerCopy, err := c.GetObject(context.Background(), bucketName+"-copy", objectName+"-copy", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ // Check the various fields of source object against destination object.
+ objInfo, err = r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ objInfoCopy, err := readerCopy.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ if objInfo.Size != objInfoCopy.Size {
+ logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(objInfoCopy.Size)+" got "+string(objInfo.Size), err)
+ return
+ }
+
+ // Close all the readers.
+ r.Close()
+ readerCopy.Close()
+
+ // CopyObject again but with wrong conditions
+ src = minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ MatchUnmodifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+ NoMatchETag: objInfo.ETag,
+ }
+
+ // Perform the Copy which should fail
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err == nil {
+ logError(testName, function, args, startTime, "", "CopyObject did not fail for invalid conditions", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testComposeObjectErrorCasesWrapper(c *minio.Client) {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject(destination, sourceList)"
+ args := map[string]interface{}{}
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err := c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Test that more than 10K source objects cannot be
+ // concatenated.
+ srcArr := [10001]minio.CopySrcOptions{}
+ srcSlice := srcArr[:]
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "object",
+ }
+
+ args["destination"] = dst
+ // Just explain about srcArr in args["sourceList"]
+ // to stop having 10,001 null headers logged
+ args["sourceList"] = "source array of 10,001 elements"
+ if _, err := c.ComposeObject(context.Background(), dst, srcSlice...); err == nil {
+ logError(testName, function, args, startTime, "", "Expected error in ComposeObject", err)
+ return
+ } else if err.Error() != "There must be as least one and up to 10000 source objects." {
+ logError(testName, function, args, startTime, "", "Got unexpected error", err)
+ return
+ }
+
+ // Create a source with invalid offset spec and check that
+ // error is returned:
+ // 1. Create the source object.
+ const badSrcSize = 5 * 1024 * 1024
+ buf := bytes.Repeat([]byte("1"), badSrcSize)
+ _, err = c.PutObject(context.Background(), bucketName, "badObject", bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ // 2. Set invalid range spec on the object (going beyond
+ // object size)
+ badSrc := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "badObject",
+ MatchRange: true,
+ Start: 1,
+ End: badSrcSize,
+ }
+
+ // 3. ComposeObject call should fail.
+ if _, err := c.ComposeObject(context.Background(), dst, badSrc); err == nil {
+ logError(testName, function, args, startTime, "", "ComposeObject expected to fail", err)
+ return
+ } else if !strings.Contains(err.Error(), "has invalid segment-to-copy") {
+ logError(testName, function, args, startTime, "", "Got invalid error", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test expected error cases
+func testComposeObjectErrorCasesV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject(destination, sourceList)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ testComposeObjectErrorCasesWrapper(c)
+}
+
+func testComposeMultipleSources(c *minio.Client) {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject(destination, sourceList)"
+ args := map[string]interface{}{
+ "destination": "",
+ "sourceList": "",
+ }
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err := c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Upload a small source object
+ const srcSize = 1024 * 1024 * 5
+ buf := bytes.Repeat([]byte("1"), srcSize)
+ _, err = c.PutObject(context.Background(), bucketName, "srcObject", bytes.NewReader(buf), int64(srcSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // We will append 10 copies of the object.
+ srcs := []minio.CopySrcOptions{}
+ for i := 0; i < 10; i++ {
+ srcs = append(srcs, minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "srcObject",
+ })
+ }
+
+ // make the last part very small
+ srcs[9].MatchRange = true
+
+ args["sourceList"] = srcs
+
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "dstObject",
+ }
+ args["destination"] = dst
+
+ ui, err := c.ComposeObject(context.Background(), dst, srcs...)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ComposeObject failed", err)
+ return
+ }
+
+ if ui.Size != 9*srcSize+1 {
+ logError(testName, function, args, startTime, "", "ComposeObject returned unexpected size", err)
+ return
+ }
+
+ objProps, err := c.StatObject(context.Background(), bucketName, "dstObject", minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ if objProps.Size != 9*srcSize+1 {
+ logError(testName, function, args, startTime, "", "Size mismatched! Expected "+string(10000*srcSize)+" got "+string(objProps.Size), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test concatenating multiple 10K objects V2
+func testCompose10KSourcesV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject(destination, sourceList)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ testComposeMultipleSources(c)
+}
+
+func testEncryptedEmptyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader, objectSize, opts)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ sse := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"object"))
+
+ // 1. create an sse-c encrypted object to copy by uploading
+ const srcSize = 0
+ var buf []byte // Empty buffer
+ args["objectName"] = "object"
+ _, err = c.PutObject(context.Background(), bucketName, "object", bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ServerSideEncryption: sse})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ // 2. Test CopyObject for an empty object
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "object",
+ Encryption: sse,
+ }
+
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "new-object",
+ Encryption: sse,
+ }
+
+ if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+ function = "CopyObject(dst, src)"
+ logError(testName, function, map[string]interface{}{}, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ // 3. Test Key rotation
+ newSSE := encrypt.DefaultPBKDF([]byte("Don't Panic"), []byte(bucketName+"new-object"))
+ src = minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "new-object",
+ Encryption: sse,
+ }
+
+ dst = minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "new-object",
+ Encryption: newSSE,
+ }
+
+ if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+ function = "CopyObject(dst, src)"
+ logError(testName, function, map[string]interface{}{}, startTime, "", "CopyObject with key rotation failed", err)
+ return
+ }
+
+ // 4. Download the object.
+ reader, err := c.GetObject(context.Background(), bucketName, "new-object", minio.GetObjectOptions{ServerSideEncryption: newSSE})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer reader.Close()
+
+ decBytes, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, map[string]interface{}{}, startTime, "", "ReadAll failed", err)
+ return
+ }
+ if !bytes.Equal(decBytes, buf) {
+ logError(testName, function, map[string]interface{}{}, startTime, "", "Downloaded object doesn't match the empty encrypted object", err)
+ return
+ }
+
+ delete(args, "objectName")
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testEncryptedCopyObjectWrapper(c *minio.Client, bucketName string, sseSrc, sseDst encrypt.ServerSide) {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncNameLoc(2)
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+ var srcEncryption, dstEncryption encrypt.ServerSide
+
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err := c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // 1. create an sse-c encrypted object to copy by uploading
+ const srcSize = 1024 * 1024
+ buf := bytes.Repeat([]byte("abcde"), srcSize) // gives a buffer of 5MiB
+ _, err = c.PutObject(context.Background(), bucketName, "srcObject", bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{
+ ServerSideEncryption: sseSrc,
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ if sseSrc != nil && sseSrc.Type() != encrypt.S3 {
+ srcEncryption = sseSrc
+ }
+
+ // 2. copy object and change encryption key
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "srcObject",
+ Encryption: srcEncryption,
+ }
+ args["source"] = src
+
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "dstObject",
+ Encryption: sseDst,
+ }
+ args["destination"] = dst
+
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ if sseDst != nil && sseDst.Type() != encrypt.S3 {
+ dstEncryption = sseDst
+ }
+ // 3. get copied object and check if content is equal
+ coreClient := minio.Core{c}
+ reader, _, _, err := coreClient.GetObject(context.Background(), bucketName, "dstObject", minio.GetObjectOptions{ServerSideEncryption: dstEncryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ decBytes, err := ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ if !bytes.Equal(decBytes, buf) {
+ logError(testName, function, args, startTime, "", "Downloaded object mismatched for encrypted object", err)
+ return
+ }
+ reader.Close()
+
+ // Test key rotation for source object in-place.
+ var newSSE encrypt.ServerSide
+ if sseSrc != nil && sseSrc.Type() == encrypt.SSEC {
+ newSSE = encrypt.DefaultPBKDF([]byte("Don't Panic"), []byte(bucketName+"srcObject")) // replace key
+ }
+ if sseSrc != nil && sseSrc.Type() == encrypt.S3 {
+ newSSE = encrypt.NewSSE()
+ }
+ if newSSE != nil {
+ dst = minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "srcObject",
+ Encryption: newSSE,
+ }
+ args["destination"] = dst
+
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ // Get copied object and check if content is equal
+ reader, _, _, err = coreClient.GetObject(context.Background(), bucketName, "srcObject", minio.GetObjectOptions{ServerSideEncryption: newSSE})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ decBytes, err = ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ if !bytes.Equal(decBytes, buf) {
+ logError(testName, function, args, startTime, "", "Downloaded object mismatched for encrypted object", err)
+ return
+ }
+ reader.Close()
+
+ // Test in-place decryption.
+ dst = minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "srcObject",
+ }
+ args["destination"] = dst
+
+ src = minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "srcObject",
+ Encryption: newSSE,
+ }
+ args["source"] = src
+ _, err = c.CopyObject(context.Background(), dst, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject Key rotation failed", err)
+ return
+ }
+ }
+
+ // Get copied decrypted object and check if content is equal
+ reader, _, _, err = coreClient.GetObject(context.Background(), bucketName, "srcObject", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ defer reader.Close()
+
+ decBytes, err = ioutil.ReadAll(reader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ if !bytes.Equal(decBytes, buf) {
+ logError(testName, function, args, startTime, "", "Downloaded object mismatched for encrypted object", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test encrypted copy object
+func testUnencryptedToSSECCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject"))
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, nil, sseDst)
+}
+
+// Test encrypted copy object
+func testUnencryptedToSSES3CopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ var sseSrc encrypt.ServerSide
+ sseDst := encrypt.NewSSE()
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testUnencryptedToUnencryptedCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ var sseSrc, sseDst encrypt.ServerSide
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedSSECToSSECCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject"))
+ sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject"))
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedSSECToSSES3CopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject"))
+ sseDst := encrypt.NewSSE()
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedSSECToUnencryptedCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject"))
+ var sseDst encrypt.ServerSide
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedSSES3ToSSECCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.NewSSE()
+ sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject"))
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedSSES3ToSSES3CopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.NewSSE()
+ sseDst := encrypt.NewSSE()
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedSSES3ToUnencryptedCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.NewSSE()
+ var sseDst encrypt.ServerSide
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+// Test encrypted copy object
+func testEncryptedCopyObjectV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+
+ sseSrc := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"srcObject"))
+ sseDst := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+"dstObject"))
+ // c.TraceOn(os.Stderr)
+ testEncryptedCopyObjectWrapper(c, bucketName, sseSrc, sseDst)
+}
+
+func testDecryptedCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v2 client object creation failed", err)
+ return
+ }
+
+ bucketName, objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-"), "object"
+ if err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"}); err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ encryption := encrypt.DefaultPBKDF([]byte("correct horse battery staple"), []byte(bucketName+objectName))
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(bytes.Repeat([]byte("a"), 1024*1024)), 1024*1024, minio.PutObjectOptions{
+ ServerSideEncryption: encryption,
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: objectName,
+ Encryption: encrypt.SSECopy(encryption),
+ }
+ args["source"] = src
+
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "decrypted-" + objectName,
+ }
+ args["destination"] = dst
+
+ if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+ if _, err = c.GetObject(context.Background(), bucketName, "decrypted-"+objectName, minio.GetObjectOptions{}); err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testSSECMultipartEncryptedToSSECCopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 6MB of data
+ buf := bytes.Repeat([]byte("abcdef"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ password := "correct horse battery staple"
+ srcencryption := encrypt.DefaultPBKDF([]byte(password), []byte(bucketName+objectName))
+
+ // Upload a 6MB object using multipart mechanism
+ uploadID, err := c.NewMultipartUpload(context.Background(), bucketName, objectName, minio.PutObjectOptions{ServerSideEncryption: srcencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ var completeParts []minio.CompletePart
+
+ part, err := c.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 1, bytes.NewReader(buf[:5*1024*1024]), 5*1024*1024, "", "", srcencryption)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectPart call failed", err)
+ return
+ }
+ completeParts = append(completeParts, minio.CompletePart{PartNumber: part.PartNumber, ETag: part.ETag})
+
+ part, err = c.PutObjectPart(context.Background(), bucketName, objectName, uploadID, 2, bytes.NewReader(buf[5*1024*1024:]), 1024*1024, "", "", srcencryption)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectPart call failed", err)
+ return
+ }
+ completeParts = append(completeParts, minio.CompletePart{PartNumber: part.PartNumber, ETag: part.ETag})
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), bucketName, objectName, uploadID, completeParts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.DefaultPBKDF([]byte(password), []byte(destBucketName+destObjectName))
+
+ uploadID, err = c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ encrypt.SSECopy(srcencryption).Marshal(header)
+ dstencryption.Marshal(header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = objInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err = c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (6*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{ServerSideEncryption: dstencryption}
+ getOpts.SetRange(0, 6*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 6*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 6MB", err)
+ return
+ }
+
+ getOpts.SetRange(6*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 6*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:6*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 6MB", err)
+ return
+ }
+ if getBuf[6*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation
+func testSSECEncryptedToSSECCopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ password := "correct horse battery staple"
+ srcencryption := encrypt.DefaultPBKDF([]byte(password), []byte(bucketName+objectName))
+ putmetadata := map[string]string{
+ "Content-Type": "binary/octet-stream",
+ }
+ opts := minio.PutObjectOptions{
+ UserMetadata: putmetadata,
+ ServerSideEncryption: srcencryption,
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.DefaultPBKDF([]byte(password), []byte(destBucketName+destObjectName))
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ encrypt.SSECopy(srcencryption).Marshal(header)
+ dstencryption.Marshal(header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{ServerSideEncryption: dstencryption}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for SSEC encrypted to unencrypted copy
+func testSSECEncryptedToUnencryptedCopyPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ password := "correct horse battery staple"
+ srcencryption := encrypt.DefaultPBKDF([]byte(password), []byte(bucketName+objectName))
+
+ opts := minio.PutObjectOptions{
+ UserMetadata: map[string]string{
+ "Content-Type": "binary/octet-stream",
+ },
+ ServerSideEncryption: srcencryption,
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ var dstencryption encrypt.ServerSide
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ encrypt.SSECopy(srcencryption).Marshal(header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for SSEC encrypted to SSE-S3 encrypted copy
+func testSSECEncryptedToSSES3CopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ password := "correct horse battery staple"
+ srcencryption := encrypt.DefaultPBKDF([]byte(password), []byte(bucketName+objectName))
+ putmetadata := map[string]string{
+ "Content-Type": "binary/octet-stream",
+ }
+ opts := minio.PutObjectOptions{
+ UserMetadata: putmetadata,
+ ServerSideEncryption: srcencryption,
+ }
+
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.NewSSE()
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ encrypt.SSECopy(srcencryption).Marshal(header)
+ dstencryption.Marshal(header)
+
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for unencrypted to SSEC encryption copy part
+func testUnencryptedToSSECCopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ password := "correct horse battery staple"
+ putmetadata := map[string]string{
+ "Content-Type": "binary/octet-stream",
+ }
+ opts := minio.PutObjectOptions{
+ UserMetadata: putmetadata,
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.DefaultPBKDF([]byte(password), []byte(destBucketName+destObjectName))
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ dstencryption.Marshal(header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{ServerSideEncryption: dstencryption}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for unencrypted to unencrypted copy
+func testUnencryptedToUnencryptedCopyPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ putmetadata := map[string]string{
+ "Content-Type": "binary/octet-stream",
+ }
+ opts := minio.PutObjectOptions{
+ UserMetadata: putmetadata,
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for unencrypted to SSE-S3 encrypted copy
+func testUnencryptedToSSES3CopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ opts := minio.PutObjectOptions{
+ UserMetadata: map[string]string{
+ "Content-Type": "binary/octet-stream",
+ },
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.NewSSE()
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ dstencryption.Marshal(header)
+
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for SSE-S3 to SSEC encryption copy part
+func testSSES3EncryptedToSSECCopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ password := "correct horse battery staple"
+ srcEncryption := encrypt.NewSSE()
+ opts := minio.PutObjectOptions{
+ UserMetadata: map[string]string{
+ "Content-Type": "binary/octet-stream",
+ },
+ ServerSideEncryption: srcEncryption,
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcEncryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.DefaultPBKDF([]byte(password), []byte(destBucketName+destObjectName))
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ dstencryption.Marshal(header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{ServerSideEncryption: dstencryption}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for unencrypted to unencrypted copy
+func testSSES3EncryptedToUnencryptedCopyPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ srcEncryption := encrypt.NewSSE()
+ opts := minio.PutObjectOptions{
+ UserMetadata: map[string]string{
+ "Content-Type": "binary/octet-stream",
+ },
+ ServerSideEncryption: srcEncryption,
+ }
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcEncryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+
+// Test Core CopyObjectPart implementation for unencrypted to SSE-S3 encrypted copy
+func testSSES3EncryptedToSSES3CopyObjectPart() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObjectPart(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ client, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Instantiate new core client object.
+ c := minio.Core{client}
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, client)
+ // Make a buffer with 5MB of data
+ buf := bytes.Repeat([]byte("abcde"), 1024*1024)
+
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ srcEncryption := encrypt.NewSSE()
+ opts := minio.PutObjectOptions{
+ UserMetadata: map[string]string{
+ "Content-Type": "binary/octet-stream",
+ },
+ ServerSideEncryption: srcEncryption,
+ }
+
+ uploadInfo, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), "", "", opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{ServerSideEncryption: srcEncryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("Error: number of bytes does not match, want %v, got %v\n", len(buf), st.Size), err)
+ return
+ }
+
+ destBucketName := bucketName
+ destObjectName := objectName + "-dest"
+ dstencryption := encrypt.NewSSE()
+
+ uploadID, err := c.NewMultipartUpload(context.Background(), destBucketName, destObjectName, minio.PutObjectOptions{ServerSideEncryption: dstencryption})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "NewMultipartUpload call failed", err)
+ return
+ }
+
+ // Content of the destination object will be two copies of
+ // `objectName` concatenated, followed by first byte of
+ // `objectName`.
+ metadata := make(map[string]string)
+ header := make(http.Header)
+ dstencryption.Marshal(header)
+
+ for k, v := range header {
+ metadata[k] = v[0]
+ }
+
+ metadata["x-amz-copy-source-if-match"] = uploadInfo.ETag
+
+ // First of three parts
+ fstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 1, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Second of three parts
+ sndPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 2, 0, -1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Last of three parts
+ lstPart, err := c.CopyObjectPart(context.Background(), bucketName, objectName, destBucketName, destObjectName, uploadID, 3, 0, 1, metadata)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObjectPart call failed", err)
+ return
+ }
+
+ // Complete the multipart upload
+ _, err = c.CompleteMultipartUpload(context.Background(), destBucketName, destObjectName, uploadID, []minio.CompletePart{fstPart, sndPart, lstPart})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CompleteMultipartUpload call failed", err)
+ return
+ }
+
+ // Stat the object and check its length matches
+ objInfo, err := c.StatObject(context.Background(), destBucketName, destObjectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject call failed", err)
+ return
+ }
+
+ if objInfo.Size != (5*1024*1024)*2+1 {
+ logError(testName, function, args, startTime, "", "Destination object has incorrect size!", err)
+ return
+ }
+
+ // Now we read the data back
+ getOpts := minio.GetObjectOptions{}
+ getOpts.SetRange(0, 5*1024*1024-1)
+ r, _, _, err := c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf := make([]byte, 5*1024*1024)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf, buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in first 5MB", err)
+ return
+ }
+
+ getOpts.SetRange(5*1024*1024, 0)
+ r, _, _, err = c.GetObject(context.Background(), destBucketName, destObjectName, getOpts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject call failed", err)
+ return
+ }
+ getBuf = make([]byte, 5*1024*1024+1)
+ _, err = readFull(r, getBuf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Read buffer failed", err)
+ return
+ }
+ if !bytes.Equal(getBuf[:5*1024*1024], buf) {
+ logError(testName, function, args, startTime, "", "Got unexpected data in second 5MB", err)
+ return
+ }
+ if getBuf[5*1024*1024] != buf[0] {
+ logError(testName, function, args, startTime, "", "Got unexpected data in last byte of copied object!", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+ // Do not need to remove destBucketName its same as bucketName.
+}
+func testUserMetadataCopying() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ // c.TraceOn(os.Stderr)
+ testUserMetadataCopyingWrapper(c)
+}
+
+func testUserMetadataCopyingWrapper(c *minio.Client) {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err := c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ fetchMeta := func(object string) (h http.Header) {
+ objInfo, err := c.StatObject(context.Background(), bucketName, object, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ h = make(http.Header)
+ for k, vs := range objInfo.Metadata {
+ if strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") {
+ h.Add(k, vs[0])
+ }
+ }
+ return h
+ }
+
+ // 1. create a client encrypted object to copy by uploading
+ const srcSize = 1024 * 1024
+ buf := bytes.Repeat([]byte("abcde"), srcSize) // gives a buffer of 5MiB
+ metadata := make(http.Header)
+ metadata.Set("x-amz-meta-myheader", "myvalue")
+ m := make(map[string]string)
+ m["x-amz-meta-myheader"] = "myvalue"
+ _, err = c.PutObject(context.Background(), bucketName, "srcObject",
+ bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{UserMetadata: m})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectWithMetadata failed", err)
+ return
+ }
+ if !reflect.DeepEqual(metadata, fetchMeta("srcObject")) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ // 2. create source
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "srcObject",
+ }
+
+ // 2.1 create destination with metadata set
+ dst1 := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "dstObject-1",
+ UserMetadata: map[string]string{"notmyheader": "notmyvalue"},
+ ReplaceMetadata: true,
+ }
+
+ // 3. Check that copying to an object with metadata set resets
+ // the headers on the copy.
+ args["source"] = src
+ args["destination"] = dst1
+ _, err = c.CopyObject(context.Background(), dst1, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ expectedHeaders := make(http.Header)
+ expectedHeaders.Set("x-amz-meta-notmyheader", "notmyvalue")
+ if !reflect.DeepEqual(expectedHeaders, fetchMeta("dstObject-1")) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ // 4. create destination with no metadata set and same source
+ dst2 := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "dstObject-2",
+ }
+
+ // 5. Check that copying to an object with no metadata set,
+ // copies metadata.
+ args["source"] = src
+ args["destination"] = dst2
+ _, err = c.CopyObject(context.Background(), dst2, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed", err)
+ return
+ }
+
+ expectedHeaders = metadata
+ if !reflect.DeepEqual(expectedHeaders, fetchMeta("dstObject-2")) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ // 6. Compose a pair of sources.
+ dst3 := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "dstObject-3",
+ ReplaceMetadata: true,
+ }
+
+ function = "ComposeObject(destination, sources)"
+ args["source"] = []minio.CopySrcOptions{src, src}
+ args["destination"] = dst3
+ _, err = c.ComposeObject(context.Background(), dst3, src, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ComposeObject failed", err)
+ return
+ }
+
+ // Check that no headers are copied in this case
+ if !reflect.DeepEqual(make(http.Header), fetchMeta("dstObject-3")) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ // 7. Compose a pair of sources with dest user metadata set.
+ dst4 := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "dstObject-4",
+ UserMetadata: map[string]string{"notmyheader": "notmyvalue"},
+ ReplaceMetadata: true,
+ }
+
+ function = "ComposeObject(destination, sources)"
+ args["source"] = []minio.CopySrcOptions{src, src}
+ args["destination"] = dst4
+ _, err = c.ComposeObject(context.Background(), dst4, src, src)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ComposeObject failed", err)
+ return
+ }
+
+ // Check that no headers are copied in this case
+ expectedHeaders = make(http.Header)
+ expectedHeaders.Set("x-amz-meta-notmyheader", "notmyvalue")
+ if !reflect.DeepEqual(expectedHeaders, fetchMeta("dstObject-4")) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testUserMetadataCopyingV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "CopyObject(destination, source)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // c.TraceOn(os.Stderr)
+ testUserMetadataCopyingWrapper(c)
+}
+
+func testStorageClassMetadataPutObject() {
+ // initialize logging params
+ startTime := time.Now()
+ function := "testStorageClassMetadataPutObject()"
+ args := map[string]interface{}{}
+ testName := getFuncName()
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ fetchMeta := func(object string) (h http.Header) {
+ objInfo, err := c.StatObject(context.Background(), bucketName, object, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ h = make(http.Header)
+ for k, vs := range objInfo.Metadata {
+ if strings.HasPrefix(strings.ToLower(k), "x-amz-storage-class") {
+ for _, v := range vs {
+ h.Add(k, v)
+ }
+ }
+ }
+ return h
+ }
+
+ metadata := make(http.Header)
+ metadata.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
+
+ emptyMetadata := make(http.Header)
+
+ const srcSize = 1024 * 1024
+ buf := bytes.Repeat([]byte("abcde"), srcSize) // gives a buffer of 1MiB
+
+ _, err = c.PutObject(context.Background(), bucketName, "srcObjectRRSClass",
+ bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{StorageClass: "REDUCED_REDUNDANCY"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Get the returned metadata
+ returnedMeta := fetchMeta("srcObjectRRSClass")
+
+ // The response metada should either be equal to metadata (with REDUCED_REDUNDANCY) or emptyMetadata (in case of gateways)
+ if !reflect.DeepEqual(metadata, returnedMeta) && !reflect.DeepEqual(emptyMetadata, returnedMeta) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ metadata = make(http.Header)
+ metadata.Set("x-amz-storage-class", "STANDARD")
+
+ _, err = c.PutObject(context.Background(), bucketName, "srcObjectSSClass",
+ bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{StorageClass: "STANDARD"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ if reflect.DeepEqual(metadata, fetchMeta("srcObjectSSClass")) {
+ logError(testName, function, args, startTime, "", "Metadata verification failed, STANDARD storage class should not be a part of response metadata", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testStorageClassInvalidMetadataPutObject() {
+ // initialize logging params
+ startTime := time.Now()
+ function := "testStorageClassInvalidMetadataPutObject()"
+ args := map[string]interface{}{}
+ testName := getFuncName()
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ const srcSize = 1024 * 1024
+ buf := bytes.Repeat([]byte("abcde"), srcSize) // gives a buffer of 1MiB
+
+ _, err = c.PutObject(context.Background(), bucketName, "srcObjectRRSClass",
+ bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{StorageClass: "INVALID_STORAGE_CLASS"})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "PutObject with invalid storage class passed, was expected to fail", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+func testStorageClassMetadataCopyObject() {
+ // initialize logging params
+ startTime := time.Now()
+ function := "testStorageClassMetadataCopyObject()"
+ args := map[string]interface{}{}
+ testName := getFuncName()
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO v4 client object creation failed", err)
+ return
+ }
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test")
+ // Make a new bucket in 'us-east-1' (source bucket).
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ fetchMeta := func(object string) (h http.Header) {
+ objInfo, err := c.StatObject(context.Background(), bucketName, object, minio.StatObjectOptions{})
+ args["bucket"] = bucketName
+ args["object"] = object
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Stat failed", err)
+ return
+ }
+ h = make(http.Header)
+ for k, vs := range objInfo.Metadata {
+ if strings.HasPrefix(strings.ToLower(k), "x-amz-storage-class") {
+ for _, v := range vs {
+ h.Add(k, v)
+ }
+ }
+ }
+ return h
+ }
+
+ metadata := make(http.Header)
+ metadata.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
+
+ emptyMetadata := make(http.Header)
+
+ const srcSize = 1024 * 1024
+ buf := bytes.Repeat([]byte("abcde"), srcSize)
+
+ // Put an object with RRS Storage class
+ _, err = c.PutObject(context.Background(), bucketName, "srcObjectRRSClass",
+ bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{StorageClass: "REDUCED_REDUNDANCY"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Make server side copy of object uploaded in previous step
+ src := minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "srcObjectRRSClass",
+ }
+ dst := minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "srcObjectRRSClassCopy",
+ }
+ if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed on RRS", err)
+ return
+ }
+
+ // Get the returned metadata
+ returnedMeta := fetchMeta("srcObjectRRSClassCopy")
+
+ // The response metada should either be equal to metadata (with REDUCED_REDUNDANCY) or emptyMetadata (in case of gateways)
+ if !reflect.DeepEqual(metadata, returnedMeta) && !reflect.DeepEqual(emptyMetadata, returnedMeta) {
+ logError(testName, function, args, startTime, "", "Metadata match failed", err)
+ return
+ }
+
+ metadata = make(http.Header)
+ metadata.Set("x-amz-storage-class", "STANDARD")
+
+ // Put an object with Standard Storage class
+ _, err = c.PutObject(context.Background(), bucketName, "srcObjectSSClass",
+ bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{StorageClass: "STANDARD"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Make server side copy of object uploaded in previous step
+ src = minio.CopySrcOptions{
+ Bucket: bucketName,
+ Object: "srcObjectSSClass",
+ }
+ dst = minio.CopyDestOptions{
+ Bucket: bucketName,
+ Object: "srcObjectSSClassCopy",
+ }
+ if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+ logError(testName, function, args, startTime, "", "CopyObject failed on SS", err)
+ return
+ }
+ // Fetch the meta data of copied object
+ if reflect.DeepEqual(metadata, fetchMeta("srcObjectSSClassCopy")) {
+ logError(testName, function, args, startTime, "", "Metadata verification failed, STANDARD storage class should not be a part of response metadata", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test put object with size -1 byte object.
+func testPutObjectNoLengthV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader, size, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "size": -1,
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ objectName := bucketName + "unique"
+ args["objectName"] = objectName
+
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+ args["size"] = bufSize
+
+ // Upload an object.
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, -1, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectWithSize failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Expected upload object size "+string(bufSize)+" got "+string(st.Size), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test put objects of unknown size.
+func testPutObjectsUnknownV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader,size,opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "size": "",
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Issues are revealed by trying to upload multiple files of unknown size
+ // sequentially (on 4GB machines)
+ for i := 1; i <= 4; i++ {
+ // Simulate that we could be receiving byte slices of data that we want
+ // to upload as a file
+ rpipe, wpipe := io.Pipe()
+ defer rpipe.Close()
+ go func() {
+ b := []byte("test")
+ wpipe.Write(b)
+ wpipe.Close()
+ }()
+
+ // Upload the object.
+ objectName := fmt.Sprintf("%sunique%d", bucketName, i)
+ args["objectName"] = objectName
+
+ ui, err := c.PutObject(context.Background(), bucketName, objectName, rpipe, -1, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectStreaming failed", err)
+ return
+ }
+
+ if ui.Size != 4 {
+ logError(testName, function, args, startTime, "", "Expected upload object size "+string(4)+" got "+string(ui.Size), nil)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObjectStreaming failed", err)
+ return
+ }
+
+ if st.Size != int64(4) {
+ logError(testName, function, args, startTime, "", "Expected upload object size "+string(4)+" got "+string(st.Size), err)
+ return
+ }
+
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test put object with 0 byte object.
+func testPutObject0ByteV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(bucketName, objectName, reader, size, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectName": "",
+ "size": 0,
+ "opts": "",
+ }
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ objectName := bucketName + "unique"
+ args["objectName"] = objectName
+ args["opts"] = minio.PutObjectOptions{}
+
+ // Upload an object.
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader([]byte("")), 0, minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObjectWithSize failed", err)
+ return
+ }
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObjectWithSize failed", err)
+ return
+ }
+ if st.Size != 0 {
+ logError(testName, function, args, startTime, "", "Expected upload object size 0 but got "+string(st.Size), err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test expected error cases
+func testComposeObjectErrorCases() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject(destination, sourceList)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ testComposeObjectErrorCasesWrapper(c)
+}
+
+// Test concatenating multiple 10K objects V4
+func testCompose10KSources() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ComposeObject(destination, sourceList)"
+ args := map[string]interface{}{}
+
+ // Instantiate new minio client object
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
+ return
+ }
+
+ testComposeMultipleSources(c)
+}
+
+// Tests comprehensive list of all methods.
+func testFunctionalV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "testFunctionalV2()"
+ functionAll := ""
+ args := map[string]interface{}{}
+
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable to debug
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ location := "us-east-1"
+ // Make a new bucket.
+ function = "MakeBucket(bucketName, location)"
+ functionAll = "MakeBucket(bucketName, location)"
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "location": location,
+ }
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: location})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ // Generate a random file name.
+ fileName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ file, err := os.Create(fileName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "file create failed", err)
+ return
+ }
+ for i := 0; i < 3; i++ {
+ buf := make([]byte, rand.Intn(1<<19))
+ _, err = file.Write(buf)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "file write failed", err)
+ return
+ }
+ }
+ file.Close()
+
+ // Verify if bucket exits and you have access.
+ var exists bool
+ function = "BucketExists(bucketName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ }
+ exists, err = c.BucketExists(context.Background(), bucketName)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "BucketExists failed", err)
+ return
+ }
+ if !exists {
+ logError(testName, function, args, startTime, "", "Could not find existing bucket "+bucketName, err)
+ return
+ }
+
+ // Make the bucket 'public read/write'.
+ function = "SetBucketPolicy(bucketName, bucketPolicy)"
+ functionAll += ", " + function
+
+ readWritePolicy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:ListBucketMultipartUploads", "s3:ListBucket"],"Effect": "Allow","Principal": {"AWS": ["*"]},"Resource": ["arn:aws:s3:::` + bucketName + `"],"Sid": ""}]}`
+
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "bucketPolicy": readWritePolicy,
+ }
+ err = c.SetBucketPolicy(context.Background(), bucketName, readWritePolicy)
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err)
+ return
+ }
+
+ // List all buckets.
+ function = "ListBuckets()"
+ functionAll += ", " + function
+ args = nil
+ buckets, err := c.ListBuckets(context.Background())
+ if len(buckets) == 0 {
+ logError(testName, function, args, startTime, "", "List buckets cannot be empty", err)
+ return
+ }
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ListBuckets failed", err)
+ return
+ }
+
+ // Verify if previously created bucket is listed in list buckets.
+ bucketFound := false
+ for _, bucket := range buckets {
+ if bucket.Name == bucketName {
+ bucketFound = true
+ }
+ }
+
+ // If bucket not found error out.
+ if !bucketFound {
+ logError(testName, function, args, startTime, "", "Bucket "+bucketName+"not found", err)
+ return
+ }
+
+ objectName := bucketName + "unique"
+
+ // Generate data
+ buf := bytes.Repeat([]byte("n"), rand.Intn(1<<19))
+
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "contentType": "",
+ }
+ _, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ st, err := c.StatObject(context.Background(), bucketName, objectName, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", "Expected uploaded object length "+string(len(buf))+" got "+string(st.Size), err)
+ return
+ }
+
+ objectNameNoLength := objectName + "-nolength"
+ args["objectName"] = objectNameNoLength
+ _, err = c.PutObject(context.Background(), bucketName, objectNameNoLength, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+ st, err = c.StatObject(context.Background(), bucketName, objectNameNoLength, minio.StatObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "StatObject failed", err)
+ return
+ }
+ if st.Size != int64(len(buf)) {
+ logError(testName, function, args, startTime, "", "Expected uploaded object length "+string(len(buf))+" got "+string(st.Size), err)
+ return
+ }
+
+ // Instantiate a done channel to close all listing.
+ doneCh := make(chan struct{})
+ defer close(doneCh)
+
+ objFound := false
+ isRecursive := true // Recursive is true.
+ function = "ListObjects(bucketName, objectName, isRecursive, doneCh)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "isRecursive": isRecursive,
+ }
+ for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Prefix: objectName, Recursive: isRecursive}) {
+ if obj.Key == objectName {
+ objFound = true
+ break
+ }
+ }
+ if !objFound {
+ logError(testName, function, args, startTime, "", "Could not find existing object "+objectName, err)
+ return
+ }
+
+ incompObjNotFound := true
+ function = "ListIncompleteUploads(bucketName, objectName, isRecursive, doneCh)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "isRecursive": isRecursive,
+ }
+ for objIncompl := range c.ListIncompleteUploads(context.Background(), bucketName, objectName, isRecursive) {
+ if objIncompl.Key != "" {
+ incompObjNotFound = false
+ break
+ }
+ }
+ if !incompObjNotFound {
+ logError(testName, function, args, startTime, "", "Unexpected dangling incomplete upload found", err)
+ return
+ }
+
+ function = "GetObject(bucketName, objectName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ }
+ newReader, err := c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ newReadBytes, err := ioutil.ReadAll(newReader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ newReader.Close()
+
+ if !bytes.Equal(newReadBytes, buf) {
+ logError(testName, function, args, startTime, "", "Bytes mismatch", err)
+ return
+ }
+
+ function = "FGetObject(bucketName, objectName, fileName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "fileName": fileName + "-f",
+ }
+ err = c.FGetObject(context.Background(), bucketName, objectName, fileName+"-f", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FgetObject failed", err)
+ return
+ }
+
+ // Generate presigned HEAD object url.
+ function = "PresignedHeadObject(bucketName, objectName, expires, reqParams)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "expires": 3600 * time.Second,
+ }
+ presignedHeadURL, err := c.PresignedHeadObject(context.Background(), bucketName, objectName, 3600*time.Second, nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject failed", err)
+ return
+ }
+
+ transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS)))
+ if err != nil {
+ logError(testName, function, args, startTime, "", "DefaultTransport failed", err)
+ return
+ }
+
+ httpClient := &http.Client{
+ // Setting a sensible time out of 30secs to wait for response
+ // headers. Request is pro-actively canceled after 30secs
+ // with no response.
+ Timeout: 30 * time.Second,
+ Transport: transport,
+ }
+
+ req, err := http.NewRequest(http.MethodHead, presignedHeadURL.String(), nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject URL head request failed", err)
+ return
+ }
+
+ // Verify if presigned url works.
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject URL head request failed", err)
+ return
+ }
+ if resp.StatusCode != http.StatusOK {
+ logError(testName, function, args, startTime, "", "PresignedHeadObject URL returns status "+string(resp.StatusCode), err)
+ return
+ }
+ if resp.Header.Get("ETag") == "" {
+ logError(testName, function, args, startTime, "", "Got empty ETag", err)
+ return
+ }
+ resp.Body.Close()
+
+ // Generate presigned GET object url.
+ function = "PresignedGetObject(bucketName, objectName, expires, reqParams)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName,
+ "expires": 3600 * time.Second,
+ }
+ presignedGetURL, err := c.PresignedGetObject(context.Background(), bucketName, objectName, 3600*time.Second, nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject failed", err)
+ return
+ }
+
+ // Verify if presigned url works.
+ req, err = http.NewRequest(http.MethodGet, presignedGetURL.String(), nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject request incorrect", err)
+ return
+ }
+
+ resp, err = httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ logError(testName, function, args, startTime, "", "PresignedGetObject URL returns status "+string(resp.StatusCode), err)
+ return
+ }
+ newPresignedBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ resp.Body.Close()
+ if !bytes.Equal(newPresignedBytes, buf) {
+ logError(testName, function, args, startTime, "", "Bytes mismatch", err)
+ return
+ }
+
+ // Set request parameters.
+ reqParams := make(url.Values)
+ reqParams.Set("response-content-disposition", "attachment; filename=\"test.txt\"")
+ // Generate presigned GET object url.
+ args["reqParams"] = reqParams
+ presignedGetURL, err = c.PresignedGetObject(context.Background(), bucketName, objectName, 3600*time.Second, reqParams)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject failed", err)
+ return
+ }
+
+ // Verify if presigned url works.
+ req, err = http.NewRequest(http.MethodGet, presignedGetURL.String(), nil)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject request incorrect", err)
+ return
+ }
+
+ resp, err = httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedGetObject response incorrect", err)
+ return
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ logError(testName, function, args, startTime, "", "PresignedGetObject URL returns status "+string(resp.StatusCode), err)
+ return
+ }
+ newPresignedBytes, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ if !bytes.Equal(newPresignedBytes, buf) {
+ logError(testName, function, args, startTime, "", "Bytes mismatch", err)
+ return
+ }
+ // Verify content disposition.
+ if resp.Header.Get("Content-Disposition") != "attachment; filename=\"test.txt\"" {
+ logError(testName, function, args, startTime, "", "wrong Content-Disposition received ", err)
+ return
+ }
+
+ function = "PresignedPutObject(bucketName, objectName, expires)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName + "-presigned",
+ "expires": 3600 * time.Second,
+ }
+ presignedPutURL, err := c.PresignedPutObject(context.Background(), bucketName, objectName+"-presigned", 3600*time.Second)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PresignedPutObject failed", err)
+ return
+ }
+
+ // Generate data more than 32K
+ buf = bytes.Repeat([]byte("1"), rand.Intn(1<<10)+32*1024)
+
+ req, err = http.NewRequest(http.MethodPut, presignedPutURL.String(), bytes.NewReader(buf))
+ if err != nil {
+ logError(testName, function, args, startTime, "", "HTTP request to PresignedPutObject URL failed", err)
+ return
+ }
+
+ resp, err = httpClient.Do(req)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "HTTP request to PresignedPutObject URL failed", err)
+ return
+ }
+
+ function = "GetObject(bucketName, objectName)"
+ functionAll += ", " + function
+ args = map[string]interface{}{
+ "bucketName": bucketName,
+ "objectName": objectName + "-presigned",
+ }
+ newReader, err = c.GetObject(context.Background(), bucketName, objectName+"-presigned", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ newReadBytes, err = ioutil.ReadAll(newReader)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "ReadAll failed", err)
+ return
+ }
+ newReader.Close()
+
+ if !bytes.Equal(newReadBytes, buf) {
+ logError(testName, function, args, startTime, "", "Bytes mismatch", err)
+ return
+ }
+
+ os.Remove(fileName)
+ os.Remove(fileName + "-f")
+ successLogger(testName, functionAll, args, startTime).Info()
+}
+
+// Test get object with GetObject with context
+func testGetObjectContext() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(ctx, bucketName, objectName)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ cancel()
+
+ r, err := c.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed unexpectedly", err)
+ return
+ }
+
+ if _, err = r.Stat(); err == nil {
+ logError(testName, function, args, startTime, "", "GetObject should fail on short timeout", err)
+ return
+ }
+ r.Close()
+
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Read the data back
+ r, err = c.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "object Stat call failed", err)
+ return
+ }
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match: want "+string(bufSize)+", got"+string(st.Size), err)
+ return
+ }
+ if err := r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", "object Close() call failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Test get object with FGetObject with a user provided context
+func testFGetObjectContext() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FGetObject(ctx, bucketName, objectName, fileName)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-1-MB"]
+ var reader = getDataReader("datafile-1-MB")
+ defer reader.Close()
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ defer cancel()
+
+ fileName := "tempfile-context"
+ args["fileName"] = fileName
+ // Read the data back
+ err = c.FGetObject(ctx, bucketName, objectName, fileName+"-f", minio.GetObjectOptions{})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "FGetObject should fail on short timeout", err)
+ return
+ }
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ defer cancel()
+
+ // Read the data back
+ err = c.FGetObject(ctx, bucketName, objectName, fileName+"-fcontext", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FGetObject with long timeout failed", err)
+ return
+ }
+ if err = os.Remove(fileName + "-fcontext"); err != nil {
+ logError(testName, function, args, startTime, "", "Remove file failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Test get object with GetObject with a user provided context
+func testGetObjectRanges() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(ctx, bucketName, objectName, fileName)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+ defer cancel()
+
+ rng := rand.NewSource(time.Now().UnixNano())
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rng, "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+ // Save the data
+ objectName := randString(60, rng, "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ // Read the data back
+ tests := []struct {
+ start int64
+ end int64
+ }{
+ {
+ start: 1024,
+ end: 1024 + 1<<20,
+ },
+ {
+ start: 20e6,
+ end: 20e6 + 10000,
+ },
+ {
+ start: 40e6,
+ end: 40e6 + 10000,
+ },
+ {
+ start: 60e6,
+ end: 60e6 + 10000,
+ },
+ {
+ start: 80e6,
+ end: 80e6 + 10000,
+ },
+ {
+ start: 120e6,
+ end: int64(bufSize),
+ },
+ }
+ for _, test := range tests {
+ wantRC := getDataReader("datafile-129-MB")
+ io.CopyN(ioutil.Discard, wantRC, test.start)
+ want := mustCrcReader(io.LimitReader(wantRC, test.end-test.start+1))
+ opts := minio.GetObjectOptions{}
+ opts.SetRange(test.start, test.end)
+ args["opts"] = fmt.Sprintf("%+v", test)
+ obj, err := c.GetObject(ctx, bucketName, objectName, opts)
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FGetObject with long timeout failed", err)
+ return
+ }
+ err = crcMatches(obj, want)
+ if err != nil {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("GetObject offset %d -> %d", test.start, test.end), err)
+ return
+ }
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test get object ACLs with GetObjectACL with custom provided context
+func testGetObjectACLContext() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObjectACL(ctx, bucketName, objectName)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // skipping region functional tests for non s3 runs
+ if os.Getenv(serverEndpoint) != "s3.amazonaws.com" {
+ ignoredLog(testName, function, args, startTime, "Skipped region functional tests for non s3 runs").Info()
+ return
+ }
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-1-MB"]
+ var reader = getDataReader("datafile-1-MB")
+ defer reader.Close()
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Add meta data to add a canned acl
+ metaData := map[string]string{
+ "X-Amz-Acl": "public-read-write",
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName,
+ objectName, reader, int64(bufSize),
+ minio.PutObjectOptions{
+ ContentType: "binary/octet-stream",
+ UserMetadata: metaData,
+ })
+
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Read the data back
+ objectInfo, getObjectACLErr := c.GetObjectACL(ctx, bucketName, objectName)
+ if getObjectACLErr != nil {
+ logError(testName, function, args, startTime, "", "GetObjectACL failed. ", getObjectACLErr)
+ return
+ }
+
+ s, ok := objectInfo.Metadata["X-Amz-Acl"]
+ if !ok {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Acl\"", nil)
+ return
+ }
+
+ if len(s) != 1 {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Acl\" canned acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
+ return
+ }
+
+ if s[0] != "public-read-write" {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Acl\" expected \"public-read-write\" but got"+fmt.Sprintf("%q", s[0]), nil)
+ return
+ }
+
+ bufSize = dataFileMap["datafile-1-MB"]
+ var reader2 = getDataReader("datafile-1-MB")
+ defer reader2.Close()
+ // Save the data
+ objectName = randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Add meta data to add a canned acl
+ metaData = map[string]string{
+ "X-Amz-Grant-Read": "id=fooread@minio.go",
+ "X-Amz-Grant-Write": "id=foowrite@minio.go",
+ }
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader2, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream", UserMetadata: metaData})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ return
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Read the data back
+ objectInfo, getObjectACLErr = c.GetObjectACL(ctx, bucketName, objectName)
+ if getObjectACLErr == nil {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail", getObjectACLErr)
+ return
+ }
+
+ if len(objectInfo.Metadata) != 3 {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail expected \"3\" ACLs but got "+fmt.Sprintf(`"%d"`, len(objectInfo.Metadata)), nil)
+ return
+ }
+
+ s, ok = objectInfo.Metadata["X-Amz-Grant-Read"]
+ if !ok {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Grant-Read\"", nil)
+ return
+ }
+
+ if len(s) != 1 {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Read\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
+ return
+ }
+
+ if s[0] != "fooread@minio.go" {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Read\" acl expected \"fooread@minio.go\" got "+fmt.Sprintf("%q", s), nil)
+ return
+ }
+
+ s, ok = objectInfo.Metadata["X-Amz-Grant-Write"]
+ if !ok {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Grant-Write\"", nil)
+ return
+ }
+
+ if len(s) != 1 {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Write\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
+ return
+ }
+
+ if s[0] != "foowrite@minio.go" {
+ logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Write\" acl expected \"foowrite@minio.go\" got "+fmt.Sprintf("%q", s), nil)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test validates putObject with context to see if request cancellation is honored for V2.
+func testPutObjectContextV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "PutObject(ctx, bucketName, objectName, reader, size, opts)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ "size": "",
+ "opts": "",
+ }
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Make a new bucket.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+ defer cleanupBucket(bucketName, c)
+ bufSize := dataFileMap["datatfile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+
+ objectName := fmt.Sprintf("test-file-%v", rand.Uint32())
+ args["objectName"] = objectName
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ args["ctx"] = ctx
+ args["size"] = bufSize
+ defer cancel()
+
+ _, err = c.PutObject(ctx, bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject with short timeout failed", err)
+ return
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ args["ctx"] = ctx
+
+ defer cancel()
+ reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+ _, err = c.PutObject(ctx, bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject with long timeout failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Test get object with GetObject with custom context
+func testGetObjectContextV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "GetObject(ctx, bucketName, objectName)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ cancel()
+
+ r, err := c.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject failed unexpectedly", err)
+ return
+ }
+ if _, err = r.Stat(); err == nil {
+ logError(testName, function, args, startTime, "", "GetObject should fail on short timeout", err)
+ return
+ }
+ r.Close()
+
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ defer cancel()
+
+ // Read the data back
+ r, err = c.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "GetObject shouldn't fail on longer timeout", err)
+ return
+ }
+
+ st, err := r.Stat()
+ if err != nil {
+ logError(testName, function, args, startTime, "", "object Stat call failed", err)
+ return
+ }
+ if st.Size != int64(bufSize) {
+ logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(bufSize)+" got "+string(st.Size), err)
+ return
+ }
+ if err := r.Close(); err != nil {
+ logError(testName, function, args, startTime, "", " object Close() call failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Test get object with FGetObject with custom context
+func testFGetObjectContextV2() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "FGetObject(ctx, bucketName, objectName,fileName)"
+ args := map[string]interface{}{
+ "ctx": "",
+ "bucketName": "",
+ "objectName": "",
+ "fileName": "",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV2(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v2 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket call failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ bufSize := dataFileMap["datatfile-1-MB"]
+ var reader = getDataReader("datafile-1-MB")
+ defer reader.Close()
+ // Save the data
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ _, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject call failed", err)
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ defer cancel()
+
+ fileName := "tempfile-context"
+ args["fileName"] = fileName
+
+ // Read the data back
+ err = c.FGetObject(ctx, bucketName, objectName, fileName+"-f", minio.GetObjectOptions{})
+ if err == nil {
+ logError(testName, function, args, startTime, "", "FGetObject should fail on short timeout", err)
+ return
+ }
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ defer cancel()
+
+ // Read the data back
+ err = c.FGetObject(ctx, bucketName, objectName, fileName+"-fcontext", minio.GetObjectOptions{})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "FGetObject call shouldn't fail on long timeout", err)
+ return
+ }
+
+ if err = os.Remove(fileName + "-fcontext"); err != nil {
+ logError(testName, function, args, startTime, "", "Remove file failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+
+}
+
+// Test list object v1 and V2
+func testListObjects() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "ListObjects(bucketName, objectPrefix, recursive, doneCh)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectPrefix": "",
+ "recursive": "true",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ defer cleanupBucket(bucketName, c)
+
+ testObjects := []struct {
+ name string
+ storageClass string
+ }{
+ // Special characters
+ {"foo bar", "STANDARD"},
+ {"foo-%", "STANDARD"},
+ {"random-object-1", "STANDARD"},
+ {"random-object-2", "REDUCED_REDUNDANCY"},
+ }
+
+ for i, object := range testObjects {
+ bufSize := dataFileMap["datafile-33-kB"]
+ var reader = getDataReader("datafile-33-kB")
+ defer reader.Close()
+ _, err = c.PutObject(context.Background(), bucketName, object.name, reader, int64(bufSize),
+ minio.PutObjectOptions{ContentType: "binary/octet-stream", StorageClass: object.storageClass})
+ if err != nil {
+ logError(testName, function, args, startTime, "", fmt.Sprintf("PutObject %d call failed", i+1), err)
+ return
+ }
+ }
+
+ testList := func(listFn func(context.Context, string, minio.ListObjectsOptions) <-chan minio.ObjectInfo, bucket string, opts minio.ListObjectsOptions) {
+ var objCursor int
+
+ // check for object name and storage-class from listing object result
+ for objInfo := range listFn(context.Background(), bucket, opts) {
+ if objInfo.Err != nil {
+ logError(testName, function, args, startTime, "", "ListObjects failed unexpectedly", err)
+ return
+ }
+ if objInfo.Key != testObjects[objCursor].name {
+ logError(testName, function, args, startTime, "", "ListObjects does not return expected object name", err)
+ return
+ }
+ if objInfo.StorageClass != testObjects[objCursor].storageClass {
+ // Ignored as Gateways (Azure/GCS etc) wont return storage class
+ ignoredLog(testName, function, args, startTime, "ListObjects doesn't return expected storage class").Info()
+ }
+ objCursor++
+ }
+
+ if objCursor != len(testObjects) {
+ logError(testName, function, args, startTime, "", "ListObjects returned unexpected number of items", errors.New(""))
+ return
+ }
+ }
+
+ testList(c.ListObjects, bucketName, minio.ListObjectsOptions{Recursive: true, UseV1: true})
+ testList(c.ListObjects, bucketName, minio.ListObjectsOptions{Recursive: true})
+ testList(c.ListObjects, bucketName, minio.ListObjectsOptions{Recursive: true, WithMetadata: true})
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Test deleting multiple objects with object retention set in Governance mode
+func testRemoveObjects() {
+ // initialize logging params
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "RemoveObjects(bucketName, objectsCh, opts)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ "objectPrefix": "",
+ "recursive": "true",
+ }
+ // Seed random based on current time.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client object.
+ c, err := minio.New(os.Getenv(serverEndpoint),
+ &minio.Options{
+ Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""),
+ Secure: mustParseBool(os.Getenv(enableHTTPS)),
+ })
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err)
+ return
+ }
+
+ // Enable tracing, write to stderr.
+ // c.TraceOn(os.Stderr)
+
+ // Set user agent.
+ c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+ objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
+ args["objectName"] = objectName
+
+ // Make a new bucket.
+ err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ return
+ }
+
+ bufSize := dataFileMap["datafile-129-MB"]
+ var reader = getDataReader("datafile-129-MB")
+ defer reader.Close()
+
+ n, err := c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
+ if err != nil {
+ log.Fatalln(err)
+ }
+ log.Println("Uploaded", objectName, " of size: ", n, "to bucket: ", bucketName, "Successfully.")
+
+ // Replace with smaller...
+ bufSize = dataFileMap["datafile-10-kB"]
+ reader = getDataReader("datafile-10-kB")
+ defer reader.Close()
+
+ n, err = c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{})
+ if err != nil {
+ log.Fatalln(err)
+ }
+ log.Println("Uploaded", objectName, " of size: ", n, "to bucket: ", bucketName, "Successfully.")
+
+ t := time.Date(2030, time.April, 25, 14, 0, 0, 0, time.UTC)
+ m := minio.RetentionMode(minio.Governance)
+ opts := minio.PutObjectRetentionOptions{
+ GovernanceBypass: false,
+ RetainUntilDate: &t,
+ Mode: &m,
+ }
+ err = c.PutObjectRetention(context.Background(), bucketName, objectName, opts)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ objectsCh := make(chan minio.ObjectInfo)
+ // Send object names that are needed to be removed to objectsCh
+ go func() {
+ defer close(objectsCh)
+ // List all objects from a bucket-name with a matching prefix.
+ for object := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Recursive: true}) {
+ if object.Err != nil {
+ log.Fatalln(object.Err)
+ }
+ objectsCh <- object
+ }
+ }()
+
+ for rErr := range c.RemoveObjects(context.Background(), bucketName, objectsCh, minio.RemoveObjectsOptions{}) {
+ // Error is expected here because Retention is set on the object
+ // and RemoveObjects is called without Bypass Governance
+ if rErr.Err == nil {
+ logError(testName, function, args, startTime, "", "Expected error during deletion", nil)
+ return
+ }
+ }
+
+ objectsCh1 := make(chan minio.ObjectInfo)
+
+ // Send object names that are needed to be removed to objectsCh
+ go func() {
+ defer close(objectsCh1)
+ // List all objects from a bucket-name with a matching prefix.
+ for object := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Recursive: true}) {
+ if object.Err != nil {
+ log.Fatalln(object.Err)
+ }
+ objectsCh1 <- object
+ }
+ }()
+
+ opts1 := minio.RemoveObjectsOptions{
+ GovernanceBypass: true,
+ }
+
+ for rErr := range c.RemoveObjects(context.Background(), bucketName, objectsCh1, opts1) {
+ // Error is not expected here because Retention is set on the object
+ // and RemoveObjects is called with Bypass Governance
+ logError(testName, function, args, startTime, "", "Error detected during deletion", rErr.Err)
+ return
+ }
+
+ // Delete all objects and buckets
+ if err = cleanupVersionedBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "CleanupBucket failed", err)
+ return
+ }
+
+ successLogger(testName, function, args, startTime).Info()
+}
+
+// Convert string to bool and always return false if any error
+func mustParseBool(str string) bool {
+ b, err := strconv.ParseBool(str)
+ if err != nil {
+ return false
+ }
+ return b
+}
+
+func main() {
+ // Output to stdout instead of the default stderr
+ log.SetOutput(os.Stdout)
+ // create custom formatter
+ mintFormatter := mintJSONFormatter{}
+ // set custom formatter
+ log.SetFormatter(&mintFormatter)
+ // log Info or above -- success cases are Info level, failures are Fatal level
+ log.SetLevel(log.InfoLevel)
+
+ tls := mustParseBool(os.Getenv(enableHTTPS))
+ kms := mustParseBool(os.Getenv(enableKMS))
+ if os.Getenv(enableKMS) == "" {
+ // Default to KMS tests.
+ kms = true
+ }
+ // execute tests
+ if isFullMode() {
+ testMakeBucketErrorV2()
+ testGetObjectClosedTwiceV2()
+ testFPutObjectV2()
+ testMakeBucketRegionsV2()
+ testGetObjectReadSeekFunctionalV2()
+ testGetObjectReadAtFunctionalV2()
+ testGetObjectRanges()
+ testCopyObjectV2()
+ testFunctionalV2()
+ testComposeObjectErrorCasesV2()
+ testCompose10KSourcesV2()
+ testUserMetadataCopyingV2()
+ testPutObject0ByteV2()
+ testPutObjectNoLengthV2()
+ testPutObjectsUnknownV2()
+ testGetObjectContextV2()
+ testFPutObjectContextV2()
+ testFGetObjectContextV2()
+ testPutObjectContextV2()
+ testMakeBucketError()
+ testMakeBucketRegions()
+ testPutObjectWithMetadata()
+ testPutObjectReadAt()
+ testPutObjectStreaming()
+ testGetObjectSeekEnd()
+ testGetObjectClosedTwice()
+ testRemoveMultipleObjects()
+ testFPutObjectMultipart()
+ testFPutObject()
+ testGetObjectReadSeekFunctional()
+ testGetObjectReadAtFunctional()
+ testGetObjectReadAtWhenEOFWasReached()
+ testPresignedPostPolicy()
+ testCopyObject()
+ testComposeObjectErrorCases()
+ testCompose10KSources()
+ testUserMetadataCopying()
+ testBucketNotification()
+ testFunctional()
+ testGetObjectModified()
+ testPutObjectUploadSeekedObject()
+ testGetObjectContext()
+ testFPutObjectContext()
+ testFGetObjectContext()
+ testGetObjectACLContext()
+ testPutObjectContext()
+ testStorageClassMetadataPutObject()
+ testStorageClassInvalidMetadataPutObject()
+ testStorageClassMetadataCopyObject()
+ testPutObjectWithContentLanguage()
+ testListObjects()
+ testRemoveObjects()
+ testListObjectVersions()
+ testStatObjectWithVersioning()
+ testGetObjectWithVersioning()
+ testCopyObjectWithVersioning()
+ testComposeObjectWithVersioning()
+ testRemoveObjectWithVersioning()
+ testRemoveObjectsWithVersioning()
+ testObjectTaggingWithVersioning()
+
+ // SSE-C tests will only work over TLS connection.
+ if tls {
+ testSSECEncryptionPutGet()
+ testSSECEncryptionFPut()
+ testSSECEncryptedGetObjectReadAtFunctional()
+ testSSECEncryptedGetObjectReadSeekFunctional()
+ testEncryptedCopyObjectV2()
+ testEncryptedSSECToSSECCopyObject()
+ testEncryptedSSECToUnencryptedCopyObject()
+ testUnencryptedToSSECCopyObject()
+ testUnencryptedToUnencryptedCopyObject()
+ testEncryptedEmptyObject()
+ testDecryptedCopyObject()
+ testSSECEncryptedToSSECCopyObjectPart()
+ testSSECMultipartEncryptedToSSECCopyObjectPart()
+ testSSECEncryptedToUnencryptedCopyPart()
+ testUnencryptedToSSECCopyObjectPart()
+ testUnencryptedToUnencryptedCopyPart()
+ testEncryptedSSECToSSES3CopyObject()
+ testEncryptedSSES3ToSSECCopyObject()
+ testSSECEncryptedToSSES3CopyObjectPart()
+ testSSES3EncryptedToSSECCopyObjectPart()
+ }
+
+ // KMS tests
+ if kms {
+ testSSES3EncryptionPutGet()
+ testSSES3EncryptionFPut()
+ testSSES3EncryptedGetObjectReadAtFunctional()
+ testSSES3EncryptedGetObjectReadSeekFunctional()
+ testEncryptedSSES3ToSSES3CopyObject()
+ testEncryptedSSES3ToUnencryptedCopyObject()
+ testUnencryptedToSSES3CopyObject()
+ testUnencryptedToSSES3CopyObjectPart()
+ testSSES3EncryptedToUnencryptedCopyPart()
+ testSSES3EncryptedToSSES3CopyObjectPart()
+ }
+ } else {
+ testFunctional()
+ testFunctionalV2()
+ }
+}
diff --git a/vendor/github.com/minio/minio-go/v7/go.mod b/vendor/github.com/minio/minio-go/v7/go.mod
new file mode 100644
index 00000000..5468818c
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/go.mod
@@ -0,0 +1,27 @@
+module github.com/minio/minio-go/v7
+
+go 1.14
+
+require (
+ github.com/dustin/go-humanize v1.0.0
+ github.com/google/uuid v1.1.1
+ github.com/json-iterator/go v1.1.10
+ github.com/klauspost/cpuid v1.3.1 // indirect
+ github.com/kr/pretty v0.1.0 // indirect
+ github.com/minio/md5-simd v1.1.0
+ github.com/minio/sha256-simd v0.1.1
+ github.com/mitchellh/go-homedir v1.1.0
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.1 // indirect
+ github.com/rs/xid v1.2.1
+ github.com/sirupsen/logrus v1.8.1
+ github.com/smartystreets/goconvey v1.6.4 // indirect
+ github.com/stretchr/testify v1.4.0 // indirect
+ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
+ golang.org/x/net v0.0.0-20200707034311-ab3426394381
+ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
+ golang.org/x/text v0.3.3 // indirect
+ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
+ gopkg.in/ini.v1 v1.57.0
+ gopkg.in/yaml.v2 v2.2.8 // indirect
+)
diff --git a/vendor/github.com/minio/minio-go/v7/go.sum b/vendor/github.com/minio/minio-go/v7/go.sum
new file mode 100644
index 00000000..1ba98c4f
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/go.sum
@@ -0,0 +1,76 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
+github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
+github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
+github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
+github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+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/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
+golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
+gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/vendor/github.com/minio/minio-go/v7/hook-reader.go b/vendor/github.com/minio/minio-go/v7/hook-reader.go
new file mode 100644
index 00000000..f251c1e9
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/hook-reader.go
@@ -0,0 +1,85 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "fmt"
+ "io"
+)
+
+// hookReader hooks additional reader in the source stream. It is
+// useful for making progress bars. Second reader is appropriately
+// notified about the exact number of bytes read from the primary
+// source on each Read operation.
+type hookReader struct {
+ source io.Reader
+ hook io.Reader
+}
+
+// Seek implements io.Seeker. Seeks source first, and if necessary
+// seeks hook if Seek method is appropriately found.
+func (hr *hookReader) Seek(offset int64, whence int) (n int64, err error) {
+ // Verify for source has embedded Seeker, use it.
+ sourceSeeker, ok := hr.source.(io.Seeker)
+ if ok {
+ n, err = sourceSeeker.Seek(offset, whence)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ // Verify if hook has embedded Seeker, use it.
+ hookSeeker, ok := hr.hook.(io.Seeker)
+ if ok {
+ var m int64
+ m, err = hookSeeker.Seek(offset, whence)
+ if err != nil {
+ return 0, err
+ }
+ if n != m {
+ return 0, fmt.Errorf("hook seeker seeked %d bytes, expected source %d bytes", m, n)
+ }
+ }
+ return n, nil
+}
+
+// Read implements io.Reader. Always reads from the source, the return
+// value 'n' number of bytes are reported through the hook. Returns
+// error for all non io.EOF conditions.
+func (hr *hookReader) Read(b []byte) (n int, err error) {
+ n, err = hr.source.Read(b)
+ if err != nil && err != io.EOF {
+ return n, err
+ }
+ // Progress the hook with the total read bytes from the source.
+ if _, herr := hr.hook.Read(b[:n]); herr != nil {
+ if herr != io.EOF {
+ return n, herr
+ }
+ }
+ return n, err
+}
+
+// newHook returns a io.ReadSeeker which implements hookReader that
+// reports the data read from the source to the hook.
+func newHook(source, hook io.Reader) io.Reader {
+ if hook == nil {
+ return source
+ }
+ return &hookReader{source, hook}
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/assume_role.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/assume_role.go
new file mode 100644
index 00000000..3b1b547b
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/assume_role.go
@@ -0,0 +1,214 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "encoding/hex"
+ "encoding/xml"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/signer"
+ sha256 "github.com/minio/sha256-simd"
+)
+
+// AssumeRoleResponse contains the result of successful AssumeRole request.
+type AssumeRoleResponse struct {
+ XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleResponse" json:"-"`
+
+ Result AssumeRoleResult `xml:"AssumeRoleResult"`
+ ResponseMetadata struct {
+ RequestID string `xml:"RequestId,omitempty"`
+ } `xml:"ResponseMetadata,omitempty"`
+}
+
+// AssumeRoleResult - Contains the response to a successful AssumeRole
+// request, including temporary credentials that can be used to make
+// MinIO API requests.
+type AssumeRoleResult struct {
+ // The identifiers for the temporary security credentials that the operation
+ // returns.
+ AssumedRoleUser AssumedRoleUser `xml:",omitempty"`
+
+ // The temporary security credentials, which include an access key ID, a secret
+ // access key, and a security (or session) token.
+ //
+ // Note: The size of the security token that STS APIs return is not fixed. We
+ // strongly recommend that you make no assumptions about the maximum size. As
+ // of this writing, the typical size is less than 4096 bytes, but that can vary.
+ // Also, future updates to AWS might require larger sizes.
+ Credentials struct {
+ AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"`
+ SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"`
+ Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
+ SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
+ } `xml:",omitempty"`
+
+ // A percentage value that indicates the size of the policy in packed form.
+ // The service rejects any policy with a packed size greater than 100 percent,
+ // which means the policy exceeded the allowed space.
+ PackedPolicySize int `xml:",omitempty"`
+}
+
+// A STSAssumeRole retrieves credentials from MinIO service, and keeps track if
+// those credentials are expired.
+type STSAssumeRole struct {
+ Expiry
+
+ // Required http Client to use when connecting to MinIO STS service.
+ Client *http.Client
+
+ // STS endpoint to fetch STS credentials.
+ STSEndpoint string
+
+ // various options for this request.
+ Options STSAssumeRoleOptions
+}
+
+// STSAssumeRoleOptions collection of various input options
+// to obtain AssumeRole credentials.
+type STSAssumeRoleOptions struct {
+ // Mandatory inputs.
+ AccessKey string
+ SecretKey string
+
+ Location string // Optional commonly needed with AWS STS.
+ DurationSeconds int // Optional defaults to 1 hour.
+
+ // Optional only valid if using with AWS STS
+ RoleARN string
+ RoleSessionName string
+}
+
+// NewSTSAssumeRole returns a pointer to a new
+// Credentials object wrapping the STSAssumeRole.
+func NewSTSAssumeRole(stsEndpoint string, opts STSAssumeRoleOptions) (*Credentials, error) {
+ if stsEndpoint == "" {
+ return nil, errors.New("STS endpoint cannot be empty")
+ }
+ if opts.AccessKey == "" || opts.SecretKey == "" {
+ return nil, errors.New("AssumeRole credentials access/secretkey is mandatory")
+ }
+ return New(&STSAssumeRole{
+ Client: &http.Client{
+ Transport: http.DefaultTransport,
+ },
+ STSEndpoint: stsEndpoint,
+ Options: opts,
+ }), nil
+}
+
+const defaultDurationSeconds = 3600
+
+// closeResponse close non nil response with any response Body.
+// convenient wrapper to drain any remaining data on response body.
+//
+// Subsequently this allows golang http RoundTripper
+// to re-use the same connection for future requests.
+func closeResponse(resp *http.Response) {
+ // Callers should close resp.Body when done reading from it.
+ // If resp.Body is not closed, the Client's underlying RoundTripper
+ // (typically Transport) may not be able to re-use a persistent TCP
+ // connection to the server for a subsequent "keep-alive" request.
+ if resp != nil && resp.Body != nil {
+ // Drain any remaining Body and then close the connection.
+ // Without this closing connection would disallow re-using
+ // the same connection for future uses.
+ // - http://stackoverflow.com/a/17961593/4465767
+ io.Copy(ioutil.Discard, resp.Body)
+ resp.Body.Close()
+ }
+}
+
+func getAssumeRoleCredentials(clnt *http.Client, endpoint string, opts STSAssumeRoleOptions) (AssumeRoleResponse, error) {
+ v := url.Values{}
+ v.Set("Action", "AssumeRole")
+ v.Set("Version", STSVersion)
+ if opts.RoleARN != "" {
+ v.Set("RoleArn", opts.RoleARN)
+ }
+ if opts.RoleSessionName != "" {
+ v.Set("RoleSessionName", opts.RoleSessionName)
+ }
+ if opts.DurationSeconds > defaultDurationSeconds {
+ v.Set("DurationSeconds", strconv.Itoa(opts.DurationSeconds))
+ } else {
+ v.Set("DurationSeconds", strconv.Itoa(defaultDurationSeconds))
+ }
+
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return AssumeRoleResponse{}, err
+ }
+ u.Path = "/"
+
+ postBody := strings.NewReader(v.Encode())
+ hash := sha256.New()
+ if _, err = io.Copy(hash, postBody); err != nil {
+ return AssumeRoleResponse{}, err
+ }
+ postBody.Seek(0, 0)
+
+ req, err := http.NewRequest(http.MethodPost, u.String(), postBody)
+ if err != nil {
+ return AssumeRoleResponse{}, err
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(hash.Sum(nil)))
+ req = signer.SignV4STS(*req, opts.AccessKey, opts.SecretKey, opts.Location)
+
+ resp, err := clnt.Do(req)
+ if err != nil {
+ return AssumeRoleResponse{}, err
+ }
+ defer closeResponse(resp)
+ if resp.StatusCode != http.StatusOK {
+ return AssumeRoleResponse{}, errors.New(resp.Status)
+ }
+
+ a := AssumeRoleResponse{}
+ if err = xml.NewDecoder(resp.Body).Decode(&a); err != nil {
+ return AssumeRoleResponse{}, err
+ }
+ return a, nil
+}
+
+// Retrieve retrieves credentials from the MinIO service.
+// Error will be returned if the request fails.
+func (m *STSAssumeRole) Retrieve() (Value, error) {
+ a, err := getAssumeRoleCredentials(m.Client, m.STSEndpoint, m.Options)
+ if err != nil {
+ return Value{}, err
+ }
+
+ // Expiry window is set to 10secs.
+ m.SetExpiration(a.Result.Credentials.Expiration, DefaultExpiryWindow)
+
+ return Value{
+ AccessKeyID: a.Result.Credentials.AccessKey,
+ SecretAccessKey: a.Result.Credentials.SecretKey,
+ SessionToken: a.Result.Credentials.SessionToken,
+ SignerType: SignatureV4,
+ }, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/chain.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/chain.go
new file mode 100644
index 00000000..6dc8e9d0
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/chain.go
@@ -0,0 +1,89 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+// A Chain will search for a provider which returns credentials
+// and cache that provider until Retrieve is called again.
+//
+// The Chain provides a way of chaining multiple providers together
+// which will pick the first available using priority order of the
+// Providers in the list.
+//
+// If none of the Providers retrieve valid credentials Value, ChainProvider's
+// Retrieve() will return the no credentials value.
+//
+// If a Provider is found which returns valid credentials Value ChainProvider
+// will cache that Provider for all calls to IsExpired(), until Retrieve is
+// called again after IsExpired() is true.
+//
+// creds := credentials.NewChainCredentials(
+// []credentials.Provider{
+// &credentials.EnvAWSS3{},
+// &credentials.EnvMinio{},
+// })
+//
+// // Usage of ChainCredentials.
+// mc, err := minio.NewWithCredentials(endpoint, creds, secure, "us-east-1")
+// if err != nil {
+// log.Fatalln(err)
+// }
+//
+type Chain struct {
+ Providers []Provider
+ curr Provider
+}
+
+// NewChainCredentials returns a pointer to a new Credentials object
+// wrapping a chain of providers.
+func NewChainCredentials(providers []Provider) *Credentials {
+ return New(&Chain{
+ Providers: append([]Provider{}, providers...),
+ })
+}
+
+// Retrieve returns the credentials value, returns no credentials(anonymous)
+// if no credentials provider returned any value.
+//
+// If a provider is found with credentials, it will be cached and any calls
+// to IsExpired() will return the expired state of the cached provider.
+func (c *Chain) Retrieve() (Value, error) {
+ for _, p := range c.Providers {
+ creds, _ := p.Retrieve()
+ // Always prioritize non-anonymous providers, if any.
+ if creds.AccessKeyID == "" && creds.SecretAccessKey == "" {
+ continue
+ }
+ c.curr = p
+ return creds, nil
+ }
+ // At this point we have exhausted all the providers and
+ // are left without any credentials return anonymous.
+ return Value{
+ SignerType: SignatureAnonymous,
+ }, nil
+}
+
+// IsExpired will returned the expired state of the currently cached provider
+// if there is one. If there is no current provider, true will be returned.
+func (c *Chain) IsExpired() bool {
+ if c.curr != nil {
+ return c.curr.IsExpired()
+ }
+
+ return true
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/config.json.sample b/vendor/github.com/minio/minio-go/v7/pkg/credentials/config.json.sample
new file mode 100644
index 00000000..d793c9e0
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/config.json.sample
@@ -0,0 +1,17 @@
+{
+ "version": "8",
+ "hosts": {
+ "play": {
+ "url": "https://play.min.io",
+ "accessKey": "Q3AM3UQ867SPQQA43P2F",
+ "secretKey": "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
+ "api": "S3v2"
+ },
+ "s3": {
+ "url": "https://s3.amazonaws.com",
+ "accessKey": "accessKey",
+ "secretKey": "secret",
+ "api": "S3v4"
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.go
new file mode 100644
index 00000000..62d1701e
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.go
@@ -0,0 +1,182 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "sync"
+ "time"
+)
+
+// STSVersion sts version string
+const STSVersion = "2011-06-15"
+
+// A Value is the AWS credentials value for individual credential fields.
+type Value struct {
+ // AWS Access key ID
+ AccessKeyID string
+
+ // AWS Secret Access Key
+ SecretAccessKey string
+
+ // AWS Session Token
+ SessionToken string
+
+ // Signature Type.
+ SignerType SignatureType
+}
+
+// A Provider is the interface for any component which will provide credentials
+// Value. A provider is required to manage its own Expired state, and what to
+// be expired means.
+type Provider interface {
+ // Retrieve returns nil if it successfully retrieved the value.
+ // Error is returned if the value were not obtainable, or empty.
+ Retrieve() (Value, error)
+
+ // IsExpired returns if the credentials are no longer valid, and need
+ // to be retrieved.
+ IsExpired() bool
+}
+
+// A Expiry provides shared expiration logic to be used by credentials
+// providers to implement expiry functionality.
+//
+// The best method to use this struct is as an anonymous field within the
+// provider's struct.
+//
+// Example:
+// type IAMCredentialProvider struct {
+// Expiry
+// ...
+// }
+type Expiry struct {
+ // The date/time when to expire on
+ expiration time.Time
+
+ // If set will be used by IsExpired to determine the current time.
+ // Defaults to time.Now if CurrentTime is not set.
+ CurrentTime func() time.Time
+}
+
+// SetExpiration sets the expiration IsExpired will check when called.
+//
+// If window is greater than 0 the expiration time will be reduced by the
+// window value.
+//
+// Using a window is helpful to trigger credentials to expire sooner than
+// the expiration time given to ensure no requests are made with expired
+// tokens.
+func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) {
+ e.expiration = expiration
+ if window > 0 {
+ e.expiration = e.expiration.Add(-window)
+ }
+}
+
+// IsExpired returns if the credentials are expired.
+func (e *Expiry) IsExpired() bool {
+ if e.CurrentTime == nil {
+ e.CurrentTime = time.Now
+ }
+ return e.expiration.Before(e.CurrentTime())
+}
+
+// Credentials - A container for synchronous safe retrieval of credentials Value.
+// Credentials will cache the credentials value until they expire. Once the value
+// expires the next Get will attempt to retrieve valid credentials.
+//
+// Credentials is safe to use across multiple goroutines and will manage the
+// synchronous state so the Providers do not need to implement their own
+// synchronization.
+//
+// The first Credentials.Get() will always call Provider.Retrieve() to get the
+// first instance of the credentials Value. All calls to Get() after that
+// will return the cached credentials Value until IsExpired() returns true.
+type Credentials struct {
+ sync.Mutex
+
+ creds Value
+ forceRefresh bool
+ provider Provider
+}
+
+// New returns a pointer to a new Credentials with the provider set.
+func New(provider Provider) *Credentials {
+ return &Credentials{
+ provider: provider,
+ forceRefresh: true,
+ }
+}
+
+// Get returns the credentials value, or error if the credentials Value failed
+// to be retrieved.
+//
+// Will return the cached credentials Value if it has not expired. If the
+// credentials Value has expired the Provider's Retrieve() will be called
+// to refresh the credentials.
+//
+// If Credentials.Expire() was called the credentials Value will be force
+// expired, and the next call to Get() will cause them to be refreshed.
+func (c *Credentials) Get() (Value, error) {
+ if c == nil {
+ return Value{}, nil
+ }
+
+ c.Lock()
+ defer c.Unlock()
+
+ if c.isExpired() {
+ creds, err := c.provider.Retrieve()
+ if err != nil {
+ return Value{}, err
+ }
+ c.creds = creds
+ c.forceRefresh = false
+ }
+
+ return c.creds, nil
+}
+
+// Expire expires the credentials and forces them to be retrieved on the
+// next call to Get().
+//
+// This will override the Provider's expired state, and force Credentials
+// to call the Provider's Retrieve().
+func (c *Credentials) Expire() {
+ c.Lock()
+ defer c.Unlock()
+
+ c.forceRefresh = true
+}
+
+// IsExpired returns if the credentials are no longer valid, and need
+// to be refreshed.
+//
+// If the Credentials were forced to be expired with Expire() this will
+// reflect that override.
+func (c *Credentials) IsExpired() bool {
+ c.Lock()
+ defer c.Unlock()
+
+ return c.isExpired()
+}
+
+// isExpired helper method wrapping the definition of expired credentials.
+func (c *Credentials) isExpired() bool {
+ return c.forceRefresh || c.provider.IsExpired()
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.sample b/vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.sample
new file mode 100644
index 00000000..7fc91d9d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/credentials.sample
@@ -0,0 +1,12 @@
+[default]
+aws_access_key_id = accessKey
+aws_secret_access_key = secret
+aws_session_token = token
+
+[no_token]
+aws_access_key_id = accessKey
+aws_secret_access_key = secret
+
+[with_colon]
+aws_access_key_id: accessKey
+aws_secret_access_key: secret
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/doc.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/doc.go
new file mode 100644
index 00000000..0c94477b
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/doc.go
@@ -0,0 +1,62 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+// Package credentials provides credential retrieval and management
+// for S3 compatible object storage.
+//
+// By default the Credentials.Get() will cache the successful result of a
+// Provider's Retrieve() until Provider.IsExpired() returns true. At which
+// point Credentials will call Provider's Retrieve() to get new credential Value.
+//
+// The Provider is responsible for determining when credentials have expired.
+// It is also important to note that Credentials will always call Retrieve the
+// first time Credentials.Get() is called.
+//
+// Example of using the environment variable credentials.
+//
+// creds := NewFromEnv()
+// // Retrieve the credentials value
+// credValue, err := creds.Get()
+// if err != nil {
+// // handle error
+// }
+//
+// Example of forcing credentials to expire and be refreshed on the next Get().
+// This may be helpful to proactively expire credentials and refresh them sooner
+// than they would naturally expire on their own.
+//
+// creds := NewFromIAM("")
+// creds.Expire()
+// credsValue, err := creds.Get()
+// // New credentials will be retrieved instead of from cache.
+//
+//
+// Custom Provider
+//
+// Each Provider built into this package also provides a helper method to generate
+// a Credentials pointer setup with the provider. To use a custom Provider just
+// create a type which satisfies the Provider interface and pass it to the
+// NewCredentials method.
+//
+// type MyProvider struct{}
+// func (m *MyProvider) Retrieve() (Value, error) {...}
+// func (m *MyProvider) IsExpired() bool {...}
+//
+// creds := NewCredentials(&MyProvider{})
+// credValue, err := creds.Get()
+//
+package credentials
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/env_aws.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/env_aws.go
new file mode 100644
index 00000000..b6e60d0e
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/env_aws.go
@@ -0,0 +1,71 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import "os"
+
+// A EnvAWS retrieves credentials from the environment variables of the
+// running process. EnvAWSironment credentials never expire.
+//
+// EnvAWSironment variables used:
+//
+// * Access Key ID: AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY.
+// * Secret Access Key: AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY.
+// * Secret Token: AWS_SESSION_TOKEN.
+type EnvAWS struct {
+ retrieved bool
+}
+
+// NewEnvAWS returns a pointer to a new Credentials object
+// wrapping the environment variable provider.
+func NewEnvAWS() *Credentials {
+ return New(&EnvAWS{})
+}
+
+// Retrieve retrieves the keys from the environment.
+func (e *EnvAWS) Retrieve() (Value, error) {
+ e.retrieved = false
+
+ id := os.Getenv("AWS_ACCESS_KEY_ID")
+ if id == "" {
+ id = os.Getenv("AWS_ACCESS_KEY")
+ }
+
+ secret := os.Getenv("AWS_SECRET_ACCESS_KEY")
+ if secret == "" {
+ secret = os.Getenv("AWS_SECRET_KEY")
+ }
+
+ signerType := SignatureV4
+ if id == "" || secret == "" {
+ signerType = SignatureAnonymous
+ }
+
+ e.retrieved = true
+ return Value{
+ AccessKeyID: id,
+ SecretAccessKey: secret,
+ SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
+ SignerType: signerType,
+ }, nil
+}
+
+// IsExpired returns if the credentials have been retrieved.
+func (e *EnvAWS) IsExpired() bool {
+ return !e.retrieved
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/env_minio.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/env_minio.go
new file mode 100644
index 00000000..5bfeab14
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/env_minio.go
@@ -0,0 +1,68 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import "os"
+
+// A EnvMinio retrieves credentials from the environment variables of the
+// running process. EnvMinioironment credentials never expire.
+//
+// Environment variables used:
+//
+// * Access Key ID: MINIO_ACCESS_KEY.
+// * Secret Access Key: MINIO_SECRET_KEY.
+// * Access Key ID: MINIO_ROOT_USER.
+// * Secret Access Key: MINIO_ROOT_PASSWORD.
+type EnvMinio struct {
+ retrieved bool
+}
+
+// NewEnvMinio returns a pointer to a new Credentials object
+// wrapping the environment variable provider.
+func NewEnvMinio() *Credentials {
+ return New(&EnvMinio{})
+}
+
+// Retrieve retrieves the keys from the environment.
+func (e *EnvMinio) Retrieve() (Value, error) {
+ e.retrieved = false
+
+ id := os.Getenv("MINIO_ROOT_USER")
+ secret := os.Getenv("MINIO_ROOT_PASSWORD")
+
+ signerType := SignatureV4
+ if id == "" || secret == "" {
+ id = os.Getenv("MINIO_ACCESS_KEY")
+ secret = os.Getenv("MINIO_SECRET_KEY")
+ if id == "" || secret == "" {
+ signerType = SignatureAnonymous
+ }
+ }
+
+ e.retrieved = true
+ return Value{
+ AccessKeyID: id,
+ SecretAccessKey: secret,
+ SignerType: signerType,
+ }, nil
+}
+
+// IsExpired returns if the credentials have been retrieved.
+func (e *EnvMinio) IsExpired() bool {
+ return !e.retrieved
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go
new file mode 100644
index 00000000..ccc8251f
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_aws_credentials.go
@@ -0,0 +1,120 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "os"
+ "path/filepath"
+
+ homedir "github.com/mitchellh/go-homedir"
+ ini "gopkg.in/ini.v1"
+)
+
+// A FileAWSCredentials retrieves credentials from the current user's home
+// directory, and keeps track if those credentials are expired.
+//
+// Profile ini file example: $HOME/.aws/credentials
+type FileAWSCredentials struct {
+ // Path to the shared credentials file.
+ //
+ // If empty will look for "AWS_SHARED_CREDENTIALS_FILE" env variable. If the
+ // env value is empty will default to current user's home directory.
+ // Linux/OSX: "$HOME/.aws/credentials"
+ // Windows: "%USERPROFILE%\.aws\credentials"
+ Filename string
+
+ // AWS Profile to extract credentials from the shared credentials file. If empty
+ // will default to environment variable "AWS_PROFILE" or "default" if
+ // environment variable is also not set.
+ Profile string
+
+ // retrieved states if the credentials have been successfully retrieved.
+ retrieved bool
+}
+
+// NewFileAWSCredentials returns a pointer to a new Credentials object
+// wrapping the Profile file provider.
+func NewFileAWSCredentials(filename string, profile string) *Credentials {
+ return New(&FileAWSCredentials{
+ Filename: filename,
+ Profile: profile,
+ })
+}
+
+// Retrieve reads and extracts the shared credentials from the current
+// users home directory.
+func (p *FileAWSCredentials) Retrieve() (Value, error) {
+ if p.Filename == "" {
+ p.Filename = os.Getenv("AWS_SHARED_CREDENTIALS_FILE")
+ if p.Filename == "" {
+ homeDir, err := homedir.Dir()
+ if err != nil {
+ return Value{}, err
+ }
+ p.Filename = filepath.Join(homeDir, ".aws", "credentials")
+ }
+ }
+ if p.Profile == "" {
+ p.Profile = os.Getenv("AWS_PROFILE")
+ if p.Profile == "" {
+ p.Profile = "default"
+ }
+ }
+
+ p.retrieved = false
+
+ iniProfile, err := loadProfile(p.Filename, p.Profile)
+ if err != nil {
+ return Value{}, err
+ }
+
+ // Default to empty string if not found.
+ id := iniProfile.Key("aws_access_key_id")
+ // Default to empty string if not found.
+ secret := iniProfile.Key("aws_secret_access_key")
+ // Default to empty string if not found.
+ token := iniProfile.Key("aws_session_token")
+
+ p.retrieved = true
+ return Value{
+ AccessKeyID: id.String(),
+ SecretAccessKey: secret.String(),
+ SessionToken: token.String(),
+ SignerType: SignatureV4,
+ }, nil
+}
+
+// IsExpired returns if the shared credentials have expired.
+func (p *FileAWSCredentials) IsExpired() bool {
+ return !p.retrieved
+}
+
+// loadProfiles loads from the file pointed to by shared credentials filename for profile.
+// The credentials retrieved from the profile will be returned or error. Error will be
+// returned if it fails to read from the file, or the data is invalid.
+func loadProfile(filename, profile string) (*ini.Section, error) {
+ config, err := ini.Load(filename)
+ if err != nil {
+ return nil, err
+ }
+ iniProfile, err := config.GetSection(profile)
+ if err != nil {
+ return nil, err
+ }
+ return iniProfile, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_minio_client.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_minio_client.go
new file mode 100644
index 00000000..ca6db005
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/file_minio_client.go
@@ -0,0 +1,135 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+
+ jsoniter "github.com/json-iterator/go"
+ homedir "github.com/mitchellh/go-homedir"
+)
+
+// A FileMinioClient retrieves credentials from the current user's home
+// directory, and keeps track if those credentials are expired.
+//
+// Configuration file example: $HOME/.mc/config.json
+type FileMinioClient struct {
+ // Path to the shared credentials file.
+ //
+ // If empty will look for "MINIO_SHARED_CREDENTIALS_FILE" env variable. If the
+ // env value is empty will default to current user's home directory.
+ // Linux/OSX: "$HOME/.mc/config.json"
+ // Windows: "%USERALIAS%\mc\config.json"
+ Filename string
+
+ // MinIO Alias to extract credentials from the shared credentials file. If empty
+ // will default to environment variable "MINIO_ALIAS" or "default" if
+ // environment variable is also not set.
+ Alias string
+
+ // retrieved states if the credentials have been successfully retrieved.
+ retrieved bool
+}
+
+// NewFileMinioClient returns a pointer to a new Credentials object
+// wrapping the Alias file provider.
+func NewFileMinioClient(filename string, alias string) *Credentials {
+ return New(&FileMinioClient{
+ Filename: filename,
+ Alias: alias,
+ })
+}
+
+// Retrieve reads and extracts the shared credentials from the current
+// users home directory.
+func (p *FileMinioClient) Retrieve() (Value, error) {
+ if p.Filename == "" {
+ if value, ok := os.LookupEnv("MINIO_SHARED_CREDENTIALS_FILE"); ok {
+ p.Filename = value
+ } else {
+ homeDir, err := homedir.Dir()
+ if err != nil {
+ return Value{}, err
+ }
+ p.Filename = filepath.Join(homeDir, ".mc", "config.json")
+ if runtime.GOOS == "windows" {
+ p.Filename = filepath.Join(homeDir, "mc", "config.json")
+ }
+ }
+ }
+
+ if p.Alias == "" {
+ p.Alias = os.Getenv("MINIO_ALIAS")
+ if p.Alias == "" {
+ p.Alias = "s3"
+ }
+ }
+
+ p.retrieved = false
+
+ hostCfg, err := loadAlias(p.Filename, p.Alias)
+ if err != nil {
+ return Value{}, err
+ }
+
+ p.retrieved = true
+ return Value{
+ AccessKeyID: hostCfg.AccessKey,
+ SecretAccessKey: hostCfg.SecretKey,
+ SignerType: parseSignatureType(hostCfg.API),
+ }, nil
+}
+
+// IsExpired returns if the shared credentials have expired.
+func (p *FileMinioClient) IsExpired() bool {
+ return !p.retrieved
+}
+
+// hostConfig configuration of a host.
+type hostConfig struct {
+ URL string `json:"url"`
+ AccessKey string `json:"accessKey"`
+ SecretKey string `json:"secretKey"`
+ API string `json:"api"`
+}
+
+// config config version.
+type config struct {
+ Version string `json:"version"`
+ Hosts map[string]hostConfig `json:"hosts"`
+}
+
+// loadAliass loads from the file pointed to by shared credentials filename for alias.
+// The credentials retrieved from the alias will be returned or error. Error will be
+// returned if it fails to read from the file.
+func loadAlias(filename, alias string) (hostConfig, error) {
+ cfg := &config{}
+ var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+ configBytes, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return hostConfig{}, err
+ }
+ if err = json.Unmarshal(configBytes, cfg); err != nil {
+ return hostConfig{}, err
+ }
+ return cfg.Hosts[alias], nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go
new file mode 100644
index 00000000..b532bcb6
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/iam_aws.go
@@ -0,0 +1,367 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ jsoniter "github.com/json-iterator/go"
+)
+
+// DefaultExpiryWindow - Default expiry window.
+// ExpiryWindow will allow the credentials to trigger refreshing
+// prior to the credentials actually expiring. This is beneficial
+// so race conditions with expiring credentials do not cause
+// request to fail unexpectedly due to ExpiredTokenException exceptions.
+const DefaultExpiryWindow = time.Second * 10 // 10 secs
+
+// A IAM retrieves credentials from the EC2 service, and keeps track if
+// those credentials are expired.
+type IAM struct {
+ Expiry
+
+ // Required http Client to use when connecting to IAM metadata service.
+ Client *http.Client
+
+ // Custom endpoint to fetch IAM role credentials.
+ Endpoint string
+}
+
+// IAM Roles for Amazon EC2
+// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+const (
+ defaultIAMRoleEndpoint = "http://169.254.169.254"
+ defaultECSRoleEndpoint = "http://169.254.170.2"
+ defaultSTSRoleEndpoint = "https://sts.amazonaws.com"
+ defaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials/"
+ tokenRequestTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
+ tokenPath = "/latest/api/token"
+ tokenTTL = "21600"
+ tokenRequestHeader = "X-aws-ec2-metadata-token"
+)
+
+// NewIAM returns a pointer to a new Credentials object wrapping the IAM.
+func NewIAM(endpoint string) *Credentials {
+ return New(&IAM{
+ Client: &http.Client{
+ Transport: http.DefaultTransport,
+ },
+ Endpoint: endpoint,
+ })
+}
+
+// Retrieve retrieves credentials from the EC2 service.
+// Error will be returned if the request fails, or unable to extract
+// the desired
+func (m *IAM) Retrieve() (Value, error) {
+ token := os.Getenv("AWS_CONTAINER_AUTHORIZATION_TOKEN")
+ var roleCreds ec2RoleCredRespBody
+ var err error
+
+ endpoint := m.Endpoint
+ switch {
+ case len(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")) > 0:
+ if len(endpoint) == 0 {
+ if len(os.Getenv("AWS_REGION")) > 0 {
+ if strings.HasPrefix(os.Getenv("AWS_REGION"), "cn-") {
+ endpoint = "https://sts." + os.Getenv("AWS_REGION") + ".amazonaws.com.cn"
+ } else {
+ endpoint = "https://sts." + os.Getenv("AWS_REGION") + ".amazonaws.com"
+ }
+ } else {
+ endpoint = defaultSTSRoleEndpoint
+ }
+ }
+
+ creds := &STSWebIdentity{
+ Client: m.Client,
+ STSEndpoint: endpoint,
+ GetWebIDTokenExpiry: func() (*WebIdentityToken, error) {
+ token, err := ioutil.ReadFile(os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE"))
+ if err != nil {
+ return nil, err
+ }
+
+ return &WebIdentityToken{Token: string(token)}, nil
+ },
+ roleARN: os.Getenv("AWS_ROLE_ARN"),
+ roleSessionName: os.Getenv("AWS_ROLE_SESSION_NAME"),
+ }
+
+ stsWebIdentityCreds, err := creds.Retrieve()
+ if err == nil {
+ m.SetExpiration(creds.Expiration(), DefaultExpiryWindow)
+ }
+ return stsWebIdentityCreds, err
+
+ case len(os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) > 0:
+ if len(endpoint) == 0 {
+ endpoint = fmt.Sprintf("%s%s", defaultECSRoleEndpoint,
+ os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"))
+ }
+
+ roleCreds, err = getEcsTaskCredentials(m.Client, endpoint, token)
+
+ case len(os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")) > 0:
+ if len(endpoint) == 0 {
+ endpoint = os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")
+ var ok bool
+ if ok, err = isLoopback(endpoint); !ok {
+ if err == nil {
+ err = fmt.Errorf("uri host is not a loopback address: %s", endpoint)
+ }
+ break
+ }
+ }
+
+ roleCreds, err = getEcsTaskCredentials(m.Client, endpoint, token)
+
+ default:
+ roleCreds, err = getCredentials(m.Client, endpoint)
+ }
+
+ if err != nil {
+ return Value{}, err
+ }
+ // Expiry window is set to 10secs.
+ m.SetExpiration(roleCreds.Expiration, DefaultExpiryWindow)
+
+ return Value{
+ AccessKeyID: roleCreds.AccessKeyID,
+ SecretAccessKey: roleCreds.SecretAccessKey,
+ SessionToken: roleCreds.Token,
+ SignerType: SignatureV4,
+ }, nil
+}
+
+// A ec2RoleCredRespBody provides the shape for unmarshaling credential
+// request responses.
+type ec2RoleCredRespBody struct {
+ // Success State
+ Expiration time.Time
+ AccessKeyID string
+ SecretAccessKey string
+ Token string
+
+ // Error state
+ Code string
+ Message string
+
+ // Unused params.
+ LastUpdated time.Time
+ Type string
+}
+
+// Get the final IAM role URL where the request will
+// be sent to fetch the rolling access credentials.
+// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+func getIAMRoleURL(endpoint string) (*url.URL, error) {
+ if endpoint == "" {
+ endpoint = defaultIAMRoleEndpoint
+ }
+
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return nil, err
+ }
+ u.Path = defaultIAMSecurityCredsPath
+ return u, nil
+}
+
+// listRoleNames lists of credential role names associated
+// with the current EC2 service. If there are no credentials,
+// or there is an error making or receiving the request.
+// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+func listRoleNames(client *http.Client, u *url.URL, token string) ([]string, error) {
+ req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+ if token != "" {
+ req.Header.Add(tokenRequestHeader, token)
+ }
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, errors.New(resp.Status)
+ }
+
+ credsList := []string{}
+ s := bufio.NewScanner(resp.Body)
+ for s.Scan() {
+ credsList = append(credsList, s.Text())
+ }
+
+ if err := s.Err(); err != nil {
+ return nil, err
+ }
+
+ return credsList, nil
+}
+
+func getEcsTaskCredentials(client *http.Client, endpoint string, token string) (ec2RoleCredRespBody, error) {
+ req, err := http.NewRequest(http.MethodGet, endpoint, nil)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ if token != "" {
+ req.Header.Set("Authorization", token)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return ec2RoleCredRespBody{}, errors.New(resp.Status)
+ }
+
+ respCreds := ec2RoleCredRespBody{}
+ if err := jsoniter.NewDecoder(resp.Body).Decode(&respCreds); err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ return respCreds, nil
+}
+
+func fetchIMDSToken(client *http.Client, endpoint string) (string, error) {
+ req, err := http.NewRequest(http.MethodPut, endpoint+tokenPath, nil)
+ if err != nil {
+ return "", err
+ }
+ req.Header.Add(tokenRequestTTLHeader, tokenTTL)
+ resp, err := client.Do(req)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return "", errors.New(resp.Status)
+ }
+ return string(data), nil
+}
+
+// getCredentials - obtains the credentials from the IAM role name associated with
+// the current EC2 service.
+//
+// If the credentials cannot be found, or there is an error
+// reading the response an error will be returned.
+func getCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody, error) {
+ // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
+ token, _ := fetchIMDSToken(client, endpoint)
+
+ // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+ u, err := getIAMRoleURL(endpoint)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+ roleNames, err := listRoleNames(client, u, token)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ if len(roleNames) == 0 {
+ return ec2RoleCredRespBody{}, errors.New("No IAM roles attached to this EC2 service")
+ }
+
+ // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+ // - An instance profile can contain only one IAM role. This limit cannot be increased.
+ roleName := roleNames[0]
+
+ // http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
+ // The following command retrieves the security credentials for an
+ // IAM role named `s3access`.
+ //
+ // $ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/s3access
+ //
+ u.Path = path.Join(u.Path, roleName)
+ req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+ if token != "" {
+ req.Header.Add(tokenRequestHeader, token)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return ec2RoleCredRespBody{}, errors.New(resp.Status)
+ }
+
+ respCreds := ec2RoleCredRespBody{}
+ if err := jsoniter.NewDecoder(resp.Body).Decode(&respCreds); err != nil {
+ return ec2RoleCredRespBody{}, err
+ }
+
+ if respCreds.Code != "Success" {
+ // If an error code was returned something failed requesting the role.
+ return ec2RoleCredRespBody{}, errors.New(respCreds.Message)
+ }
+
+ return respCreds, nil
+}
+
+// isLoopback identifies if a uri's host is on a loopback address
+func isLoopback(uri string) (bool, error) {
+ u, err := url.Parse(uri)
+ if err != nil {
+ return false, err
+ }
+
+ host := u.Hostname()
+ if len(host) == 0 {
+ return false, fmt.Errorf("can't parse host from uri: %s", uri)
+ }
+
+ ips, err := net.LookupHost(host)
+ if err != nil {
+ return false, err
+ }
+ for _, ip := range ips {
+ if !net.ParseIP(ip).IsLoopback() {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/signature-type.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/signature-type.go
new file mode 100644
index 00000000..b7943330
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/signature-type.go
@@ -0,0 +1,77 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+import "strings"
+
+// SignatureType is type of Authorization requested for a given HTTP request.
+type SignatureType int
+
+// Different types of supported signatures - default is SignatureV4 or SignatureDefault.
+const (
+ // SignatureDefault is always set to v4.
+ SignatureDefault SignatureType = iota
+ SignatureV4
+ SignatureV2
+ SignatureV4Streaming
+ SignatureAnonymous // Anonymous signature signifies, no signature.
+)
+
+// IsV2 - is signature SignatureV2?
+func (s SignatureType) IsV2() bool {
+ return s == SignatureV2
+}
+
+// IsV4 - is signature SignatureV4?
+func (s SignatureType) IsV4() bool {
+ return s == SignatureV4 || s == SignatureDefault
+}
+
+// IsStreamingV4 - is signature SignatureV4Streaming?
+func (s SignatureType) IsStreamingV4() bool {
+ return s == SignatureV4Streaming
+}
+
+// IsAnonymous - is signature empty?
+func (s SignatureType) IsAnonymous() bool {
+ return s == SignatureAnonymous
+}
+
+// Stringer humanized version of signature type,
+// strings returned here are case insensitive.
+func (s SignatureType) String() string {
+ if s.IsV2() {
+ return "S3v2"
+ } else if s.IsV4() {
+ return "S3v4"
+ } else if s.IsStreamingV4() {
+ return "S3v4Streaming"
+ }
+ return "Anonymous"
+}
+
+func parseSignatureType(str string) SignatureType {
+ if strings.EqualFold(str, "S3v4") {
+ return SignatureV4
+ } else if strings.EqualFold(str, "S3v2") {
+ return SignatureV2
+ } else if strings.EqualFold(str, "S3v4Streaming") {
+ return SignatureV4Streaming
+ }
+ return SignatureAnonymous
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/static.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/static.go
new file mode 100644
index 00000000..7dde00b0
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/static.go
@@ -0,0 +1,67 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package credentials
+
+// A Static is a set of credentials which are set programmatically,
+// and will never expire.
+type Static struct {
+ Value
+}
+
+// NewStaticV2 returns a pointer to a new Credentials object
+// wrapping a static credentials value provider, signature is
+// set to v2. If access and secret are not specified then
+// regardless of signature type set it Value will return
+// as anonymous.
+func NewStaticV2(id, secret, token string) *Credentials {
+ return NewStatic(id, secret, token, SignatureV2)
+}
+
+// NewStaticV4 is similar to NewStaticV2 with similar considerations.
+func NewStaticV4(id, secret, token string) *Credentials {
+ return NewStatic(id, secret, token, SignatureV4)
+}
+
+// NewStatic returns a pointer to a new Credentials object
+// wrapping a static credentials value provider.
+func NewStatic(id, secret, token string, signerType SignatureType) *Credentials {
+ return New(&Static{
+ Value: Value{
+ AccessKeyID: id,
+ SecretAccessKey: secret,
+ SessionToken: token,
+ SignerType: signerType,
+ },
+ })
+}
+
+// Retrieve returns the static credentials.
+func (s *Static) Retrieve() (Value, error) {
+ if s.AccessKeyID == "" || s.SecretAccessKey == "" {
+ // Anonymous is not an error
+ return Value{SignerType: SignatureAnonymous}, nil
+ }
+ return s.Value, nil
+}
+
+// IsExpired returns if the credentials are expired.
+//
+// For Static, the credentials never expired.
+func (s *Static) IsExpired() bool {
+ return false
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_client_grants.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_client_grants.go
new file mode 100644
index 00000000..b79f920f
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_client_grants.go
@@ -0,0 +1,162 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2019 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "time"
+)
+
+// AssumedRoleUser - The identifiers for the temporary security credentials that
+// the operation returns. Please also see https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15/AssumedRoleUser
+type AssumedRoleUser struct {
+ Arn string
+ AssumedRoleID string `xml:"AssumeRoleId"`
+}
+
+// AssumeRoleWithClientGrantsResponse contains the result of successful AssumeRoleWithClientGrants request.
+type AssumeRoleWithClientGrantsResponse struct {
+ XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleWithClientGrantsResponse" json:"-"`
+ Result ClientGrantsResult `xml:"AssumeRoleWithClientGrantsResult"`
+ ResponseMetadata struct {
+ RequestID string `xml:"RequestId,omitempty"`
+ } `xml:"ResponseMetadata,omitempty"`
+}
+
+// ClientGrantsResult - Contains the response to a successful AssumeRoleWithClientGrants
+// request, including temporary credentials that can be used to make MinIO API requests.
+type ClientGrantsResult struct {
+ AssumedRoleUser AssumedRoleUser `xml:",omitempty"`
+ Audience string `xml:",omitempty"`
+ Credentials struct {
+ AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"`
+ SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"`
+ Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
+ SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
+ } `xml:",omitempty"`
+ PackedPolicySize int `xml:",omitempty"`
+ Provider string `xml:",omitempty"`
+ SubjectFromClientGrantsToken string `xml:",omitempty"`
+}
+
+// ClientGrantsToken - client grants token with expiry.
+type ClientGrantsToken struct {
+ Token string
+ Expiry int
+}
+
+// A STSClientGrants retrieves credentials from MinIO service, and keeps track if
+// those credentials are expired.
+type STSClientGrants struct {
+ Expiry
+
+ // Required http Client to use when connecting to MinIO STS service.
+ Client *http.Client
+
+ // MinIO endpoint to fetch STS credentials.
+ STSEndpoint string
+
+ // getClientGrantsTokenExpiry function to retrieve tokens
+ // from IDP This function should return two values one is
+ // accessToken which is a self contained access token (JWT)
+ // and second return value is the expiry associated with
+ // this token. This is a customer provided function and
+ // is mandatory.
+ GetClientGrantsTokenExpiry func() (*ClientGrantsToken, error)
+}
+
+// NewSTSClientGrants returns a pointer to a new
+// Credentials object wrapping the STSClientGrants.
+func NewSTSClientGrants(stsEndpoint string, getClientGrantsTokenExpiry func() (*ClientGrantsToken, error)) (*Credentials, error) {
+ if stsEndpoint == "" {
+ return nil, errors.New("STS endpoint cannot be empty")
+ }
+ if getClientGrantsTokenExpiry == nil {
+ return nil, errors.New("Client grants access token and expiry retrieval function should be defined")
+ }
+ return New(&STSClientGrants{
+ Client: &http.Client{
+ Transport: http.DefaultTransport,
+ },
+ STSEndpoint: stsEndpoint,
+ GetClientGrantsTokenExpiry: getClientGrantsTokenExpiry,
+ }), nil
+}
+
+func getClientGrantsCredentials(clnt *http.Client, endpoint string,
+ getClientGrantsTokenExpiry func() (*ClientGrantsToken, error)) (AssumeRoleWithClientGrantsResponse, error) {
+
+ accessToken, err := getClientGrantsTokenExpiry()
+ if err != nil {
+ return AssumeRoleWithClientGrantsResponse{}, err
+ }
+
+ v := url.Values{}
+ v.Set("Action", "AssumeRoleWithClientGrants")
+ v.Set("Token", accessToken.Token)
+ v.Set("DurationSeconds", fmt.Sprintf("%d", accessToken.Expiry))
+ v.Set("Version", STSVersion)
+
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return AssumeRoleWithClientGrantsResponse{}, err
+ }
+ u.RawQuery = v.Encode()
+
+ req, err := http.NewRequest(http.MethodPost, u.String(), nil)
+ if err != nil {
+ return AssumeRoleWithClientGrantsResponse{}, err
+ }
+ resp, err := clnt.Do(req)
+ if err != nil {
+ return AssumeRoleWithClientGrantsResponse{}, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return AssumeRoleWithClientGrantsResponse{}, errors.New(resp.Status)
+ }
+
+ a := AssumeRoleWithClientGrantsResponse{}
+ if err = xml.NewDecoder(resp.Body).Decode(&a); err != nil {
+ return AssumeRoleWithClientGrantsResponse{}, err
+ }
+ return a, nil
+}
+
+// Retrieve retrieves credentials from the MinIO service.
+// Error will be returned if the request fails.
+func (m *STSClientGrants) Retrieve() (Value, error) {
+ a, err := getClientGrantsCredentials(m.Client, m.STSEndpoint, m.GetClientGrantsTokenExpiry)
+ if err != nil {
+ return Value{}, err
+ }
+
+ // Expiry window is set to 10secs.
+ m.SetExpiration(a.Result.Credentials.Expiration, DefaultExpiryWindow)
+
+ return Value{
+ AccessKeyID: a.Result.Credentials.AccessKey,
+ SecretAccessKey: a.Result.Credentials.SecretKey,
+ SessionToken: a.Result.Credentials.SessionToken,
+ SignerType: SignatureV4,
+ }, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_ldap_identity.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_ldap_identity.go
new file mode 100644
index 00000000..bcb3c36a
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_ldap_identity.go
@@ -0,0 +1,124 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2019 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "encoding/xml"
+ "errors"
+ "net/http"
+ "net/url"
+ "time"
+)
+
+// AssumeRoleWithLDAPResponse contains the result of successful
+// AssumeRoleWithLDAPIdentity request
+type AssumeRoleWithLDAPResponse struct {
+ XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleWithLDAPIdentityResponse" json:"-"`
+ Result LDAPIdentityResult `xml:"AssumeRoleWithLDAPIdentityResult"`
+ ResponseMetadata struct {
+ RequestID string `xml:"RequestId,omitempty"`
+ } `xml:"ResponseMetadata,omitempty"`
+}
+
+// LDAPIdentityResult - contains credentials for a successful
+// AssumeRoleWithLDAPIdentity request.
+type LDAPIdentityResult struct {
+ Credentials struct {
+ AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"`
+ SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"`
+ Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
+ SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
+ } `xml:",omitempty"`
+
+ SubjectFromToken string `xml:",omitempty"`
+}
+
+// LDAPIdentity retrieves credentials from MinIO
+type LDAPIdentity struct {
+ Expiry
+
+ // Required http Client to use when connecting to MinIO STS service.
+ Client *http.Client
+
+ // Exported STS endpoint to fetch STS credentials.
+ STSEndpoint string
+
+ // LDAP username/password used to fetch LDAP STS credentials.
+ LDAPUsername, LDAPPassword string
+}
+
+// NewLDAPIdentity returns new credentials object that uses LDAP
+// Identity.
+func NewLDAPIdentity(stsEndpoint, ldapUsername, ldapPassword string) (*Credentials, error) {
+ return New(&LDAPIdentity{
+ Client: &http.Client{Transport: http.DefaultTransport},
+ STSEndpoint: stsEndpoint,
+ LDAPUsername: ldapUsername,
+ LDAPPassword: ldapPassword,
+ }), nil
+}
+
+// Retrieve gets the credential by calling the MinIO STS API for
+// LDAP on the configured stsEndpoint.
+func (k *LDAPIdentity) Retrieve() (value Value, err error) {
+ u, kerr := url.Parse(k.STSEndpoint)
+ if kerr != nil {
+ err = kerr
+ return
+ }
+
+ v := url.Values{}
+ v.Set("Action", "AssumeRoleWithLDAPIdentity")
+ v.Set("Version", STSVersion)
+ v.Set("LDAPUsername", k.LDAPUsername)
+ v.Set("LDAPPassword", k.LDAPPassword)
+
+ u.RawQuery = v.Encode()
+
+ req, kerr := http.NewRequest(http.MethodPost, u.String(), nil)
+ if kerr != nil {
+ err = kerr
+ return
+ }
+
+ resp, kerr := k.Client.Do(req)
+ if kerr != nil {
+ err = kerr
+ return
+ }
+
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ err = errors.New(resp.Status)
+ return
+ }
+
+ r := AssumeRoleWithLDAPResponse{}
+ if err = xml.NewDecoder(resp.Body).Decode(&r); err != nil {
+ return
+ }
+
+ cr := r.Result.Credentials
+ k.SetExpiration(cr.Expiration, DefaultExpiryWindow)
+ return Value{
+ AccessKeyID: cr.AccessKey,
+ SecretAccessKey: cr.SecretKey,
+ SessionToken: cr.SessionToken,
+ SignerType: SignatureV4,
+ }, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go
new file mode 100644
index 00000000..161ffd36
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/credentials/sts_web_identity.go
@@ -0,0 +1,181 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2019 MinIO, 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.
+ */
+
+package credentials
+
+import (
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "time"
+)
+
+// AssumeRoleWithWebIdentityResponse contains the result of successful AssumeRoleWithWebIdentity request.
+type AssumeRoleWithWebIdentityResponse struct {
+ XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleWithWebIdentityResponse" json:"-"`
+ Result WebIdentityResult `xml:"AssumeRoleWithWebIdentityResult"`
+ ResponseMetadata struct {
+ RequestID string `xml:"RequestId,omitempty"`
+ } `xml:"ResponseMetadata,omitempty"`
+}
+
+// WebIdentityResult - Contains the response to a successful AssumeRoleWithWebIdentity
+// request, including temporary credentials that can be used to make MinIO API requests.
+type WebIdentityResult struct {
+ AssumedRoleUser AssumedRoleUser `xml:",omitempty"`
+ Audience string `xml:",omitempty"`
+ Credentials struct {
+ AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"`
+ SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"`
+ Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
+ SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
+ } `xml:",omitempty"`
+ PackedPolicySize int `xml:",omitempty"`
+ Provider string `xml:",omitempty"`
+ SubjectFromWebIdentityToken string `xml:",omitempty"`
+}
+
+// WebIdentityToken - web identity token with expiry.
+type WebIdentityToken struct {
+ Token string
+ Expiry int
+}
+
+// A STSWebIdentity retrieves credentials from MinIO service, and keeps track if
+// those credentials are expired.
+type STSWebIdentity struct {
+ Expiry
+
+ // Required http Client to use when connecting to MinIO STS service.
+ Client *http.Client
+
+ // Exported STS endpoint to fetch STS credentials.
+ STSEndpoint string
+
+ // Exported GetWebIDTokenExpiry function which returns ID
+ // tokens from IDP. This function should return two values
+ // one is ID token which is a self contained ID token (JWT)
+ // and second return value is the expiry associated with
+ // this token.
+ // This is a customer provided function and is mandatory.
+ GetWebIDTokenExpiry func() (*WebIdentityToken, error)
+
+ // roleARN is the Amazon Resource Name (ARN) of the role that the caller is
+ // assuming.
+ roleARN string
+
+ // roleSessionName is the identifier for the assumed role session.
+ roleSessionName string
+}
+
+// NewSTSWebIdentity returns a pointer to a new
+// Credentials object wrapping the STSWebIdentity.
+func NewSTSWebIdentity(stsEndpoint string, getWebIDTokenExpiry func() (*WebIdentityToken, error)) (*Credentials, error) {
+ if stsEndpoint == "" {
+ return nil, errors.New("STS endpoint cannot be empty")
+ }
+ if getWebIDTokenExpiry == nil {
+ return nil, errors.New("Web ID token and expiry retrieval function should be defined")
+ }
+ return New(&STSWebIdentity{
+ Client: &http.Client{
+ Transport: http.DefaultTransport,
+ },
+ STSEndpoint: stsEndpoint,
+ GetWebIDTokenExpiry: getWebIDTokenExpiry,
+ }), nil
+}
+
+func getWebIdentityCredentials(clnt *http.Client, endpoint, roleARN, roleSessionName string,
+ getWebIDTokenExpiry func() (*WebIdentityToken, error)) (AssumeRoleWithWebIdentityResponse, error) {
+ idToken, err := getWebIDTokenExpiry()
+ if err != nil {
+ return AssumeRoleWithWebIdentityResponse{}, err
+ }
+
+ v := url.Values{}
+ v.Set("Action", "AssumeRoleWithWebIdentity")
+ if len(roleARN) > 0 {
+ v.Set("RoleArn", roleARN)
+
+ if len(roleSessionName) == 0 {
+ roleSessionName = strconv.FormatInt(time.Now().UnixNano(), 10)
+ }
+ v.Set("RoleSessionName", roleSessionName)
+ }
+ v.Set("WebIdentityToken", idToken.Token)
+ if idToken.Expiry > 0 {
+ v.Set("DurationSeconds", fmt.Sprintf("%d", idToken.Expiry))
+ }
+ v.Set("Version", STSVersion)
+
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return AssumeRoleWithWebIdentityResponse{}, err
+ }
+
+ u.RawQuery = v.Encode()
+
+ req, err := http.NewRequest(http.MethodPost, u.String(), nil)
+ if err != nil {
+ return AssumeRoleWithWebIdentityResponse{}, err
+ }
+
+ resp, err := clnt.Do(req)
+ if err != nil {
+ return AssumeRoleWithWebIdentityResponse{}, err
+ }
+
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return AssumeRoleWithWebIdentityResponse{}, errors.New(resp.Status)
+ }
+
+ a := AssumeRoleWithWebIdentityResponse{}
+ if err = xml.NewDecoder(resp.Body).Decode(&a); err != nil {
+ return AssumeRoleWithWebIdentityResponse{}, err
+ }
+
+ return a, nil
+}
+
+// Retrieve retrieves credentials from the MinIO service.
+// Error will be returned if the request fails.
+func (m *STSWebIdentity) Retrieve() (Value, error) {
+ a, err := getWebIdentityCredentials(m.Client, m.STSEndpoint, m.roleARN, m.roleSessionName, m.GetWebIDTokenExpiry)
+ if err != nil {
+ return Value{}, err
+ }
+
+ // Expiry window is set to 10secs.
+ m.SetExpiration(a.Result.Credentials.Expiration, DefaultExpiryWindow)
+
+ return Value{
+ AccessKeyID: a.Result.Credentials.AccessKey,
+ SecretAccessKey: a.Result.Credentials.SecretKey,
+ SessionToken: a.Result.Credentials.SessionToken,
+ SignerType: SignatureV4,
+ }, nil
+}
+
+// Expiration returns the expiration time of the credentials
+func (m *STSWebIdentity) Expiration() time.Time {
+ return m.expiration
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/encrypt/server-side.go b/vendor/github.com/minio/minio-go/v7/pkg/encrypt/server-side.go
new file mode 100644
index 00000000..ce7d2153
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/encrypt/server-side.go
@@ -0,0 +1,198 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2018 MinIO, 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.
+ */
+
+package encrypt
+
+import (
+ "crypto/md5"
+ "encoding/base64"
+ "errors"
+ "net/http"
+
+ jsoniter "github.com/json-iterator/go"
+ "golang.org/x/crypto/argon2"
+)
+
+const (
+ // sseGenericHeader is the AWS SSE header used for SSE-S3 and SSE-KMS.
+ sseGenericHeader = "X-Amz-Server-Side-Encryption"
+
+ // sseKmsKeyID is the AWS SSE-KMS key id.
+ sseKmsKeyID = sseGenericHeader + "-Aws-Kms-Key-Id"
+ // sseEncryptionContext is the AWS SSE-KMS Encryption Context data.
+ sseEncryptionContext = sseGenericHeader + "-Context"
+
+ // sseCustomerAlgorithm is the AWS SSE-C algorithm HTTP header key.
+ sseCustomerAlgorithm = sseGenericHeader + "-Customer-Algorithm"
+ // sseCustomerKey is the AWS SSE-C encryption key HTTP header key.
+ sseCustomerKey = sseGenericHeader + "-Customer-Key"
+ // sseCustomerKeyMD5 is the AWS SSE-C encryption key MD5 HTTP header key.
+ sseCustomerKeyMD5 = sseGenericHeader + "-Customer-Key-MD5"
+
+ // sseCopyCustomerAlgorithm is the AWS SSE-C algorithm HTTP header key for CopyObject API.
+ sseCopyCustomerAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
+ // sseCopyCustomerKey is the AWS SSE-C encryption key HTTP header key for CopyObject API.
+ sseCopyCustomerKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
+ // sseCopyCustomerKeyMD5 is the AWS SSE-C encryption key MD5 HTTP header key for CopyObject API.
+ sseCopyCustomerKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-MD5"
+)
+
+// PBKDF creates a SSE-C key from the provided password and salt.
+// PBKDF is a password-based key derivation function
+// which can be used to derive a high-entropy cryptographic
+// key from a low-entropy password and a salt.
+type PBKDF func(password, salt []byte) ServerSide
+
+// DefaultPBKDF is the default PBKDF. It uses Argon2id with the
+// recommended parameters from the RFC draft (1 pass, 64 MB memory, 4 threads).
+var DefaultPBKDF PBKDF = func(password, salt []byte) ServerSide {
+ sse := ssec{}
+ copy(sse[:], argon2.IDKey(password, salt, 1, 64*1024, 4, 32))
+ return sse
+}
+
+// Type is the server-side-encryption method. It represents one of
+// the following encryption methods:
+// - SSE-C: server-side-encryption with customer provided keys
+// - KMS: server-side-encryption with managed keys
+// - S3: server-side-encryption using S3 storage encryption
+type Type string
+
+const (
+ // SSEC represents server-side-encryption with customer provided keys
+ SSEC Type = "SSE-C"
+ // KMS represents server-side-encryption with managed keys
+ KMS Type = "KMS"
+ // S3 represents server-side-encryption using S3 storage encryption
+ S3 Type = "S3"
+)
+
+// ServerSide is a form of S3 server-side-encryption.
+type ServerSide interface {
+ // Type returns the server-side-encryption method.
+ Type() Type
+
+ // Marshal adds encryption headers to the provided HTTP headers.
+ // It marks an HTTP request as server-side-encryption request
+ // and inserts the required data into the headers.
+ Marshal(h http.Header)
+}
+
+// NewSSE returns a server-side-encryption using S3 storage encryption.
+// Using SSE-S3 the server will encrypt the object with server-managed keys.
+func NewSSE() ServerSide { return s3{} }
+
+// NewSSEKMS returns a new server-side-encryption using SSE-KMS and the provided Key Id and context.
+func NewSSEKMS(keyID string, context interface{}) (ServerSide, error) {
+ if context == nil {
+ return kms{key: keyID, hasContext: false}, nil
+ }
+ var json = jsoniter.ConfigCompatibleWithStandardLibrary
+ serializedContext, err := json.Marshal(context)
+ if err != nil {
+ return nil, err
+ }
+ return kms{key: keyID, context: serializedContext, hasContext: true}, nil
+}
+
+// NewSSEC returns a new server-side-encryption using SSE-C and the provided key.
+// The key must be 32 bytes long.
+func NewSSEC(key []byte) (ServerSide, error) {
+ if len(key) != 32 {
+ return nil, errors.New("encrypt: SSE-C key must be 256 bit long")
+ }
+ sse := ssec{}
+ copy(sse[:], key)
+ return sse, nil
+}
+
+// SSE transforms a SSE-C copy encryption into a SSE-C encryption.
+// It is the inverse of SSECopy(...).
+//
+// If the provided sse is no SSE-C copy encryption SSE returns
+// sse unmodified.
+func SSE(sse ServerSide) ServerSide {
+ if sse == nil || sse.Type() != SSEC {
+ return sse
+ }
+ if sse, ok := sse.(ssecCopy); ok {
+ return ssec(sse)
+ }
+ return sse
+}
+
+// SSECopy transforms a SSE-C encryption into a SSE-C copy
+// encryption. This is required for SSE-C key rotation or a SSE-C
+// copy where the source and the destination should be encrypted.
+//
+// If the provided sse is no SSE-C encryption SSECopy returns
+// sse unmodified.
+func SSECopy(sse ServerSide) ServerSide {
+ if sse == nil || sse.Type() != SSEC {
+ return sse
+ }
+ if sse, ok := sse.(ssec); ok {
+ return ssecCopy(sse)
+ }
+ return sse
+}
+
+type ssec [32]byte
+
+func (s ssec) Type() Type { return SSEC }
+
+func (s ssec) Marshal(h http.Header) {
+ keyMD5 := md5.Sum(s[:])
+ h.Set(sseCustomerAlgorithm, "AES256")
+ h.Set(sseCustomerKey, base64.StdEncoding.EncodeToString(s[:]))
+ h.Set(sseCustomerKeyMD5, base64.StdEncoding.EncodeToString(keyMD5[:]))
+}
+
+type ssecCopy [32]byte
+
+func (s ssecCopy) Type() Type { return SSEC }
+
+func (s ssecCopy) Marshal(h http.Header) {
+ keyMD5 := md5.Sum(s[:])
+ h.Set(sseCopyCustomerAlgorithm, "AES256")
+ h.Set(sseCopyCustomerKey, base64.StdEncoding.EncodeToString(s[:]))
+ h.Set(sseCopyCustomerKeyMD5, base64.StdEncoding.EncodeToString(keyMD5[:]))
+}
+
+type s3 struct{}
+
+func (s s3) Type() Type { return S3 }
+
+func (s s3) Marshal(h http.Header) { h.Set(sseGenericHeader, "AES256") }
+
+type kms struct {
+ key string
+ context []byte
+ hasContext bool
+}
+
+func (s kms) Type() Type { return KMS }
+
+func (s kms) Marshal(h http.Header) {
+ h.Set(sseGenericHeader, "aws:kms")
+ if s.key != "" {
+ h.Set(sseKmsKeyID, s.key)
+ }
+ if s.hasContext {
+ h.Set(sseEncryptionContext, base64.StdEncoding.EncodeToString(s.context))
+ }
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/lifecycle/lifecycle.go b/vendor/github.com/minio/minio-go/v7/pkg/lifecycle/lifecycle.go
new file mode 100644
index 00000000..b6f9601b
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/lifecycle/lifecycle.go
@@ -0,0 +1,303 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+// Package lifecycle contains all the lifecycle related data types and marshallers.
+package lifecycle
+
+import (
+ "encoding/xml"
+ "time"
+)
+
+// AbortIncompleteMultipartUpload structure, not supported yet on MinIO
+type AbortIncompleteMultipartUpload struct {
+ XMLName xml.Name `xml:"AbortIncompleteMultipartUpload,omitempty" json:"-"`
+ DaysAfterInitiation ExpirationDays `xml:"DaysAfterInitiation,omitempty" json:"DaysAfterInitiation,omitempty"`
+}
+
+// IsDaysNull returns true if days field is null
+func (n AbortIncompleteMultipartUpload) IsDaysNull() bool {
+ return n.DaysAfterInitiation == ExpirationDays(0)
+}
+
+// MarshalXML if days after initiation is set to non-zero value
+func (n AbortIncompleteMultipartUpload) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if n.IsDaysNull() {
+ return nil
+ }
+ type abortIncompleteMultipartUploadWrapper AbortIncompleteMultipartUpload
+ return e.EncodeElement(abortIncompleteMultipartUploadWrapper(n), start)
+}
+
+// NoncurrentVersionExpiration - Specifies when noncurrent object versions expire.
+// Upon expiration, server permanently deletes the noncurrent object versions.
+// Set this lifecycle configuration action on a bucket that has versioning enabled
+// (or suspended) to request server delete noncurrent object versions at a
+// specific period in the object's lifetime.
+type NoncurrentVersionExpiration struct {
+ XMLName xml.Name `xml:"NoncurrentVersionExpiration" json:"-"`
+ NoncurrentDays ExpirationDays `xml:"NoncurrentDays,omitempty"`
+}
+
+// MarshalXML if non-current days not set to non zero value
+func (n NoncurrentVersionExpiration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if n.IsDaysNull() {
+ return nil
+ }
+ type noncurrentVersionExpirationWrapper NoncurrentVersionExpiration
+ return e.EncodeElement(noncurrentVersionExpirationWrapper(n), start)
+}
+
+// IsDaysNull returns true if days field is null
+func (n NoncurrentVersionExpiration) IsDaysNull() bool {
+ return n.NoncurrentDays == ExpirationDays(0)
+}
+
+// NoncurrentVersionTransition structure, set this action to request server to
+// transition noncurrent object versions to different set storage classes
+// at a specific period in the object's lifetime.
+type NoncurrentVersionTransition struct {
+ XMLName xml.Name `xml:"NoncurrentVersionTransition,omitempty" json:"-"`
+ StorageClass string `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"`
+ NoncurrentDays ExpirationDays `xml:"NoncurrentDays,omitempty" json:"NoncurrentDays,omitempty"`
+}
+
+// IsDaysNull returns true if days field is null
+func (n NoncurrentVersionTransition) IsDaysNull() bool {
+ return n.NoncurrentDays == ExpirationDays(0)
+}
+
+// IsStorageClassEmpty returns true if storage class field is empty
+func (n NoncurrentVersionTransition) IsStorageClassEmpty() bool {
+ return n.StorageClass == ""
+}
+
+// MarshalXML is extended to leave out
+// <NoncurrentVersionTransition></NoncurrentVersionTransition> tags
+func (n NoncurrentVersionTransition) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if n.IsDaysNull() || n.IsStorageClassEmpty() {
+ return nil
+ }
+ type noncurrentVersionTransitionWrapper NoncurrentVersionTransition
+ return e.EncodeElement(noncurrentVersionTransitionWrapper(n), start)
+}
+
+// Tag structure key/value pair representing an object tag to apply lifecycle configuration
+type Tag struct {
+ XMLName xml.Name `xml:"Tag,omitempty" json:"-"`
+ Key string `xml:"Key,omitempty" json:"Key,omitempty"`
+ Value string `xml:"Value,omitempty" json:"Value,omitempty"`
+}
+
+// IsEmpty returns whether this tag is empty or not.
+func (tag Tag) IsEmpty() bool {
+ return tag.Key == ""
+}
+
+// Transition structure - transition details of lifecycle configuration
+type Transition struct {
+ XMLName xml.Name `xml:"Transition" json:"-"`
+ Date ExpirationDate `xml:"Date,omitempty" json:"Date,omitempty"`
+ StorageClass string `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"`
+ Days ExpirationDays `xml:"Days,omitempty" json:"Days,omitempty"`
+}
+
+// IsDaysNull returns true if days field is null
+func (t Transition) IsDaysNull() bool {
+ return t.Days == ExpirationDays(0)
+}
+
+// IsDateNull returns true if date field is null
+func (t Transition) IsDateNull() bool {
+ return t.Date.Time.IsZero()
+}
+
+// IsNull returns true if both date and days fields are null
+func (t Transition) IsNull() bool {
+ return t.IsDaysNull() && t.IsDateNull()
+}
+
+// MarshalXML is transition is non null
+func (t Transition) MarshalXML(en *xml.Encoder, startElement xml.StartElement) error {
+ if t.IsNull() {
+ return nil
+ }
+ type transitionWrapper Transition
+ return en.EncodeElement(transitionWrapper(t), startElement)
+}
+
+// And And Rule for LifecycleTag, to be used in LifecycleRuleFilter
+type And struct {
+ XMLName xml.Name `xml:"And" json:"-"`
+ Prefix string `xml:"Prefix" json:"Prefix,omitempty"`
+ Tags []Tag `xml:"Tag" json:"Tags,omitempty"`
+}
+
+// IsEmpty returns true if Tags field is null
+func (a And) IsEmpty() bool {
+ return len(a.Tags) == 0 && a.Prefix == ""
+}
+
+// Filter will be used in selecting rule(s) for lifecycle configuration
+type Filter struct {
+ XMLName xml.Name `xml:"Filter" json:"-"`
+ And And `xml:"And,omitempty" json:"And,omitempty"`
+ Prefix string `xml:"Prefix,omitempty" json:"Prefix,omitempty"`
+ Tag Tag `xml:"Tag,omitempty" json:"Tag,omitempty"`
+}
+
+// MarshalXML - produces the xml representation of the Filter struct
+// only one of Prefix, And and Tag should be present in the output.
+func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+
+ switch {
+ case !f.And.IsEmpty():
+ if err := e.EncodeElement(f.And, xml.StartElement{Name: xml.Name{Local: "And"}}); err != nil {
+ return err
+ }
+ case !f.Tag.IsEmpty():
+ if err := e.EncodeElement(f.Tag, xml.StartElement{Name: xml.Name{Local: "Tag"}}); err != nil {
+ return err
+ }
+ default:
+ // Always print Prefix field when both And & Tag are empty
+ if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// ExpirationDays is a type alias to unmarshal Days in Expiration
+type ExpirationDays int
+
+// MarshalXML encodes number of days to expire if it is non-zero and
+// encodes empty string otherwise
+func (eDays ExpirationDays) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
+ if eDays == 0 {
+ return nil
+ }
+ return e.EncodeElement(int(eDays), startElement)
+}
+
+// ExpirationDate is a embedded type containing time.Time to unmarshal
+// Date in Expiration
+type ExpirationDate struct {
+ time.Time
+}
+
+// MarshalXML encodes expiration date if it is non-zero and encodes
+// empty string otherwise
+func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
+ if eDate.Time.IsZero() {
+ return nil
+ }
+ return e.EncodeElement(eDate.Format(time.RFC3339), startElement)
+}
+
+// ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element.
+type ExpireDeleteMarker bool
+
+// MarshalXML encodes delete marker boolean into an XML form.
+func (b ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
+ if !b {
+ return nil
+ }
+ type expireDeleteMarkerWrapper ExpireDeleteMarker
+ return e.EncodeElement(expireDeleteMarkerWrapper(b), startElement)
+}
+
+// IsEnabled returns true if the auto delete-marker expiration is enabled
+func (b ExpireDeleteMarker) IsEnabled() bool {
+ return bool(b)
+}
+
+// Expiration structure - expiration details of lifecycle configuration
+type Expiration struct {
+ XMLName xml.Name `xml:"Expiration,omitempty" json:"-"`
+ Date ExpirationDate `xml:"Date,omitempty" json:"Date,omitempty"`
+ Days ExpirationDays `xml:"Days,omitempty" json:"Days,omitempty"`
+ DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker,omitempty"`
+}
+
+// IsDaysNull returns true if days field is null
+func (e Expiration) IsDaysNull() bool {
+ return e.Days == ExpirationDays(0)
+}
+
+// IsDateNull returns true if date field is null
+func (e Expiration) IsDateNull() bool {
+ return e.Date.Time.IsZero()
+}
+
+// IsDeleteMarkerExpirationEnabled returns true if the auto-expiration of delete marker is enabled
+func (e Expiration) IsDeleteMarkerExpirationEnabled() bool {
+ return e.DeleteMarker.IsEnabled()
+}
+
+// IsNull returns true if both date and days fields are null
+func (e Expiration) IsNull() bool {
+ return e.IsDaysNull() && e.IsDateNull() && !e.IsDeleteMarkerExpirationEnabled()
+}
+
+// MarshalXML is expiration is non null
+func (e Expiration) MarshalXML(en *xml.Encoder, startElement xml.StartElement) error {
+ if e.IsNull() {
+ return nil
+ }
+ type expirationWrapper Expiration
+ return en.EncodeElement(expirationWrapper(e), startElement)
+}
+
+// Rule represents a single rule in lifecycle configuration
+type Rule struct {
+ XMLName xml.Name `xml:"Rule,omitempty" json:"-"`
+ AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty" json:"AbortIncompleteMultipartUpload,omitempty"`
+ Expiration Expiration `xml:"Expiration,omitempty" json:"Expiration,omitempty"`
+ ID string `xml:"ID" json:"ID"`
+ RuleFilter Filter `xml:"Filter,omitempty" json:"Filter,omitempty"`
+ NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty" json:"NoncurrentVersionExpiration,omitempty"`
+ NoncurrentVersionTransition NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty" json:"NoncurrentVersionTransition,omitempty"`
+ Prefix string `xml:"Prefix,omitempty" json:"Prefix,omitempty"`
+ Status string `xml:"Status" json:"Status"`
+ Transition Transition `xml:"Transition,omitempty" json:"Transition,omitempty"`
+}
+
+// Configuration is a collection of Rule objects.
+type Configuration struct {
+ XMLName xml.Name `xml:"LifecycleConfiguration,omitempty" json:"-"`
+ Rules []Rule `xml:"Rule"`
+}
+
+// Empty check if lifecycle configuration is empty
+func (c *Configuration) Empty() bool {
+ if c == nil {
+ return true
+ }
+ return len(c.Rules) == 0
+}
+
+// NewConfiguration initializes a fresh lifecycle configuration
+// for manipulation, such as setting and removing lifecycle rules
+// and filters.
+func NewConfiguration() *Configuration {
+ return &Configuration{}
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/notification/info.go b/vendor/github.com/minio/minio-go/v7/pkg/notification/info.go
new file mode 100644
index 00000000..d0a47163
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/notification/info.go
@@ -0,0 +1,78 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017-2020 MinIO, 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.
+ */
+
+package notification
+
+// Indentity represents the user id, this is a compliance field.
+type identity struct {
+ PrincipalID string `json:"principalId"`
+}
+
+// event bucket metadata.
+type bucketMeta struct {
+ Name string `json:"name"`
+ OwnerIdentity identity `json:"ownerIdentity"`
+ ARN string `json:"arn"`
+}
+
+// event object metadata.
+type objectMeta struct {
+ Key string `json:"key"`
+ Size int64 `json:"size,omitempty"`
+ ETag string `json:"eTag,omitempty"`
+ ContentType string `json:"contentType,omitempty"`
+ UserMetadata map[string]string `json:"userMetadata,omitempty"`
+ VersionID string `json:"versionId,omitempty"`
+ Sequencer string `json:"sequencer"`
+}
+
+// event server specific metadata.
+type eventMeta struct {
+ SchemaVersion string `json:"s3SchemaVersion"`
+ ConfigurationID string `json:"configurationId"`
+ Bucket bucketMeta `json:"bucket"`
+ Object objectMeta `json:"object"`
+}
+
+// sourceInfo represents information on the client that
+// triggered the event notification.
+type sourceInfo struct {
+ Host string `json:"host"`
+ Port string `json:"port"`
+ UserAgent string `json:"userAgent"`
+}
+
+// Event represents an Amazon an S3 bucket notification event.
+type Event struct {
+ EventVersion string `json:"eventVersion"`
+ EventSource string `json:"eventSource"`
+ AwsRegion string `json:"awsRegion"`
+ EventTime string `json:"eventTime"`
+ EventName string `json:"eventName"`
+ UserIdentity identity `json:"userIdentity"`
+ RequestParameters map[string]string `json:"requestParameters"`
+ ResponseElements map[string]string `json:"responseElements"`
+ S3 eventMeta `json:"s3"`
+ Source sourceInfo `json:"source"`
+}
+
+// Info - represents the collection of notification events, additionally
+// also reports errors if any while listening on bucket notifications.
+type Info struct {
+ Records []Event
+ Err error
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/notification/notification.go b/vendor/github.com/minio/minio-go/v7/pkg/notification/notification.go
new file mode 100644
index 00000000..b17e6c54
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/notification/notification.go
@@ -0,0 +1,395 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package notification
+
+import (
+ "encoding/xml"
+ "errors"
+ "fmt"
+
+ "github.com/minio/minio-go/v7/pkg/set"
+)
+
+// EventType is a S3 notification event associated to the bucket notification configuration
+type EventType string
+
+// The role of all event types are described in :
+// http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations
+const (
+ ObjectCreatedAll EventType = "s3:ObjectCreated:*"
+ ObjectCreatedPut = "s3:ObjectCreated:Put"
+ ObjectCreatedPost = "s3:ObjectCreated:Post"
+ ObjectCreatedCopy = "s3:ObjectCreated:Copy"
+ ObjectCreatedCompleteMultipartUpload = "s3:ObjectCreated:CompleteMultipartUpload"
+ ObjectAccessedGet = "s3:ObjectAccessed:Get"
+ ObjectAccessedHead = "s3:ObjectAccessed:Head"
+ ObjectAccessedAll = "s3:ObjectAccessed:*"
+ ObjectRemovedAll = "s3:ObjectRemoved:*"
+ ObjectRemovedDelete = "s3:ObjectRemoved:Delete"
+ ObjectRemovedDeleteMarkerCreated = "s3:ObjectRemoved:DeleteMarkerCreated"
+ ObjectReducedRedundancyLostObject = "s3:ReducedRedundancyLostObject"
+ BucketCreatedAll = "s3:BucketCreated:*"
+ BucketRemovedAll = "s3:BucketRemoved:*"
+)
+
+// FilterRule - child of S3Key, a tag in the notification xml which
+// carries suffix/prefix filters
+type FilterRule struct {
+ Name string `xml:"Name"`
+ Value string `xml:"Value"`
+}
+
+// S3Key - child of Filter, a tag in the notification xml which
+// carries suffix/prefix filters
+type S3Key struct {
+ FilterRules []FilterRule `xml:"FilterRule,omitempty"`
+}
+
+// Filter - a tag in the notification xml structure which carries
+// suffix/prefix filters
+type Filter struct {
+ S3Key S3Key `xml:"S3Key,omitempty"`
+}
+
+// Arn - holds ARN information that will be sent to the web service,
+// ARN desciption can be found in http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
+type Arn struct {
+ Partition string
+ Service string
+ Region string
+ AccountID string
+ Resource string
+}
+
+// NewArn creates new ARN based on the given partition, service, region, account id and resource
+func NewArn(partition, service, region, accountID, resource string) Arn {
+ return Arn{Partition: partition,
+ Service: service,
+ Region: region,
+ AccountID: accountID,
+ Resource: resource}
+}
+
+// String returns the string format of the ARN
+func (arn Arn) String() string {
+ return "arn:" + arn.Partition + ":" + arn.Service + ":" + arn.Region + ":" + arn.AccountID + ":" + arn.Resource
+}
+
+// Config - represents one single notification configuration
+// such as topic, queue or lambda configuration.
+type Config struct {
+ ID string `xml:"Id,omitempty"`
+ Arn Arn `xml:"-"`
+ Events []EventType `xml:"Event"`
+ Filter *Filter `xml:"Filter,omitempty"`
+}
+
+// NewConfig creates one notification config and sets the given ARN
+func NewConfig(arn Arn) Config {
+ return Config{Arn: arn, Filter: &Filter{}}
+}
+
+// AddEvents adds one event to the current notification config
+func (t *Config) AddEvents(events ...EventType) {
+ t.Events = append(t.Events, events...)
+}
+
+// AddFilterSuffix sets the suffix configuration to the current notification config
+func (t *Config) AddFilterSuffix(suffix string) {
+ if t.Filter == nil {
+ t.Filter = &Filter{}
+ }
+ newFilterRule := FilterRule{Name: "suffix", Value: suffix}
+ // Replace any suffix rule if existing and add to the list otherwise
+ for index := range t.Filter.S3Key.FilterRules {
+ if t.Filter.S3Key.FilterRules[index].Name == "suffix" {
+ t.Filter.S3Key.FilterRules[index] = newFilterRule
+ return
+ }
+ }
+ t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule)
+}
+
+// AddFilterPrefix sets the prefix configuration to the current notification config
+func (t *Config) AddFilterPrefix(prefix string) {
+ if t.Filter == nil {
+ t.Filter = &Filter{}
+ }
+ newFilterRule := FilterRule{Name: "prefix", Value: prefix}
+ // Replace any prefix rule if existing and add to the list otherwise
+ for index := range t.Filter.S3Key.FilterRules {
+ if t.Filter.S3Key.FilterRules[index].Name == "prefix" {
+ t.Filter.S3Key.FilterRules[index] = newFilterRule
+ return
+ }
+ }
+ t.Filter.S3Key.FilterRules = append(t.Filter.S3Key.FilterRules, newFilterRule)
+}
+
+// EqualEventTypeList tells whether a and b contain the same events
+func EqualEventTypeList(a, b []EventType) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ setA := set.NewStringSet()
+ for _, i := range a {
+ setA.Add(string(i))
+ }
+
+ setB := set.NewStringSet()
+ for _, i := range b {
+ setB.Add(string(i))
+ }
+
+ return setA.Difference(setB).IsEmpty()
+}
+
+// EqualFilterRuleList tells whether a and b contain the same filters
+func EqualFilterRuleList(a, b []FilterRule) bool {
+ if len(a) != len(b) {
+ return false
+ }
+
+ setA := set.NewStringSet()
+ for _, i := range a {
+ setA.Add(fmt.Sprintf("%s-%s", i.Name, i.Value))
+ }
+
+ setB := set.NewStringSet()
+ for _, i := range b {
+ setB.Add(fmt.Sprintf("%s-%s", i.Name, i.Value))
+ }
+
+ return setA.Difference(setB).IsEmpty()
+}
+
+// Equal returns whether this `Config` is equal to another defined by the passed parameters
+func (t *Config) Equal(events []EventType, prefix, suffix string) bool {
+ if t == nil {
+ return false
+ }
+
+ // Compare events
+ passEvents := EqualEventTypeList(t.Events, events)
+
+ // Compare filters
+ var newFilterRules []FilterRule
+ if prefix != "" {
+ newFilterRules = append(newFilterRules, FilterRule{Name: "prefix", Value: prefix})
+ }
+ if suffix != "" {
+ newFilterRules = append(newFilterRules, FilterRule{Name: "suffix", Value: suffix})
+ }
+
+ var currentFilterRules []FilterRule
+ if t.Filter != nil {
+ currentFilterRules = t.Filter.S3Key.FilterRules
+ }
+
+ passFilters := EqualFilterRuleList(currentFilterRules, newFilterRules)
+ return passEvents && passFilters
+}
+
+// TopicConfig carries one single topic notification configuration
+type TopicConfig struct {
+ Config
+ Topic string `xml:"Topic"`
+}
+
+// QueueConfig carries one single queue notification configuration
+type QueueConfig struct {
+ Config
+ Queue string `xml:"Queue"`
+}
+
+// LambdaConfig carries one single cloudfunction notification configuration
+type LambdaConfig struct {
+ Config
+ Lambda string `xml:"CloudFunction"`
+}
+
+// Configuration - the struct that represents the whole XML to be sent to the web service
+type Configuration struct {
+ XMLName xml.Name `xml:"NotificationConfiguration"`
+ LambdaConfigs []LambdaConfig `xml:"CloudFunctionConfiguration"`
+ TopicConfigs []TopicConfig `xml:"TopicConfiguration"`
+ QueueConfigs []QueueConfig `xml:"QueueConfiguration"`
+}
+
+// AddTopic adds a given topic config to the general bucket notification config
+func (b *Configuration) AddTopic(topicConfig Config) bool {
+ newTopicConfig := TopicConfig{Config: topicConfig, Topic: topicConfig.Arn.String()}
+ for _, n := range b.TopicConfigs {
+ // If new config matches existing one
+ if n.Topic == newTopicConfig.Arn.String() && newTopicConfig.Filter == n.Filter {
+
+ existingConfig := set.NewStringSet()
+ for _, v := range n.Events {
+ existingConfig.Add(string(v))
+ }
+
+ newConfig := set.NewStringSet()
+ for _, v := range topicConfig.Events {
+ newConfig.Add(string(v))
+ }
+
+ if !newConfig.Intersection(existingConfig).IsEmpty() {
+ return false
+ }
+ }
+ }
+ b.TopicConfigs = append(b.TopicConfigs, newTopicConfig)
+ return true
+}
+
+// AddQueue adds a given queue config to the general bucket notification config
+func (b *Configuration) AddQueue(queueConfig Config) bool {
+ newQueueConfig := QueueConfig{Config: queueConfig, Queue: queueConfig.Arn.String()}
+ for _, n := range b.QueueConfigs {
+ if n.Queue == newQueueConfig.Arn.String() && newQueueConfig.Filter == n.Filter {
+
+ existingConfig := set.NewStringSet()
+ for _, v := range n.Events {
+ existingConfig.Add(string(v))
+ }
+
+ newConfig := set.NewStringSet()
+ for _, v := range queueConfig.Events {
+ newConfig.Add(string(v))
+ }
+
+ if !newConfig.Intersection(existingConfig).IsEmpty() {
+ return false
+ }
+ }
+ }
+ b.QueueConfigs = append(b.QueueConfigs, newQueueConfig)
+ return true
+}
+
+// AddLambda adds a given lambda config to the general bucket notification config
+func (b *Configuration) AddLambda(lambdaConfig Config) bool {
+ newLambdaConfig := LambdaConfig{Config: lambdaConfig, Lambda: lambdaConfig.Arn.String()}
+ for _, n := range b.LambdaConfigs {
+ if n.Lambda == newLambdaConfig.Arn.String() && newLambdaConfig.Filter == n.Filter {
+
+ existingConfig := set.NewStringSet()
+ for _, v := range n.Events {
+ existingConfig.Add(string(v))
+ }
+
+ newConfig := set.NewStringSet()
+ for _, v := range lambdaConfig.Events {
+ newConfig.Add(string(v))
+ }
+
+ if !newConfig.Intersection(existingConfig).IsEmpty() {
+ return false
+ }
+ }
+ }
+ b.LambdaConfigs = append(b.LambdaConfigs, newLambdaConfig)
+ return true
+}
+
+// RemoveTopicByArn removes all topic configurations that match the exact specified ARN
+func (b *Configuration) RemoveTopicByArn(arn Arn) {
+ var topics []TopicConfig
+ for _, topic := range b.TopicConfigs {
+ if topic.Topic != arn.String() {
+ topics = append(topics, topic)
+ }
+ }
+ b.TopicConfigs = topics
+}
+
+// ErrNoConfigMatch is returned when a notification configuration (sqs,sns,lambda) is not found when trying to delete
+var ErrNoConfigMatch = errors.New("no notification configuration matched")
+
+// RemoveTopicByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix
+func (b *Configuration) RemoveTopicByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
+ removeIndex := -1
+ for i, v := range b.TopicConfigs {
+ // if it matches events and filters, mark the index for deletion
+ if v.Topic == arn.String() && v.Config.Equal(events, prefix, suffix) {
+ removeIndex = i
+ break // since we have at most one matching config
+ }
+ }
+ if removeIndex >= 0 {
+ b.TopicConfigs = append(b.TopicConfigs[:removeIndex], b.TopicConfigs[removeIndex+1:]...)
+ return nil
+ }
+ return ErrNoConfigMatch
+}
+
+// RemoveQueueByArn removes all queue configurations that match the exact specified ARN
+func (b *Configuration) RemoveQueueByArn(arn Arn) {
+ var queues []QueueConfig
+ for _, queue := range b.QueueConfigs {
+ if queue.Queue != arn.String() {
+ queues = append(queues, queue)
+ }
+ }
+ b.QueueConfigs = queues
+}
+
+// RemoveQueueByArnEventsPrefixSuffix removes a queue configuration that match the exact specified ARN, events, prefix and suffix
+func (b *Configuration) RemoveQueueByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
+ removeIndex := -1
+ for i, v := range b.QueueConfigs {
+ // if it matches events and filters, mark the index for deletion
+ if v.Queue == arn.String() && v.Config.Equal(events, prefix, suffix) {
+ removeIndex = i
+ break // since we have at most one matching config
+ }
+ }
+ if removeIndex >= 0 {
+ b.QueueConfigs = append(b.QueueConfigs[:removeIndex], b.QueueConfigs[removeIndex+1:]...)
+ return nil
+ }
+ return ErrNoConfigMatch
+}
+
+// RemoveLambdaByArn removes all lambda configurations that match the exact specified ARN
+func (b *Configuration) RemoveLambdaByArn(arn Arn) {
+ var lambdas []LambdaConfig
+ for _, lambda := range b.LambdaConfigs {
+ if lambda.Lambda != arn.String() {
+ lambdas = append(lambdas, lambda)
+ }
+ }
+ b.LambdaConfigs = lambdas
+}
+
+// RemoveLambdaByArnEventsPrefixSuffix removes a topic configuration that match the exact specified ARN, events, prefix and suffix
+func (b *Configuration) RemoveLambdaByArnEventsPrefixSuffix(arn Arn, events []EventType, prefix, suffix string) error {
+ removeIndex := -1
+ for i, v := range b.LambdaConfigs {
+ // if it matches events and filters, mark the index for deletion
+ if v.Lambda == arn.String() && v.Config.Equal(events, prefix, suffix) {
+ removeIndex = i
+ break // since we have at most one matching config
+ }
+ }
+ if removeIndex >= 0 {
+ b.LambdaConfigs = append(b.LambdaConfigs[:removeIndex], b.LambdaConfigs[removeIndex+1:]...)
+ return nil
+ }
+ return ErrNoConfigMatch
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go b/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go
new file mode 100644
index 00000000..beacc71f
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/replication/replication.go
@@ -0,0 +1,696 @@
+/*
+ * MinIO Client (C) 2020 MinIO, 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.
+ */
+
+package replication
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+
+ "github.com/rs/xid"
+)
+
+var errInvalidFilter = fmt.Errorf("invalid filter")
+
+// OptionType specifies operation to be performed on config
+type OptionType string
+
+const (
+ // AddOption specifies addition of rule to config
+ AddOption OptionType = "Add"
+ // SetOption specifies modification of existing rule to config
+ SetOption OptionType = "Set"
+
+ // RemoveOption specifies rule options are for removing a rule
+ RemoveOption OptionType = "Remove"
+ // ImportOption is for getting current config
+ ImportOption OptionType = "Import"
+)
+
+// Options represents options to set a replication configuration rule
+type Options struct {
+ Op OptionType
+ ID string
+ Prefix string
+ RuleStatus string
+ Priority string
+ TagString string
+ StorageClass string
+ RoleArn string
+ DestBucket string
+ IsTagSet bool
+ IsSCSet bool
+ ReplicateDeletes string // replicate versioned deletes
+ ReplicateDeleteMarkers string // replicate soft deletes
+ ReplicaSync string // replicate replica metadata modifications
+ ExistingObjectReplicate string
+}
+
+// Tags returns a slice of tags for a rule
+func (opts Options) Tags() ([]Tag, error) {
+ var tagList []Tag
+ tagTokens := strings.Split(opts.TagString, "&")
+ for _, tok := range tagTokens {
+ if tok == "" {
+ break
+ }
+ kv := strings.SplitN(tok, "=", 2)
+ if len(kv) != 2 {
+ return []Tag{}, fmt.Errorf("tags should be entered as comma separated k=v pairs")
+ }
+ tagList = append(tagList, Tag{
+ Key: kv[0],
+ Value: kv[1],
+ })
+ }
+ return tagList, nil
+}
+
+// Config - replication configuration specified in
+// https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
+type Config struct {
+ XMLName xml.Name `xml:"ReplicationConfiguration" json:"-"`
+ Rules []Rule `xml:"Rule" json:"Rules"`
+ Role string `xml:"Role" json:"Role"`
+}
+
+// Empty returns true if config is not set
+func (c *Config) Empty() bool {
+ return len(c.Rules) == 0
+}
+
+// AddRule adds a new rule to existing replication config. If a rule exists with the
+// same ID, then the rule is replaced.
+func (c *Config) AddRule(opts Options) error {
+ priority, err := strconv.Atoi(opts.Priority)
+ if err != nil {
+ return err
+ }
+ if opts.RoleArn != c.Role && c.Role != "" {
+ return fmt.Errorf("role ARN does not match existing configuration")
+ }
+ var status Status
+ // toggle rule status for edit option
+ switch opts.RuleStatus {
+ case "enable":
+ status = Enabled
+ case "disable":
+ status = Disabled
+ default:
+ return fmt.Errorf("rule state should be either [enable|disable]")
+ }
+
+ tags, err := opts.Tags()
+ if err != nil {
+ return err
+ }
+ andVal := And{
+ Tags: tags,
+ }
+ filter := Filter{Prefix: opts.Prefix}
+ // only a single tag is set.
+ if opts.Prefix == "" && len(tags) == 1 {
+ filter.Tag = tags[0]
+ }
+ // both prefix and tag are present
+ if len(andVal.Tags) > 1 || opts.Prefix != "" {
+ filter.And = andVal
+ filter.And.Prefix = opts.Prefix
+ filter.Prefix = ""
+ filter.Tag = Tag{}
+ }
+ if opts.ID == "" {
+ opts.ID = xid.New().String()
+ }
+ arnStr := opts.RoleArn
+ if opts.RoleArn == "" {
+ arnStr = c.Role
+ }
+ if arnStr == "" {
+ return fmt.Errorf("role ARN required")
+ }
+ tokens := strings.Split(arnStr, ":")
+ if len(tokens) != 6 {
+ return fmt.Errorf("invalid format for replication Arn")
+ }
+ if c.Role == "" {
+ c.Role = arnStr
+ }
+ destBucket := opts.DestBucket
+ // ref https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html
+ if btokens := strings.Split(destBucket, ":"); len(btokens) != 6 {
+ if len(btokens) == 1 {
+ destBucket = fmt.Sprintf("arn:aws:s3:::%s", destBucket)
+ } else {
+ return fmt.Errorf("destination bucket needs to be in Arn format")
+ }
+ }
+ dmStatus := Disabled
+ if opts.ReplicateDeleteMarkers != "" {
+ switch opts.ReplicateDeleteMarkers {
+ case "enable":
+ dmStatus = Enabled
+ case "disable":
+ dmStatus = Disabled
+ default:
+ return fmt.Errorf("ReplicateDeleteMarkers should be either enable|disable")
+ }
+ }
+
+ vDeleteStatus := Disabled
+ if opts.ReplicateDeletes != "" {
+ switch opts.ReplicateDeletes {
+ case "enable":
+ vDeleteStatus = Enabled
+ case "disable":
+ vDeleteStatus = Disabled
+ default:
+ return fmt.Errorf("ReplicateDeletes should be either enable|disable")
+ }
+ }
+ var replicaSync Status
+ // replica sync is by default Enabled, unless specified.
+ switch opts.ReplicaSync {
+ case "enable", "":
+ replicaSync = Enabled
+ case "disable":
+ replicaSync = Disabled
+ default:
+ return fmt.Errorf("replica metadata sync should be either [enable|disable]")
+ }
+
+ var existingStatus Status
+ if opts.ExistingObjectReplicate != "" {
+ switch opts.ExistingObjectReplicate {
+ case "enable":
+ existingStatus = Enabled
+ case "disable", "":
+ existingStatus = Disabled
+ default:
+ return fmt.Errorf("existingObjectReplicate should be either enable|disable")
+ }
+ }
+ newRule := Rule{
+ ID: opts.ID,
+ Priority: priority,
+ Status: status,
+ Filter: filter,
+ Destination: Destination{
+ Bucket: destBucket,
+ StorageClass: opts.StorageClass,
+ },
+ DeleteMarkerReplication: DeleteMarkerReplication{Status: dmStatus},
+ DeleteReplication: DeleteReplication{Status: vDeleteStatus},
+ // MinIO enables replica metadata syncing by default in the case of bi-directional replication to allow
+ // automatic failover as the expectation in this case is that replica and source should be identical.
+ // However AWS leaves this configurable https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-for-metadata-changes.html
+ SourceSelectionCriteria: SourceSelectionCriteria{
+ ReplicaModifications: ReplicaModifications{
+ Status: replicaSync,
+ },
+ },
+ // By default disable existing object replication unless selected
+ ExistingObjectReplication: ExistingObjectReplication{
+ Status: existingStatus,
+ },
+ }
+
+ // validate rule after overlaying priority for pre-existing rule being disabled.
+ if err := newRule.Validate(); err != nil {
+ return err
+ }
+ for _, rule := range c.Rules {
+ if rule.Priority == newRule.Priority {
+ return fmt.Errorf("priority must be unique. Replication configuration already has a rule with this priority")
+ }
+ if rule.Destination.Bucket != newRule.Destination.Bucket {
+ return fmt.Errorf("the destination bucket must be same for all rules")
+ }
+ if rule.ID == newRule.ID {
+ return fmt.Errorf("a rule exists with this ID")
+ }
+ }
+
+ c.Rules = append(c.Rules, newRule)
+ return nil
+}
+
+// EditRule modifies an existing rule in replication config
+func (c *Config) EditRule(opts Options) error {
+ if opts.ID == "" {
+ return fmt.Errorf("rule ID missing")
+ }
+ rIdx := -1
+ var newRule Rule
+ for i, rule := range c.Rules {
+ if rule.ID == opts.ID {
+ rIdx = i
+ newRule = rule
+ break
+ }
+ }
+ if rIdx < 0 {
+ return fmt.Errorf("rule with ID %s not found in replication configuration", opts.ID)
+ }
+ prefixChg := opts.Prefix != newRule.Prefix()
+ if opts.IsTagSet || prefixChg {
+ prefix := newRule.Prefix()
+ if prefix != opts.Prefix {
+ prefix = opts.Prefix
+ }
+ tags := []Tag{newRule.Filter.Tag}
+ if len(newRule.Filter.And.Tags) != 0 {
+ tags = newRule.Filter.And.Tags
+ }
+ var err error
+ if opts.IsTagSet {
+ tags, err = opts.Tags()
+ if err != nil {
+ return err
+ }
+ }
+ andVal := And{
+ Tags: tags,
+ }
+
+ filter := Filter{Prefix: prefix}
+ // only a single tag is set.
+ if prefix == "" && len(tags) == 1 {
+ filter.Tag = tags[0]
+ }
+ // both prefix and tag are present
+ if len(andVal.Tags) > 1 || prefix != "" {
+ filter.And = andVal
+ filter.And.Prefix = prefix
+ filter.Prefix = ""
+ filter.Tag = Tag{}
+ }
+ newRule.Filter = filter
+ }
+
+ // toggle rule status for edit option
+ if opts.RuleStatus != "" {
+ switch opts.RuleStatus {
+ case "enable":
+ newRule.Status = Enabled
+ case "disable":
+ newRule.Status = Disabled
+ default:
+ return fmt.Errorf("rule state should be either [enable|disable]")
+ }
+ }
+ // set DeleteMarkerReplication rule status for edit option
+ if opts.ReplicateDeleteMarkers != "" {
+ switch opts.ReplicateDeleteMarkers {
+ case "enable":
+ newRule.DeleteMarkerReplication.Status = Enabled
+ case "disable":
+ newRule.DeleteMarkerReplication.Status = Disabled
+ default:
+ return fmt.Errorf("ReplicateDeleteMarkers state should be either [enable|disable]")
+ }
+ }
+
+ // set DeleteReplication rule status for edit option. This is a MinIO specific
+ // option to replicate versioned deletes
+ if opts.ReplicateDeletes != "" {
+ switch opts.ReplicateDeletes {
+ case "enable":
+ newRule.DeleteReplication.Status = Enabled
+ case "disable":
+ newRule.DeleteReplication.Status = Disabled
+ default:
+ return fmt.Errorf("ReplicateDeletes state should be either [enable|disable]")
+ }
+ }
+
+ if opts.ReplicaSync != "" {
+ switch opts.ReplicaSync {
+ case "enable", "":
+ newRule.SourceSelectionCriteria.ReplicaModifications.Status = Enabled
+ case "disable":
+ newRule.SourceSelectionCriteria.ReplicaModifications.Status = Disabled
+ default:
+ return fmt.Errorf("replica metadata sync should be either [enable|disable]")
+ }
+ }
+ fmt.Println("opts.ExistingObjectReplicate>", opts.ExistingObjectReplicate)
+ if opts.ExistingObjectReplicate != "" {
+ switch opts.ExistingObjectReplicate {
+ case "enable":
+ newRule.ExistingObjectReplication.Status = Enabled
+ case "disable":
+ newRule.ExistingObjectReplication.Status = Disabled
+ default:
+ return fmt.Errorf("existingObjectsReplication state should be either [enable|disable]")
+ }
+ }
+ if opts.IsSCSet {
+ newRule.Destination.StorageClass = opts.StorageClass
+ }
+ if opts.Priority != "" {
+ priority, err := strconv.Atoi(opts.Priority)
+ if err != nil {
+ return err
+ }
+ newRule.Priority = priority
+ }
+ if opts.DestBucket != "" {
+ destBucket := opts.DestBucket
+ // ref https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html
+ if btokens := strings.Split(opts.DestBucket, ":"); len(btokens) != 6 {
+ if len(btokens) == 1 {
+ destBucket = fmt.Sprintf("arn:aws:s3:::%s", destBucket)
+ } else {
+ return fmt.Errorf("destination bucket needs to be in Arn format")
+ }
+ }
+ newRule.Destination.Bucket = destBucket
+ }
+ // validate rule
+ if err := newRule.Validate(); err != nil {
+ return err
+ }
+ // ensure priority and destination bucket restrictions are not violated
+ for idx, rule := range c.Rules {
+ if rule.Priority == newRule.Priority && rIdx != idx {
+ return fmt.Errorf("priority must be unique. Replication configuration already has a rule with this priority")
+ }
+ if rule.Destination.Bucket != newRule.Destination.Bucket {
+ return fmt.Errorf("the destination bucket must be same for all rules")
+ }
+ }
+
+ c.Rules[rIdx] = newRule
+ return nil
+}
+
+// RemoveRule removes a rule from replication config.
+func (c *Config) RemoveRule(opts Options) error {
+ var newRules []Rule
+ ruleFound := false
+ for _, rule := range c.Rules {
+ if rule.ID != opts.ID {
+ newRules = append(newRules, rule)
+ continue
+ }
+ ruleFound = true
+ }
+ if !ruleFound {
+ return fmt.Errorf("Rule with ID %s not found", opts.ID)
+ }
+ if len(newRules) == 0 {
+ return fmt.Errorf("replication configuration should have at least one rule")
+ }
+ c.Rules = newRules
+ return nil
+
+}
+
+// Rule - a rule for replication configuration.
+type Rule struct {
+ XMLName xml.Name `xml:"Rule" json:"-"`
+ ID string `xml:"ID,omitempty"`
+ Status Status `xml:"Status"`
+ Priority int `xml:"Priority"`
+ DeleteMarkerReplication DeleteMarkerReplication `xml:"DeleteMarkerReplication"`
+ DeleteReplication DeleteReplication `xml:"DeleteReplication"`
+ Destination Destination `xml:"Destination"`
+ Filter Filter `xml:"Filter" json:"Filter"`
+ SourceSelectionCriteria SourceSelectionCriteria `xml:"SourceSelectionCriteria" json:"SourceSelectionCriteria"`
+ ExistingObjectReplication ExistingObjectReplication `xml:"ExistingObjectReplication,omitempty" json:"ExistingObjectReplication,omitempty"`
+}
+
+// Validate validates the rule for correctness
+func (r Rule) Validate() error {
+ if err := r.validateID(); err != nil {
+ return err
+ }
+ if err := r.validateStatus(); err != nil {
+ return err
+ }
+ if err := r.validateFilter(); err != nil {
+ return err
+ }
+
+ if r.Priority < 0 && r.Status == Enabled {
+ return fmt.Errorf("priority must be set for the rule")
+ }
+
+ if err := r.validateStatus(); err != nil {
+ return err
+ }
+ return r.ExistingObjectReplication.Validate()
+}
+
+// validateID - checks if ID is valid or not.
+func (r Rule) validateID() error {
+ // cannot be longer than 255 characters
+ if len(r.ID) > 255 {
+ return fmt.Errorf("ID must be less than 255 characters")
+ }
+ return nil
+}
+
+// validateStatus - checks if status is valid or not.
+func (r Rule) validateStatus() error {
+ // Status can't be empty
+ if len(r.Status) == 0 {
+ return fmt.Errorf("status cannot be empty")
+ }
+
+ // Status must be one of Enabled or Disabled
+ if r.Status != Enabled && r.Status != Disabled {
+ return fmt.Errorf("status must be set to either Enabled or Disabled")
+ }
+ return nil
+}
+
+func (r Rule) validateFilter() error {
+ if err := r.Filter.Validate(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Prefix - a rule can either have prefix under <filter></filter> or under
+// <filter><and></and></filter>. This method returns the prefix from the
+// location where it is available
+func (r Rule) Prefix() string {
+ if r.Filter.Prefix != "" {
+ return r.Filter.Prefix
+ }
+ return r.Filter.And.Prefix
+}
+
+// Tags - a rule can either have tag under <filter></filter> or under
+// <filter><and></and></filter>. This method returns all the tags from the
+// rule in the format tag1=value1&tag2=value2
+func (r Rule) Tags() string {
+ ts := []Tag{r.Filter.Tag}
+ if len(r.Filter.And.Tags) != 0 {
+ ts = r.Filter.And.Tags
+ }
+
+ var buf bytes.Buffer
+ for _, t := range ts {
+ if buf.Len() > 0 {
+ buf.WriteString("&")
+ }
+ buf.WriteString(t.String())
+ }
+ return buf.String()
+}
+
+// Filter - a filter for a replication configuration Rule.
+type Filter struct {
+ XMLName xml.Name `xml:"Filter" json:"-"`
+ Prefix string `json:"Prefix,omitempty"`
+ And And `xml:"And,omitempty" json:"And,omitempty"`
+ Tag Tag `xml:"Tag,omitempty" json:"Tag,omitempty"`
+}
+
+// Validate - validates the filter element
+func (f Filter) Validate() error {
+ // A Filter must have exactly one of Prefix, Tag, or And specified.
+ if !f.And.isEmpty() {
+ if f.Prefix != "" {
+ return errInvalidFilter
+ }
+ if !f.Tag.IsEmpty() {
+ return errInvalidFilter
+ }
+ }
+ if f.Prefix != "" {
+ if !f.Tag.IsEmpty() {
+ return errInvalidFilter
+ }
+ }
+ if !f.Tag.IsEmpty() {
+ if err := f.Tag.Validate(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Tag - a tag for a replication configuration Rule filter.
+type Tag struct {
+ XMLName xml.Name `json:"-"`
+ Key string `xml:"Key,omitempty" json:"Key,omitempty"`
+ Value string `xml:"Value,omitempty" json:"Value,omitempty"`
+}
+
+func (tag Tag) String() string {
+ if tag.IsEmpty() {
+ return ""
+ }
+ return tag.Key + "=" + tag.Value
+}
+
+// IsEmpty returns whether this tag is empty or not.
+func (tag Tag) IsEmpty() bool {
+ return tag.Key == ""
+}
+
+// Validate checks this tag.
+func (tag Tag) Validate() error {
+ if len(tag.Key) == 0 || utf8.RuneCountInString(tag.Key) > 128 {
+ return fmt.Errorf("invalid Tag Key")
+ }
+
+ if utf8.RuneCountInString(tag.Value) > 256 {
+ return fmt.Errorf("invalid Tag Value")
+ }
+ return nil
+}
+
+// Destination - destination in ReplicationConfiguration.
+type Destination struct {
+ XMLName xml.Name `xml:"Destination" json:"-"`
+ Bucket string `xml:"Bucket" json:"Bucket"`
+ StorageClass string `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"`
+}
+
+// And - a tag to combine a prefix and multiple tags for replication configuration rule.
+type And struct {
+ XMLName xml.Name `xml:"And,omitempty" json:"-"`
+ Prefix string `xml:"Prefix,omitempty" json:"Prefix,omitempty"`
+ Tags []Tag `xml:"Tag,omitempty" json:"Tag,omitempty"`
+}
+
+// isEmpty returns true if Tags field is null
+func (a And) isEmpty() bool {
+ return len(a.Tags) == 0 && a.Prefix == ""
+}
+
+// Status represents Enabled/Disabled status
+type Status string
+
+// Supported status types
+const (
+ Enabled Status = "Enabled"
+ Disabled Status = "Disabled"
+)
+
+// DeleteMarkerReplication - whether delete markers are replicated - https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
+type DeleteMarkerReplication struct {
+ Status Status `xml:"Status" json:"Status"` // should be set to "Disabled" by default
+}
+
+// IsEmpty returns true if DeleteMarkerReplication is not set
+func (d DeleteMarkerReplication) IsEmpty() bool {
+ return len(d.Status) == 0
+}
+
+// DeleteReplication - whether versioned deletes are replicated - this
+// is a MinIO specific extension
+type DeleteReplication struct {
+ Status Status `xml:"Status" json:"Status"` // should be set to "Disabled" by default
+}
+
+// IsEmpty returns true if DeleteReplication is not set
+func (d DeleteReplication) IsEmpty() bool {
+ return len(d.Status) == 0
+}
+
+// ReplicaModifications specifies if replica modification sync is enabled
+type ReplicaModifications struct {
+ Status Status `xml:"Status" json:"Status"` // should be set to "Enabled" by default
+}
+
+// SourceSelectionCriteria - specifies additional source selection criteria in ReplicationConfiguration.
+type SourceSelectionCriteria struct {
+ ReplicaModifications ReplicaModifications `xml:"ReplicaModifications" json:"ReplicaModifications"`
+}
+
+// IsValid - checks whether SourceSelectionCriteria is valid or not.
+func (s SourceSelectionCriteria) IsValid() bool {
+ return s.ReplicaModifications.Status == Enabled || s.ReplicaModifications.Status == Disabled
+}
+
+// Validate source selection criteria
+func (s SourceSelectionCriteria) Validate() error {
+ if (s == SourceSelectionCriteria{}) {
+ return nil
+ }
+ if !s.IsValid() {
+ return fmt.Errorf("invalid ReplicaModification status")
+ }
+ return nil
+}
+
+// ExistingObjectReplication - whether existing object replication is enabled
+type ExistingObjectReplication struct {
+ Status Status `xml:"Status"` // should be set to "Disabled" by default
+}
+
+// IsEmpty returns true if DeleteMarkerReplication is not set
+func (e ExistingObjectReplication) IsEmpty() bool {
+ return len(e.Status) == 0
+}
+
+// Validate validates whether the status is disabled.
+func (e ExistingObjectReplication) Validate() error {
+ if e.IsEmpty() {
+ return nil
+ }
+ if e.Status != Disabled && e.Status != Enabled {
+ return fmt.Errorf("invalid ExistingObjectReplication status")
+ }
+ return nil
+}
+
+// Metrics represents inline replication metrics
+// such as pending, failed and completed bytes in total for a bucket
+type Metrics struct {
+ // Pending size in bytes
+ PendingSize uint64 `json:"pendingReplicationSize"`
+ // Completed size in bytes
+ ReplicatedSize uint64 `json:"completedReplicationSize"`
+ // Total Replica size in bytes
+ ReplicaSize uint64 `json:"replicaSize"`
+ // Failed size in bytes
+ FailedSize uint64 `json:"failedReplicationSize"`
+ // Total number of pending operations including metadata updates
+ PendingCount uint64 `json:"pendingReplicationCount"`
+ // Total number of failed operations including metadata updates
+ FailedCount uint64 `json:"failedReplicationCount"`
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go b/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go
new file mode 100644
index 00000000..fea25d6e
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go
@@ -0,0 +1,391 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2020 MinIO, 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.
+ */
+
+package s3utils
+
+import (
+ "bytes"
+ "encoding/hex"
+ "errors"
+ "net"
+ "net/url"
+ "regexp"
+ "sort"
+ "strings"
+ "unicode/utf8"
+)
+
+// Sentinel URL is the default url value which is invalid.
+var sentinelURL = url.URL{}
+
+// IsValidDomain validates if input string is a valid domain name.
+func IsValidDomain(host string) bool {
+ // See RFC 1035, RFC 3696.
+ host = strings.TrimSpace(host)
+ if len(host) == 0 || len(host) > 255 {
+ return false
+ }
+ // host cannot start or end with "-"
+ if host[len(host)-1:] == "-" || host[:1] == "-" {
+ return false
+ }
+ // host cannot start or end with "_"
+ if host[len(host)-1:] == "_" || host[:1] == "_" {
+ return false
+ }
+ // host cannot start with a "."
+ if host[:1] == "." {
+ return false
+ }
+ // All non alphanumeric characters are invalid.
+ if strings.ContainsAny(host, "`~!@#$%^&*()+={}[]|\\\"';:><?/") {
+ return false
+ }
+ // No need to regexp match, since the list is non-exhaustive.
+ // We let it valid and fail later.
+ return true
+}
+
+// IsValidIP parses input string for ip address validity.
+func IsValidIP(ip string) bool {
+ return net.ParseIP(ip) != nil
+}
+
+// IsVirtualHostSupported - verifies if bucketName can be part of
+// virtual host. Currently only Amazon S3 and Google Cloud Storage
+// would support this.
+func IsVirtualHostSupported(endpointURL url.URL, bucketName string) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ // bucketName can be valid but '.' in the hostname will fail SSL
+ // certificate validation. So do not use host-style for such buckets.
+ if endpointURL.Scheme == "https" && strings.Contains(bucketName, ".") {
+ return false
+ }
+ // Return true for all other cases
+ return IsAmazonEndpoint(endpointURL) || IsGoogleEndpoint(endpointURL) || IsAliyunOSSEndpoint(endpointURL)
+}
+
+// Refer for region styles - https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
+
+// amazonS3HostHyphen - regular expression used to determine if an arg is s3 host in hyphenated style.
+var amazonS3HostHyphen = regexp.MustCompile(`^s3-(.*?).amazonaws.com$`)
+
+// amazonS3HostDualStack - regular expression used to determine if an arg is s3 host dualstack.
+var amazonS3HostDualStack = regexp.MustCompile(`^s3.dualstack.(.*?).amazonaws.com$`)
+
+// amazonS3HostDot - regular expression used to determine if an arg is s3 host in . style.
+var amazonS3HostDot = regexp.MustCompile(`^s3.(.*?).amazonaws.com$`)
+
+// amazonS3ChinaHost - regular expression used to determine if the arg is s3 china host.
+var amazonS3ChinaHost = regexp.MustCompile(`^s3.(cn.*?).amazonaws.com.cn$`)
+
+// amazonS3ChinaHostDualStack - regular expression used to determine if the arg is s3 china host dualstack.
+var amazonS3ChinaHostDualStack = regexp.MustCompile(`^s3.dualstack.(cn.*?).amazonaws.com.cn$`)
+
+// Regular expression used to determine if the arg is elb host.
+var elbAmazonRegex = regexp.MustCompile(`elb(.*?).amazonaws.com$`)
+
+// Regular expression used to determine if the arg is elb host in china.
+var elbAmazonCnRegex = regexp.MustCompile(`elb(.*?).amazonaws.com.cn$`)
+
+// GetRegionFromURL - returns a region from url host.
+func GetRegionFromURL(endpointURL url.URL) string {
+ if endpointURL == sentinelURL {
+ return ""
+ }
+ if endpointURL.Host == "s3-external-1.amazonaws.com" {
+ return ""
+ }
+ if IsAmazonGovCloudEndpoint(endpointURL) {
+ return "us-gov-west-1"
+ }
+ // if elb's are used we cannot calculate which region it may be, just return empty.
+ if elbAmazonRegex.MatchString(endpointURL.Host) || elbAmazonCnRegex.MatchString(endpointURL.Host) {
+ return ""
+ }
+ parts := amazonS3HostDualStack.FindStringSubmatch(endpointURL.Host)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ parts = amazonS3HostHyphen.FindStringSubmatch(endpointURL.Host)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ parts = amazonS3ChinaHost.FindStringSubmatch(endpointURL.Host)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ parts = amazonS3ChinaHostDualStack.FindStringSubmatch(endpointURL.Host)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ parts = amazonS3HostDot.FindStringSubmatch(endpointURL.Host)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ return ""
+}
+
+// IsAliyunOSSEndpoint - Match if it is exactly Aliyun OSS endpoint.
+func IsAliyunOSSEndpoint(endpointURL url.URL) bool {
+ return strings.HasSuffix(endpointURL.Host, "aliyuncs.com")
+}
+
+// IsAmazonEndpoint - Match if it is exactly Amazon S3 endpoint.
+func IsAmazonEndpoint(endpointURL url.URL) bool {
+ if endpointURL.Host == "s3-external-1.amazonaws.com" || endpointURL.Host == "s3.amazonaws.com" {
+ return true
+ }
+ return GetRegionFromURL(endpointURL) != ""
+}
+
+// IsAmazonGovCloudEndpoint - Match if it is exactly Amazon S3 GovCloud endpoint.
+func IsAmazonGovCloudEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ return (endpointURL.Host == "s3-us-gov-west-1.amazonaws.com" ||
+ IsAmazonFIPSGovCloudEndpoint(endpointURL))
+}
+
+// IsAmazonFIPSGovCloudEndpoint - Match if it is exactly Amazon S3 FIPS GovCloud endpoint.
+// See https://aws.amazon.com/compliance/fips.
+func IsAmazonFIPSGovCloudEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ return endpointURL.Host == "s3-fips-us-gov-west-1.amazonaws.com" ||
+ endpointURL.Host == "s3-fips.dualstack.us-gov-west-1.amazonaws.com"
+}
+
+// IsAmazonFIPSUSEastWestEndpoint - Match if it is exactly Amazon S3 FIPS US East/West endpoint.
+// See https://aws.amazon.com/compliance/fips.
+func IsAmazonFIPSUSEastWestEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ switch endpointURL.Host {
+ case "s3-fips.us-east-2.amazonaws.com":
+ case "s3-fips.dualstack.us-west-1.amazonaws.com":
+ case "s3-fips.dualstack.us-west-2.amazonaws.com":
+ case "s3-fips.dualstack.us-east-2.amazonaws.com":
+ case "s3-fips.dualstack.us-east-1.amazonaws.com":
+ case "s3-fips.us-west-1.amazonaws.com":
+ case "s3-fips.us-west-2.amazonaws.com":
+ case "s3-fips.us-east-1.amazonaws.com":
+ default:
+ return false
+ }
+ return true
+}
+
+// IsAmazonFIPSEndpoint - Match if it is exactly Amazon S3 FIPS endpoint.
+// See https://aws.amazon.com/compliance/fips.
+func IsAmazonFIPSEndpoint(endpointURL url.URL) bool {
+ return IsAmazonFIPSUSEastWestEndpoint(endpointURL) || IsAmazonFIPSGovCloudEndpoint(endpointURL)
+}
+
+// IsGoogleEndpoint - Match if it is exactly Google cloud storage endpoint.
+func IsGoogleEndpoint(endpointURL url.URL) bool {
+ if endpointURL == sentinelURL {
+ return false
+ }
+ return endpointURL.Host == "storage.googleapis.com"
+}
+
+// Expects ascii encoded strings - from output of urlEncodePath
+func percentEncodeSlash(s string) string {
+ return strings.Replace(s, "/", "%2F", -1)
+}
+
+// QueryEncode - encodes query values in their URL encoded form. In
+// addition to the percent encoding performed by urlEncodePath() used
+// here, it also percent encodes '/' (forward slash)
+func QueryEncode(v url.Values) string {
+ if v == nil {
+ return ""
+ }
+ var buf bytes.Buffer
+ keys := make([]string, 0, len(v))
+ for k := range v {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ vs := v[k]
+ prefix := percentEncodeSlash(EncodePath(k)) + "="
+ for _, v := range vs {
+ if buf.Len() > 0 {
+ buf.WriteByte('&')
+ }
+ buf.WriteString(prefix)
+ buf.WriteString(percentEncodeSlash(EncodePath(v)))
+ }
+ }
+ return buf.String()
+}
+
+// TagDecode - decodes canonical tag into map of key and value.
+func TagDecode(ctag string) map[string]string {
+ if ctag == "" {
+ return map[string]string{}
+ }
+ tags := strings.Split(ctag, "&")
+ tagMap := make(map[string]string, len(tags))
+ var err error
+ for _, tag := range tags {
+ kvs := strings.SplitN(tag, "=", 2)
+ if len(kvs) == 0 {
+ return map[string]string{}
+ }
+ if len(kvs) == 1 {
+ return map[string]string{}
+ }
+ tagMap[kvs[0]], err = url.PathUnescape(kvs[1])
+ if err != nil {
+ continue
+ }
+ }
+ return tagMap
+}
+
+// TagEncode - encodes tag values in their URL encoded form. In
+// addition to the percent encoding performed by urlEncodePath() used
+// here, it also percent encodes '/' (forward slash)
+func TagEncode(tags map[string]string) string {
+ if tags == nil {
+ return ""
+ }
+ values := url.Values{}
+ for k, v := range tags {
+ values[k] = []string{v}
+ }
+ return QueryEncode(values)
+}
+
+// if object matches reserved string, no need to encode them
+var reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
+
+// EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
+//
+// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
+// non english characters cannot be parsed due to the nature in which url.Encode() is written
+//
+// This function on the other hand is a direct replacement for url.Encode() technique to support
+// pretty much every UTF-8 character.
+func EncodePath(pathName string) string {
+ if reservedObjectNames.MatchString(pathName) {
+ return pathName
+ }
+ var encodedPathname strings.Builder
+ for _, s := range pathName {
+ if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // ยง2.3 Unreserved characters (mark)
+ encodedPathname.WriteRune(s)
+ continue
+ }
+ switch s {
+ case '-', '_', '.', '~', '/': // ยง2.3 Unreserved characters (mark)
+ encodedPathname.WriteRune(s)
+ continue
+ default:
+ len := utf8.RuneLen(s)
+ if len < 0 {
+ // if utf8 cannot convert return the same string as is
+ return pathName
+ }
+ u := make([]byte, len)
+ utf8.EncodeRune(u, s)
+ for _, r := range u {
+ hex := hex.EncodeToString([]byte{r})
+ encodedPathname.WriteString("%" + strings.ToUpper(hex))
+ }
+ }
+ }
+ return encodedPathname.String()
+}
+
+// We support '.' with bucket names but we fallback to using path
+// style requests instead for such buckets.
+var (
+ validBucketName = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9\.\-\_\:]{1,61}[A-Za-z0-9]$`)
+ validBucketNameStrict = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
+ ipAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
+)
+
+// Common checker for both stricter and basic validation.
+func checkBucketNameCommon(bucketName string, strict bool) (err error) {
+ if strings.TrimSpace(bucketName) == "" {
+ return errors.New("Bucket name cannot be empty")
+ }
+ if len(bucketName) < 3 {
+ return errors.New("Bucket name cannot be shorter than 3 characters")
+ }
+ if len(bucketName) > 63 {
+ return errors.New("Bucket name cannot be longer than 63 characters")
+ }
+ if ipAddress.MatchString(bucketName) {
+ return errors.New("Bucket name cannot be an ip address")
+ }
+ if strings.Contains(bucketName, "..") || strings.Contains(bucketName, ".-") || strings.Contains(bucketName, "-.") {
+ return errors.New("Bucket name contains invalid characters")
+ }
+ if strict {
+ if !validBucketNameStrict.MatchString(bucketName) {
+ err = errors.New("Bucket name contains invalid characters")
+ }
+ return err
+ }
+ if !validBucketName.MatchString(bucketName) {
+ err = errors.New("Bucket name contains invalid characters")
+ }
+ return err
+}
+
+// CheckValidBucketName - checks if we have a valid input bucket name.
+func CheckValidBucketName(bucketName string) (err error) {
+ return checkBucketNameCommon(bucketName, false)
+}
+
+// CheckValidBucketNameStrict - checks if we have a valid input bucket name.
+// This is a stricter version.
+// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html
+func CheckValidBucketNameStrict(bucketName string) (err error) {
+ return checkBucketNameCommon(bucketName, true)
+}
+
+// CheckValidObjectNamePrefix - checks if we have a valid input object name prefix.
+// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
+func CheckValidObjectNamePrefix(objectName string) error {
+ if len(objectName) > 1024 {
+ return errors.New("Object name cannot be longer than 1024 characters")
+ }
+ if !utf8.ValidString(objectName) {
+ return errors.New("Object name with non UTF-8 strings are not supported")
+ }
+ return nil
+}
+
+// CheckValidObjectName - checks if we have a valid input object name.
+// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
+func CheckValidObjectName(objectName string) error {
+ if strings.TrimSpace(objectName) == "" {
+ return errors.New("Object name cannot be empty")
+ }
+ return CheckValidObjectNamePrefix(objectName)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/set/stringset.go b/vendor/github.com/minio/minio-go/v7/pkg/set/stringset.go
new file mode 100644
index 00000000..c35e58e1
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/set/stringset.go
@@ -0,0 +1,200 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package set
+
+import (
+ "fmt"
+ "sort"
+
+ jsoniter "github.com/json-iterator/go"
+)
+
+// StringSet - uses map as set of strings.
+type StringSet map[string]struct{}
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+// ToSlice - returns StringSet as string slice.
+func (set StringSet) ToSlice() []string {
+ keys := make([]string, 0, len(set))
+ for k := range set {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ return keys
+}
+
+// IsEmpty - returns whether the set is empty or not.
+func (set StringSet) IsEmpty() bool {
+ return len(set) == 0
+}
+
+// Add - adds string to the set.
+func (set StringSet) Add(s string) {
+ set[s] = struct{}{}
+}
+
+// Remove - removes string in the set. It does nothing if string does not exist in the set.
+func (set StringSet) Remove(s string) {
+ delete(set, s)
+}
+
+// Contains - checks if string is in the set.
+func (set StringSet) Contains(s string) bool {
+ _, ok := set[s]
+ return ok
+}
+
+// FuncMatch - returns new set containing each value who passes match function.
+// A 'matchFn' should accept element in a set as first argument and
+// 'matchString' as second argument. The function can do any logic to
+// compare both the arguments and should return true to accept element in
+// a set to include in output set else the element is ignored.
+func (set StringSet) FuncMatch(matchFn func(string, string) bool, matchString string) StringSet {
+ nset := NewStringSet()
+ for k := range set {
+ if matchFn(k, matchString) {
+ nset.Add(k)
+ }
+ }
+ return nset
+}
+
+// ApplyFunc - returns new set containing each value processed by 'applyFn'.
+// A 'applyFn' should accept element in a set as a argument and return
+// a processed string. The function can do any logic to return a processed
+// string.
+func (set StringSet) ApplyFunc(applyFn func(string) string) StringSet {
+ nset := NewStringSet()
+ for k := range set {
+ nset.Add(applyFn(k))
+ }
+ return nset
+}
+
+// Equals - checks whether given set is equal to current set or not.
+func (set StringSet) Equals(sset StringSet) bool {
+ // If length of set is not equal to length of given set, the
+ // set is not equal to given set.
+ if len(set) != len(sset) {
+ return false
+ }
+
+ // As both sets are equal in length, check each elements are equal.
+ for k := range set {
+ if _, ok := sset[k]; !ok {
+ return false
+ }
+ }
+
+ return true
+}
+
+// Intersection - returns the intersection with given set as new set.
+func (set StringSet) Intersection(sset StringSet) StringSet {
+ nset := NewStringSet()
+ for k := range set {
+ if _, ok := sset[k]; ok {
+ nset.Add(k)
+ }
+ }
+
+ return nset
+}
+
+// Difference - returns the difference with given set as new set.
+func (set StringSet) Difference(sset StringSet) StringSet {
+ nset := NewStringSet()
+ for k := range set {
+ if _, ok := sset[k]; !ok {
+ nset.Add(k)
+ }
+ }
+
+ return nset
+}
+
+// Union - returns the union with given set as new set.
+func (set StringSet) Union(sset StringSet) StringSet {
+ nset := NewStringSet()
+ for k := range set {
+ nset.Add(k)
+ }
+
+ for k := range sset {
+ nset.Add(k)
+ }
+
+ return nset
+}
+
+// MarshalJSON - converts to JSON data.
+func (set StringSet) MarshalJSON() ([]byte, error) {
+ return json.Marshal(set.ToSlice())
+}
+
+// UnmarshalJSON - parses JSON data and creates new set with it.
+// If 'data' contains JSON string array, the set contains each string.
+// If 'data' contains JSON string, the set contains the string as one element.
+// If 'data' contains Other JSON types, JSON parse error is returned.
+func (set *StringSet) UnmarshalJSON(data []byte) error {
+ sl := []string{}
+ var err error
+ if err = json.Unmarshal(data, &sl); err == nil {
+ *set = make(StringSet)
+ for _, s := range sl {
+ set.Add(s)
+ }
+ } else {
+ var s string
+ if err = json.Unmarshal(data, &s); err == nil {
+ *set = make(StringSet)
+ set.Add(s)
+ }
+ }
+
+ return err
+}
+
+// String - returns printable string of the set.
+func (set StringSet) String() string {
+ return fmt.Sprintf("%s", set.ToSlice())
+}
+
+// NewStringSet - creates new string set.
+func NewStringSet() StringSet {
+ return make(StringSet)
+}
+
+// CreateStringSet - creates new string set with given string values.
+func CreateStringSet(sl ...string) StringSet {
+ set := make(StringSet)
+ for _, k := range sl {
+ set.Add(k)
+ }
+ return set
+}
+
+// CopyStringSet - returns copy of given set.
+func CopyStringSet(set StringSet) StringSet {
+ nset := NewStringSet()
+ for k, v := range set {
+ nset[k] = v
+ }
+ return nset
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-streaming.go b/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-streaming.go
new file mode 100644
index 00000000..7b2ca91d
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-streaming.go
@@ -0,0 +1,306 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017 MinIO, 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.
+ */
+
+package signer
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Reference for constants used below -
+// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#example-signature-calculations-streaming
+const (
+ streamingSignAlgorithm = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
+ streamingPayloadHdr = "AWS4-HMAC-SHA256-PAYLOAD"
+ emptySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ payloadChunkSize = 64 * 1024
+ chunkSigConstLen = 17 // ";chunk-signature="
+ signatureStrLen = 64 // e.g. "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"
+ crlfLen = 2 // CRLF
+)
+
+// Request headers to be ignored while calculating seed signature for
+// a request.
+var ignoredStreamingHeaders = map[string]bool{
+ "Authorization": true,
+ "User-Agent": true,
+ "Content-Type": true,
+}
+
+// getSignedChunkLength - calculates the length of chunk metadata
+func getSignedChunkLength(chunkDataSize int64) int64 {
+ return int64(len(fmt.Sprintf("%x", chunkDataSize))) +
+ chunkSigConstLen +
+ signatureStrLen +
+ crlfLen +
+ chunkDataSize +
+ crlfLen
+}
+
+// getStreamLength - calculates the length of the overall stream (data + metadata)
+func getStreamLength(dataLen, chunkSize int64) int64 {
+ if dataLen <= 0 {
+ return 0
+ }
+
+ chunksCount := int64(dataLen / chunkSize)
+ remainingBytes := int64(dataLen % chunkSize)
+ streamLen := int64(0)
+ streamLen += chunksCount * getSignedChunkLength(chunkSize)
+ if remainingBytes > 0 {
+ streamLen += getSignedChunkLength(remainingBytes)
+ }
+ streamLen += getSignedChunkLength(0)
+ return streamLen
+}
+
+// buildChunkStringToSign - returns the string to sign given chunk data
+// and previous signature.
+func buildChunkStringToSign(t time.Time, region, previousSig string, chunkData []byte) string {
+ stringToSignParts := []string{
+ streamingPayloadHdr,
+ t.Format(iso8601DateFormat),
+ getScope(region, t, ServiceTypeS3),
+ previousSig,
+ emptySHA256,
+ hex.EncodeToString(sum256(chunkData)),
+ }
+
+ return strings.Join(stringToSignParts, "\n")
+}
+
+// prepareStreamingRequest - prepares a request with appropriate
+// headers before computing the seed signature.
+func prepareStreamingRequest(req *http.Request, sessionToken string, dataLen int64, timestamp time.Time) {
+ // Set x-amz-content-sha256 header.
+ req.Header.Set("X-Amz-Content-Sha256", streamingSignAlgorithm)
+ if sessionToken != "" {
+ req.Header.Set("X-Amz-Security-Token", sessionToken)
+ }
+
+ req.Header.Set("X-Amz-Date", timestamp.Format(iso8601DateFormat))
+ // Set content length with streaming signature for each chunk included.
+ req.ContentLength = getStreamLength(dataLen, int64(payloadChunkSize))
+ req.Header.Set("x-amz-decoded-content-length", strconv.FormatInt(dataLen, 10))
+}
+
+// buildChunkHeader - returns the chunk header.
+// e.g string(IntHexBase(chunk-size)) + ";chunk-signature=" + signature + \r\n + chunk-data + \r\n
+func buildChunkHeader(chunkLen int64, signature string) []byte {
+ return []byte(strconv.FormatInt(chunkLen, 16) + ";chunk-signature=" + signature + "\r\n")
+}
+
+// buildChunkSignature - returns chunk signature for a given chunk and previous signature.
+func buildChunkSignature(chunkData []byte, reqTime time.Time, region,
+ previousSignature, secretAccessKey string) string {
+
+ chunkStringToSign := buildChunkStringToSign(reqTime, region,
+ previousSignature, chunkData)
+ signingKey := getSigningKey(secretAccessKey, region, reqTime, ServiceTypeS3)
+ return getSignature(signingKey, chunkStringToSign)
+}
+
+// getSeedSignature - returns the seed signature for a given request.
+func (s *StreamingReader) setSeedSignature(req *http.Request) {
+ // Get canonical request
+ canonicalRequest := getCanonicalRequest(*req, ignoredStreamingHeaders, getHashedPayload(*req))
+
+ // Get string to sign from canonical request.
+ stringToSign := getStringToSignV4(s.reqTime, s.region, canonicalRequest, ServiceTypeS3)
+
+ signingKey := getSigningKey(s.secretAccessKey, s.region, s.reqTime, ServiceTypeS3)
+
+ // Calculate signature.
+ s.seedSignature = getSignature(signingKey, stringToSign)
+}
+
+// StreamingReader implements chunked upload signature as a reader on
+// top of req.Body's ReaderCloser chunk header;data;... repeat
+type StreamingReader struct {
+ accessKeyID string
+ secretAccessKey string
+ sessionToken string
+ region string
+ prevSignature string
+ seedSignature string
+ contentLen int64 // Content-Length from req header
+ baseReadCloser io.ReadCloser // underlying io.Reader
+ bytesRead int64 // bytes read from underlying io.Reader
+ buf bytes.Buffer // holds signed chunk
+ chunkBuf []byte // holds raw data read from req Body
+ chunkBufLen int // no. of bytes read so far into chunkBuf
+ done bool // done reading the underlying reader to EOF
+ reqTime time.Time
+ chunkNum int
+ totalChunks int
+ lastChunkSize int
+}
+
+// signChunk - signs a chunk read from s.baseReader of chunkLen size.
+func (s *StreamingReader) signChunk(chunkLen int) {
+ // Compute chunk signature for next header
+ signature := buildChunkSignature(s.chunkBuf[:chunkLen], s.reqTime,
+ s.region, s.prevSignature, s.secretAccessKey)
+
+ // For next chunk signature computation
+ s.prevSignature = signature
+
+ // Write chunk header into streaming buffer
+ chunkHdr := buildChunkHeader(int64(chunkLen), signature)
+ s.buf.Write(chunkHdr)
+
+ // Write chunk data into streaming buffer
+ s.buf.Write(s.chunkBuf[:chunkLen])
+
+ // Write the chunk trailer.
+ s.buf.Write([]byte("\r\n"))
+
+ // Reset chunkBufLen for next chunk read.
+ s.chunkBufLen = 0
+ s.chunkNum++
+}
+
+// setStreamingAuthHeader - builds and sets authorization header value
+// for streaming signature.
+func (s *StreamingReader) setStreamingAuthHeader(req *http.Request) {
+ credential := GetCredential(s.accessKeyID, s.region, s.reqTime, ServiceTypeS3)
+ authParts := []string{
+ signV4Algorithm + " Credential=" + credential,
+ "SignedHeaders=" + getSignedHeaders(*req, ignoredStreamingHeaders),
+ "Signature=" + s.seedSignature,
+ }
+
+ // Set authorization header.
+ auth := strings.Join(authParts, ",")
+ req.Header.Set("Authorization", auth)
+}
+
+// StreamingSignV4 - provides chunked upload signatureV4 support by
+// implementing io.Reader.
+func StreamingSignV4(req *http.Request, accessKeyID, secretAccessKey, sessionToken,
+ region string, dataLen int64, reqTime time.Time) *http.Request {
+
+ // Set headers needed for streaming signature.
+ prepareStreamingRequest(req, sessionToken, dataLen, reqTime)
+
+ if req.Body == nil {
+ req.Body = ioutil.NopCloser(bytes.NewReader([]byte("")))
+ }
+
+ stReader := &StreamingReader{
+ baseReadCloser: req.Body,
+ accessKeyID: accessKeyID,
+ secretAccessKey: secretAccessKey,
+ sessionToken: sessionToken,
+ region: region,
+ reqTime: reqTime,
+ chunkBuf: make([]byte, payloadChunkSize),
+ contentLen: dataLen,
+ chunkNum: 1,
+ totalChunks: int((dataLen+payloadChunkSize-1)/payloadChunkSize) + 1,
+ lastChunkSize: int(dataLen % payloadChunkSize),
+ }
+
+ // Add the request headers required for chunk upload signing.
+
+ // Compute the seed signature.
+ stReader.setSeedSignature(req)
+
+ // Set the authorization header with the seed signature.
+ stReader.setStreamingAuthHeader(req)
+
+ // Set seed signature as prevSignature for subsequent
+ // streaming signing process.
+ stReader.prevSignature = stReader.seedSignature
+ req.Body = stReader
+
+ return req
+}
+
+// Read - this method performs chunk upload signature providing a
+// io.Reader interface.
+func (s *StreamingReader) Read(buf []byte) (int, error) {
+ switch {
+ // After the last chunk is read from underlying reader, we
+ // never re-fill s.buf.
+ case s.done:
+
+ // s.buf will be (re-)filled with next chunk when has lesser
+ // bytes than asked for.
+ case s.buf.Len() < len(buf):
+ s.chunkBufLen = 0
+ for {
+ n1, err := s.baseReadCloser.Read(s.chunkBuf[s.chunkBufLen:])
+ // Usually we validate `err` first, but in this case
+ // we are validating n > 0 for the following reasons.
+ //
+ // 1. n > 0, err is one of io.EOF, nil (near end of stream)
+ // A Reader returning a non-zero number of bytes at the end
+ // of the input stream may return either err == EOF or err == nil
+ //
+ // 2. n == 0, err is io.EOF (actual end of stream)
+ //
+ // Callers should always process the n > 0 bytes returned
+ // before considering the error err.
+ if n1 > 0 {
+ s.chunkBufLen += n1
+ s.bytesRead += int64(n1)
+
+ if s.chunkBufLen == payloadChunkSize ||
+ (s.chunkNum == s.totalChunks-1 &&
+ s.chunkBufLen == s.lastChunkSize) {
+ // Sign the chunk and write it to s.buf.
+ s.signChunk(s.chunkBufLen)
+ break
+ }
+ }
+ if err != nil {
+ if err == io.EOF {
+ // No more data left in baseReader - last chunk.
+ // Done reading the last chunk from baseReader.
+ s.done = true
+
+ // bytes read from baseReader different than
+ // content length provided.
+ if s.bytesRead != s.contentLen {
+ return 0, fmt.Errorf("http: ContentLength=%d with Body length %d", s.contentLen, s.bytesRead)
+ }
+
+ // Sign the chunk and write it to s.buf.
+ s.signChunk(0)
+ break
+ }
+ return 0, err
+ }
+
+ }
+ }
+ return s.buf.Read(buf)
+}
+
+// Close - this method makes underlying io.ReadCloser's Close method available.
+func (s *StreamingReader) Close() error {
+ return s.baseReadCloser.Close()
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v2.go b/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v2.go
new file mode 100644
index 00000000..71821a26
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v2.go
@@ -0,0 +1,317 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package signer
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// Signature and API related constants.
+const (
+ signV2Algorithm = "AWS"
+)
+
+// Encode input URL path to URL encoded path.
+func encodeURL2Path(req *http.Request, virtualHost bool) (path string) {
+ if virtualHost {
+ reqHost := getHostAddr(req)
+ dotPos := strings.Index(reqHost, ".")
+ if dotPos > -1 {
+ bucketName := reqHost[:dotPos]
+ path = "/" + bucketName
+ path += req.URL.Path
+ path = s3utils.EncodePath(path)
+ return
+ }
+ }
+ path = s3utils.EncodePath(req.URL.Path)
+ return
+}
+
+// PreSignV2 - presign the request in following style.
+// https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
+func PreSignV2(req http.Request, accessKeyID, secretAccessKey string, expires int64, virtualHost bool) *http.Request {
+ // Presign is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ d := time.Now().UTC()
+ // Find epoch expires when the request will expire.
+ epochExpires := d.Unix() + expires
+
+ // Add expires header if not present.
+ if expiresStr := req.Header.Get("Expires"); expiresStr == "" {
+ req.Header.Set("Expires", strconv.FormatInt(epochExpires, 10))
+ }
+
+ // Get presigned string to sign.
+ stringToSign := preStringToSignV2(req, virtualHost)
+ hm := hmac.New(sha1.New, []byte(secretAccessKey))
+ hm.Write([]byte(stringToSign))
+
+ // Calculate signature.
+ signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
+
+ query := req.URL.Query()
+ // Handle specially for Google Cloud Storage.
+ if strings.Contains(getHostAddr(&req), ".storage.googleapis.com") {
+ query.Set("GoogleAccessId", accessKeyID)
+ } else {
+ query.Set("AWSAccessKeyId", accessKeyID)
+ }
+
+ // Fill in Expires for presigned query.
+ query.Set("Expires", strconv.FormatInt(epochExpires, 10))
+
+ // Encode query and save.
+ req.URL.RawQuery = s3utils.QueryEncode(query)
+
+ // Save signature finally.
+ req.URL.RawQuery += "&Signature=" + s3utils.EncodePath(signature)
+
+ // Return.
+ return &req
+}
+
+// PostPresignSignatureV2 - presigned signature for PostPolicy
+// request.
+func PostPresignSignatureV2(policyBase64, secretAccessKey string) string {
+ hm := hmac.New(sha1.New, []byte(secretAccessKey))
+ hm.Write([]byte(policyBase64))
+ signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
+ return signature
+}
+
+// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
+// Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
+//
+// StringToSign = HTTP-Verb + "\n" +
+// Content-Md5 + "\n" +
+// Content-Type + "\n" +
+// Date + "\n" +
+// CanonicalizedProtocolHeaders +
+// CanonicalizedResource;
+//
+// CanonicalizedResource = [ "/" + Bucket ] +
+// <HTTP-Request-URI, from the protocol name up to the query string> +
+// [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
+//
+// CanonicalizedProtocolHeaders = <described below>
+
+// SignV2 sign the request before Do() (AWS Signature Version 2).
+func SignV2(req http.Request, accessKeyID, secretAccessKey string, virtualHost bool) *http.Request {
+ // Signature calculation is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ // Initial time.
+ d := time.Now().UTC()
+
+ // Add date if not present.
+ if date := req.Header.Get("Date"); date == "" {
+ req.Header.Set("Date", d.Format(http.TimeFormat))
+ }
+
+ // Calculate HMAC for secretAccessKey.
+ stringToSign := stringToSignV2(req, virtualHost)
+ hm := hmac.New(sha1.New, []byte(secretAccessKey))
+ hm.Write([]byte(stringToSign))
+
+ // Prepare auth header.
+ authHeader := new(bytes.Buffer)
+ authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKeyID))
+ encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
+ encoder.Write(hm.Sum(nil))
+ encoder.Close()
+
+ // Set Authorization header.
+ req.Header.Set("Authorization", authHeader.String())
+
+ return &req
+}
+
+// From the Amazon docs:
+//
+// StringToSign = HTTP-Verb + "\n" +
+// Content-Md5 + "\n" +
+// Content-Type + "\n" +
+// Expires + "\n" +
+// CanonicalizedProtocolHeaders +
+// CanonicalizedResource;
+func preStringToSignV2(req http.Request, virtualHost bool) string {
+ buf := new(bytes.Buffer)
+ // Write standard headers.
+ writePreSignV2Headers(buf, req)
+ // Write canonicalized protocol headers if any.
+ writeCanonicalizedHeaders(buf, req)
+ // Write canonicalized Query resources if any.
+ writeCanonicalizedResource(buf, req, virtualHost)
+ return buf.String()
+}
+
+// writePreSignV2Headers - write preSign v2 required headers.
+func writePreSignV2Headers(buf *bytes.Buffer, req http.Request) {
+ buf.WriteString(req.Method + "\n")
+ buf.WriteString(req.Header.Get("Content-Md5") + "\n")
+ buf.WriteString(req.Header.Get("Content-Type") + "\n")
+ buf.WriteString(req.Header.Get("Expires") + "\n")
+}
+
+// From the Amazon docs:
+//
+// StringToSign = HTTP-Verb + "\n" +
+// Content-Md5 + "\n" +
+// Content-Type + "\n" +
+// Date + "\n" +
+// CanonicalizedProtocolHeaders +
+// CanonicalizedResource;
+func stringToSignV2(req http.Request, virtualHost bool) string {
+ buf := new(bytes.Buffer)
+ // Write standard headers.
+ writeSignV2Headers(buf, req)
+ // Write canonicalized protocol headers if any.
+ writeCanonicalizedHeaders(buf, req)
+ // Write canonicalized Query resources if any.
+ writeCanonicalizedResource(buf, req, virtualHost)
+ return buf.String()
+}
+
+// writeSignV2Headers - write signV2 required headers.
+func writeSignV2Headers(buf *bytes.Buffer, req http.Request) {
+ buf.WriteString(req.Method + "\n")
+ buf.WriteString(req.Header.Get("Content-Md5") + "\n")
+ buf.WriteString(req.Header.Get("Content-Type") + "\n")
+ buf.WriteString(req.Header.Get("Date") + "\n")
+}
+
+// writeCanonicalizedHeaders - write canonicalized headers.
+func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) {
+ var protoHeaders []string
+ vals := make(map[string][]string)
+ for k, vv := range req.Header {
+ // All the AMZ headers should be lowercase
+ lk := strings.ToLower(k)
+ if strings.HasPrefix(lk, "x-amz") {
+ protoHeaders = append(protoHeaders, lk)
+ vals[lk] = vv
+ }
+ }
+ sort.Strings(protoHeaders)
+ for _, k := range protoHeaders {
+ buf.WriteString(k)
+ buf.WriteByte(':')
+ for idx, v := range vals[k] {
+ if idx > 0 {
+ buf.WriteByte(',')
+ }
+ if strings.Contains(v, "\n") {
+ // TODO: "Unfold" long headers that
+ // span multiple lines (as allowed by
+ // RFC 2616, section 4.2) by replacing
+ // the folding white-space (including
+ // new-line) by a single space.
+ buf.WriteString(v)
+ } else {
+ buf.WriteString(v)
+ }
+ }
+ buf.WriteByte('\n')
+ }
+}
+
+// AWS S3 Signature V2 calculation rule is give here:
+// http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationStringToSign
+
+// Whitelist resource list that will be used in query string for signature-V2 calculation.
+// The list should be alphabetically sorted
+var resourceList = []string{
+ "acl",
+ "delete",
+ "lifecycle",
+ "location",
+ "logging",
+ "notification",
+ "partNumber",
+ "policy",
+ "replication",
+ "requestPayment",
+ "response-cache-control",
+ "response-content-disposition",
+ "response-content-encoding",
+ "response-content-language",
+ "response-content-type",
+ "response-expires",
+ "torrent",
+ "uploadId",
+ "uploads",
+ "versionId",
+ "versioning",
+ "versions",
+ "website",
+}
+
+// From the Amazon docs:
+//
+// CanonicalizedResource = [ "/" + Bucket ] +
+// <HTTP-Request-URI, from the protocol name up to the query string> +
+// [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
+func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, virtualHost bool) {
+ // Save request URL.
+ requestURL := req.URL
+ // Get encoded URL path.
+ buf.WriteString(encodeURL2Path(&req, virtualHost))
+ if requestURL.RawQuery != "" {
+ var n int
+ vals, _ := url.ParseQuery(requestURL.RawQuery)
+ // Verify if any sub resource queries are present, if yes
+ // canonicallize them.
+ for _, resource := range resourceList {
+ if vv, ok := vals[resource]; ok && len(vv) > 0 {
+ n++
+ // First element
+ switch n {
+ case 1:
+ buf.WriteByte('?')
+ // The rest
+ default:
+ buf.WriteByte('&')
+ }
+ buf.WriteString(resource)
+ // Request parameters
+ if len(vv[0]) > 0 {
+ buf.WriteByte('=')
+ buf.WriteString(vv[0])
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v4.go b/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v4.go
new file mode 100644
index 00000000..67572b20
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/signer/request-signature-v4.go
@@ -0,0 +1,318 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package signer
+
+import (
+ "bytes"
+ "encoding/hex"
+ "net/http"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+// Signature and API related constants.
+const (
+ signV4Algorithm = "AWS4-HMAC-SHA256"
+ iso8601DateFormat = "20060102T150405Z"
+ yyyymmdd = "20060102"
+)
+
+// Different service types
+const (
+ ServiceTypeS3 = "s3"
+ ServiceTypeSTS = "sts"
+)
+
+///
+/// Excerpts from @lsegal -
+/// https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258.
+///
+/// User-Agent:
+///
+/// This is ignored from signing because signing this causes
+/// problems with generating pre-signed URLs (that are executed
+/// by other agents) or when customers pass requests through
+/// proxies, which may modify the user-agent.
+///
+///
+/// Authorization:
+///
+/// Is skipped for obvious reasons
+///
+var v4IgnoredHeaders = map[string]bool{
+ "Authorization": true,
+ "User-Agent": true,
+}
+
+// getSigningKey hmac seed to calculate final signature.
+func getSigningKey(secret, loc string, t time.Time, serviceType string) []byte {
+ date := sumHMAC([]byte("AWS4"+secret), []byte(t.Format(yyyymmdd)))
+ location := sumHMAC(date, []byte(loc))
+ service := sumHMAC(location, []byte(serviceType))
+ signingKey := sumHMAC(service, []byte("aws4_request"))
+ return signingKey
+}
+
+// getSignature final signature in hexadecimal form.
+func getSignature(signingKey []byte, stringToSign string) string {
+ return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
+}
+
+// getScope generate a string of a specific date, an AWS region, and a
+// service.
+func getScope(location string, t time.Time, serviceType string) string {
+ scope := strings.Join([]string{
+ t.Format(yyyymmdd),
+ location,
+ serviceType,
+ "aws4_request",
+ }, "/")
+ return scope
+}
+
+// GetCredential generate a credential string.
+func GetCredential(accessKeyID, location string, t time.Time, serviceType string) string {
+ scope := getScope(location, t, serviceType)
+ return accessKeyID + "/" + scope
+}
+
+// getHashedPayload get the hexadecimal value of the SHA256 hash of
+// the request payload.
+func getHashedPayload(req http.Request) string {
+ hashedPayload := req.Header.Get("X-Amz-Content-Sha256")
+ if hashedPayload == "" {
+ // Presign does not have a payload, use S3 recommended value.
+ hashedPayload = unsignedPayload
+ }
+ return hashedPayload
+}
+
+// getCanonicalHeaders generate a list of request headers for
+// signature.
+func getCanonicalHeaders(req http.Request, ignoredHeaders map[string]bool) string {
+ var headers []string
+ vals := make(map[string][]string)
+ for k, vv := range req.Header {
+ if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
+ continue // ignored header
+ }
+ headers = append(headers, strings.ToLower(k))
+ vals[strings.ToLower(k)] = vv
+ }
+ headers = append(headers, "host")
+ sort.Strings(headers)
+
+ var buf bytes.Buffer
+ // Save all the headers in canonical form <header>:<value> newline
+ // separated for each header.
+ for _, k := range headers {
+ buf.WriteString(k)
+ buf.WriteByte(':')
+ switch {
+ case k == "host":
+ buf.WriteString(getHostAddr(&req))
+ fallthrough
+ default:
+ for idx, v := range vals[k] {
+ if idx > 0 {
+ buf.WriteByte(',')
+ }
+ buf.WriteString(signV4TrimAll(v))
+ }
+ buf.WriteByte('\n')
+ }
+ }
+ return buf.String()
+}
+
+// getSignedHeaders generate all signed request headers.
+// i.e lexically sorted, semicolon-separated list of lowercase
+// request header names.
+func getSignedHeaders(req http.Request, ignoredHeaders map[string]bool) string {
+ var headers []string
+ for k := range req.Header {
+ if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
+ continue // Ignored header found continue.
+ }
+ headers = append(headers, strings.ToLower(k))
+ }
+ headers = append(headers, "host")
+ sort.Strings(headers)
+ return strings.Join(headers, ";")
+}
+
+// getCanonicalRequest generate a canonical request of style.
+//
+// canonicalRequest =
+// <HTTPMethod>\n
+// <CanonicalURI>\n
+// <CanonicalQueryString>\n
+// <CanonicalHeaders>\n
+// <SignedHeaders>\n
+// <HashedPayload>
+func getCanonicalRequest(req http.Request, ignoredHeaders map[string]bool, hashedPayload string) string {
+ req.URL.RawQuery = strings.Replace(req.URL.Query().Encode(), "+", "%20", -1)
+ canonicalRequest := strings.Join([]string{
+ req.Method,
+ s3utils.EncodePath(req.URL.Path),
+ req.URL.RawQuery,
+ getCanonicalHeaders(req, ignoredHeaders),
+ getSignedHeaders(req, ignoredHeaders),
+ hashedPayload,
+ }, "\n")
+ return canonicalRequest
+}
+
+// getStringToSign a string based on selected query values.
+func getStringToSignV4(t time.Time, location, canonicalRequest, serviceType string) string {
+ stringToSign := signV4Algorithm + "\n" + t.Format(iso8601DateFormat) + "\n"
+ stringToSign = stringToSign + getScope(location, t, serviceType) + "\n"
+ stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))
+ return stringToSign
+}
+
+// PreSignV4 presign the request, in accordance with
+// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html.
+func PreSignV4(req http.Request, accessKeyID, secretAccessKey, sessionToken, location string, expires int64) *http.Request {
+ // Presign is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ // Initial time.
+ t := time.Now().UTC()
+
+ // Get credential string.
+ credential := GetCredential(accessKeyID, location, t, ServiceTypeS3)
+
+ // Get all signed headers.
+ signedHeaders := getSignedHeaders(req, v4IgnoredHeaders)
+
+ // Set URL query.
+ query := req.URL.Query()
+ query.Set("X-Amz-Algorithm", signV4Algorithm)
+ query.Set("X-Amz-Date", t.Format(iso8601DateFormat))
+ query.Set("X-Amz-Expires", strconv.FormatInt(expires, 10))
+ query.Set("X-Amz-SignedHeaders", signedHeaders)
+ query.Set("X-Amz-Credential", credential)
+ // Set session token if available.
+ if sessionToken != "" {
+ query.Set("X-Amz-Security-Token", sessionToken)
+ }
+ req.URL.RawQuery = query.Encode()
+
+ // Get canonical request.
+ canonicalRequest := getCanonicalRequest(req, v4IgnoredHeaders, getHashedPayload(req))
+
+ // Get string to sign from canonical request.
+ stringToSign := getStringToSignV4(t, location, canonicalRequest, ServiceTypeS3)
+
+ // Gext hmac signing key.
+ signingKey := getSigningKey(secretAccessKey, location, t, ServiceTypeS3)
+
+ // Calculate signature.
+ signature := getSignature(signingKey, stringToSign)
+
+ // Add signature header to RawQuery.
+ req.URL.RawQuery += "&X-Amz-Signature=" + signature
+
+ return &req
+}
+
+// PostPresignSignatureV4 - presigned signature for PostPolicy
+// requests.
+func PostPresignSignatureV4(policyBase64 string, t time.Time, secretAccessKey, location string) string {
+ // Get signining key.
+ signingkey := getSigningKey(secretAccessKey, location, t, ServiceTypeS3)
+ // Calculate signature.
+ signature := getSignature(signingkey, policyBase64)
+ return signature
+}
+
+// SignV4STS - signature v4 for STS request.
+func SignV4STS(req http.Request, accessKeyID, secretAccessKey, location string) *http.Request {
+ return signV4(req, accessKeyID, secretAccessKey, "", location, ServiceTypeSTS)
+}
+
+// Internal function called for different service types.
+func signV4(req http.Request, accessKeyID, secretAccessKey, sessionToken, location, serviceType string) *http.Request {
+ // Signature calculation is not needed for anonymous credentials.
+ if accessKeyID == "" || secretAccessKey == "" {
+ return &req
+ }
+
+ // Initial time.
+ t := time.Now().UTC()
+
+ // Set x-amz-date.
+ req.Header.Set("X-Amz-Date", t.Format(iso8601DateFormat))
+
+ // Set session token if available.
+ if sessionToken != "" {
+ req.Header.Set("X-Amz-Security-Token", sessionToken)
+ }
+
+ hashedPayload := getHashedPayload(req)
+ if serviceType == ServiceTypeSTS {
+ // Content sha256 header is not sent with the request
+ // but it is expected to have sha256 of payload for signature
+ // in STS service type request.
+ req.Header.Del("X-Amz-Content-Sha256")
+ }
+
+ // Get canonical request.
+ canonicalRequest := getCanonicalRequest(req, v4IgnoredHeaders, hashedPayload)
+
+ // Get string to sign from canonical request.
+ stringToSign := getStringToSignV4(t, location, canonicalRequest, serviceType)
+
+ // Get hmac signing key.
+ signingKey := getSigningKey(secretAccessKey, location, t, serviceType)
+
+ // Get credential string.
+ credential := GetCredential(accessKeyID, location, t, serviceType)
+
+ // Get all signed headers.
+ signedHeaders := getSignedHeaders(req, v4IgnoredHeaders)
+
+ // Calculate signature.
+ signature := getSignature(signingKey, stringToSign)
+
+ // If regular request, construct the final authorization header.
+ parts := []string{
+ signV4Algorithm + " Credential=" + credential,
+ "SignedHeaders=" + signedHeaders,
+ "Signature=" + signature,
+ }
+
+ // Set authorization header.
+ auth := strings.Join(parts, ", ")
+ req.Header.Set("Authorization", auth)
+
+ return &req
+}
+
+// SignV4 sign the request before Do(), in accordance with
+// http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html.
+func SignV4(req http.Request, accessKeyID, secretAccessKey, sessionToken, location string) *http.Request {
+ return signV4(req, accessKeyID, secretAccessKey, sessionToken, location, ServiceTypeS3)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/signer/utils.go b/vendor/github.com/minio/minio-go/v7/pkg/signer/utils.go
new file mode 100644
index 00000000..2192a369
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/signer/utils.go
@@ -0,0 +1,59 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package signer
+
+import (
+ "crypto/hmac"
+ "net/http"
+ "strings"
+
+ "github.com/minio/sha256-simd"
+)
+
+// unsignedPayload - value to be set to X-Amz-Content-Sha256 header when
+const unsignedPayload = "UNSIGNED-PAYLOAD"
+
+// sum256 calculate sha256 sum for an input byte array.
+func sum256(data []byte) []byte {
+ hash := sha256.New()
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+// sumHMAC calculate hmac between two input byte array.
+func sumHMAC(key []byte, data []byte) []byte {
+ hash := hmac.New(sha256.New, key)
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+// getHostAddr returns host header if available, otherwise returns host from URL
+func getHostAddr(req *http.Request) string {
+ if req.Host != "" {
+ return req.Host
+ }
+ return req.URL.Host
+}
+
+// Trim leading and trailing spaces and replace sequential spaces with one space, following Trimall()
+// in http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
+func signV4TrimAll(input string) string {
+ // Compress adjacent spaces (a space is determined by
+ // unicode.IsSpace() internally here) to one space and return
+ return strings.Join(strings.Fields(input), " ")
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/sse/sse.go b/vendor/github.com/minio/minio-go/v7/pkg/sse/sse.go
new file mode 100644
index 00000000..b5fb9565
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/sse/sse.go
@@ -0,0 +1,66 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2020 MinIO, 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.
+ */
+
+package sse
+
+import "encoding/xml"
+
+// ApplySSEByDefault defines default encryption configuration, KMS or SSE. To activate
+// KMS, SSEAlgoritm needs to be set to "aws:kms"
+// Minio currently does not support Kms.
+type ApplySSEByDefault struct {
+ KmsMasterKeyID string `xml:"KMSMasterKeyID,omitempty"`
+ SSEAlgorithm string `xml:"SSEAlgorithm"`
+}
+
+// Rule layer encapsulates default encryption configuration
+type Rule struct {
+ Apply ApplySSEByDefault `xml:"ApplyServerSideEncryptionByDefault"`
+}
+
+// Configuration is the default encryption configuration structure
+type Configuration struct {
+ XMLName xml.Name `xml:"ServerSideEncryptionConfiguration"`
+ Rules []Rule `xml:"Rule"`
+}
+
+// NewConfigurationSSES3 initializes a new SSE-S3 configuration
+func NewConfigurationSSES3() *Configuration {
+ return &Configuration{
+ Rules: []Rule{
+ {
+ Apply: ApplySSEByDefault{
+ SSEAlgorithm: "AES256",
+ },
+ },
+ },
+ }
+}
+
+// NewConfigurationSSEKMS initializes a new SSE-KMS configuration
+func NewConfigurationSSEKMS(kmsMasterKey string) *Configuration {
+ return &Configuration{
+ Rules: []Rule{
+ {
+ Apply: ApplySSEByDefault{
+ KmsMasterKeyID: kmsMasterKey,
+ SSEAlgorithm: "aws:kms",
+ },
+ },
+ },
+ }
+}
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/tags/tags.go b/vendor/github.com/minio/minio-go/v7/pkg/tags/tags.go
new file mode 100644
index 00000000..d7c65af5
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/tags/tags.go
@@ -0,0 +1,341 @@
+/*
+ * MinIO Cloud Storage, (C) 2020 MinIO, 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.
+ */
+
+package tags
+
+import (
+ "encoding/xml"
+ "io"
+ "net/url"
+ "strings"
+ "unicode/utf8"
+)
+
+// Error contains tag specific error.
+type Error interface {
+ error
+ Code() string
+}
+
+type errTag struct {
+ code string
+ message string
+}
+
+// Code contains error code.
+func (err errTag) Code() string {
+ return err.code
+}
+
+// Error contains error message.
+func (err errTag) Error() string {
+ return err.message
+}
+
+var (
+ errTooManyObjectTags = &errTag{"BadRequest", "Tags cannot be more than 10"}
+ errTooManyTags = &errTag{"BadRequest", "Tags cannot be more than 50"}
+ errInvalidTagKey = &errTag{"InvalidTag", "The TagKey you have provided is invalid"}
+ errInvalidTagValue = &errTag{"InvalidTag", "The TagValue you have provided is invalid"}
+ errDuplicateTagKey = &errTag{"InvalidTag", "Cannot provide multiple Tags with the same key"}
+)
+
+// Tag comes with limitation as per
+// https://docs.aws.amazon.com/AmazonS3/latest/dev/object-tagging.html amd
+// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#tag-restrictions
+const (
+ maxKeyLength = 128
+ maxValueLength = 256
+ maxObjectTagCount = 10
+ maxTagCount = 50
+)
+
+func checkKey(key string) error {
+ if len(key) == 0 || utf8.RuneCountInString(key) > maxKeyLength || strings.Contains(key, "&") {
+ return errInvalidTagKey
+ }
+
+ return nil
+}
+
+func checkValue(value string) error {
+ if utf8.RuneCountInString(value) > maxValueLength || strings.Contains(value, "&") {
+ return errInvalidTagValue
+ }
+
+ return nil
+}
+
+// Tag denotes key and value.
+type Tag struct {
+ Key string `xml:"Key"`
+ Value string `xml:"Value"`
+}
+
+func (tag Tag) String() string {
+ return tag.Key + "=" + tag.Value
+}
+
+// IsEmpty returns whether this tag is empty or not.
+func (tag Tag) IsEmpty() bool {
+ return tag.Key == ""
+}
+
+// Validate checks this tag.
+func (tag Tag) Validate() error {
+ if err := checkKey(tag.Key); err != nil {
+ return err
+ }
+
+ return checkValue(tag.Value)
+}
+
+// MarshalXML encodes to XML data.
+func (tag Tag) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if err := tag.Validate(); err != nil {
+ return err
+ }
+
+ type subTag Tag // to avoid recursively calling MarshalXML()
+ return e.EncodeElement(subTag(tag), start)
+}
+
+// UnmarshalXML decodes XML data to tag.
+func (tag *Tag) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ type subTag Tag // to avoid recursively calling UnmarshalXML()
+ var st subTag
+ if err := d.DecodeElement(&st, &start); err != nil {
+ return err
+ }
+
+ if err := Tag(st).Validate(); err != nil {
+ return err
+ }
+
+ *tag = Tag(st)
+ return nil
+}
+
+// tagSet represents list of unique tags.
+type tagSet struct {
+ tagMap map[string]string
+ isObject bool
+}
+
+func (tags tagSet) String() string {
+ vals := make(url.Values)
+ for key, value := range tags.tagMap {
+ vals.Set(key, value)
+ }
+ return vals.Encode()
+}
+
+func (tags *tagSet) remove(key string) {
+ delete(tags.tagMap, key)
+}
+
+func (tags *tagSet) set(key, value string, failOnExist bool) error {
+ if failOnExist {
+ if _, found := tags.tagMap[key]; found {
+ return errDuplicateTagKey
+ }
+ }
+
+ if err := checkKey(key); err != nil {
+ return err
+ }
+
+ if err := checkValue(value); err != nil {
+ return err
+ }
+
+ if tags.isObject {
+ if len(tags.tagMap) == maxObjectTagCount {
+ return errTooManyObjectTags
+ }
+ } else if len(tags.tagMap) == maxTagCount {
+ return errTooManyTags
+ }
+
+ tags.tagMap[key] = value
+ return nil
+}
+
+func (tags tagSet) toMap() map[string]string {
+ m := make(map[string]string)
+ for key, value := range tags.tagMap {
+ m[key] = value
+ }
+ return m
+}
+
+// MarshalXML encodes to XML data.
+func (tags tagSet) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ tagList := struct {
+ Tags []Tag `xml:"Tag"`
+ }{}
+
+ for key, value := range tags.tagMap {
+ tagList.Tags = append(tagList.Tags, Tag{key, value})
+ }
+
+ return e.EncodeElement(tagList, start)
+}
+
+// UnmarshalXML decodes XML data to tag list.
+func (tags *tagSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ tagList := struct {
+ Tags []Tag `xml:"Tag"`
+ }{}
+
+ if err := d.DecodeElement(&tagList, &start); err != nil {
+ return err
+ }
+
+ if tags.isObject {
+ if len(tagList.Tags) > maxObjectTagCount {
+ return errTooManyObjectTags
+ }
+ } else if len(tagList.Tags) > maxTagCount {
+ return errTooManyTags
+ }
+
+ m := map[string]string{}
+ for _, tag := range tagList.Tags {
+ if _, found := m[tag.Key]; found {
+ return errDuplicateTagKey
+ }
+
+ m[tag.Key] = tag.Value
+ }
+
+ tags.tagMap = m
+ return nil
+}
+
+type tagging struct {
+ XMLName xml.Name `xml:"Tagging"`
+ TagSet *tagSet `xml:"TagSet"`
+}
+
+// Tags is list of tags of XML request/response as per
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html#API_GetBucketTagging_RequestBody
+type Tags tagging
+
+func (tags Tags) String() string {
+ return tags.TagSet.String()
+}
+
+// Remove removes a tag by its key.
+func (tags *Tags) Remove(key string) {
+ tags.TagSet.remove(key)
+}
+
+// Set sets new tag.
+func (tags *Tags) Set(key, value string) error {
+ return tags.TagSet.set(key, value, false)
+}
+
+// ToMap returns copy of tags.
+func (tags Tags) ToMap() map[string]string {
+ return tags.TagSet.toMap()
+}
+
+// MapToObjectTags converts an input map of key and value into
+// *Tags data structure with validation.
+func MapToObjectTags(tagMap map[string]string) (*Tags, error) {
+ return NewTags(tagMap, true)
+}
+
+// MapToBucketTags converts an input map of key and value into
+// *Tags data structure with validation.
+func MapToBucketTags(tagMap map[string]string) (*Tags, error) {
+ return NewTags(tagMap, false)
+}
+
+// NewTags creates Tags from tagMap, If isObject is set, it validates for object tags.
+func NewTags(tagMap map[string]string, isObject bool) (*Tags, error) {
+ tagging := &Tags{
+ TagSet: &tagSet{
+ tagMap: make(map[string]string),
+ isObject: isObject,
+ },
+ }
+
+ for key, value := range tagMap {
+ if err := tagging.TagSet.set(key, value, true); err != nil {
+ return nil, err
+ }
+ }
+
+ return tagging, nil
+}
+
+func unmarshalXML(reader io.Reader, isObject bool) (*Tags, error) {
+ tagging := &Tags{
+ TagSet: &tagSet{
+ tagMap: make(map[string]string),
+ isObject: isObject,
+ },
+ }
+
+ if err := xml.NewDecoder(reader).Decode(tagging); err != nil {
+ return nil, err
+ }
+
+ return tagging, nil
+}
+
+// ParseBucketXML decodes XML data of tags in reader specified in
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketTagging.html#API_PutBucketTagging_RequestSyntax.
+func ParseBucketXML(reader io.Reader) (*Tags, error) {
+ return unmarshalXML(reader, false)
+}
+
+// ParseObjectXML decodes XML data of tags in reader specified in
+// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectTagging.html#API_PutObjectTagging_RequestSyntax
+func ParseObjectXML(reader io.Reader) (*Tags, error) {
+ return unmarshalXML(reader, true)
+}
+
+// Parse decodes HTTP query formatted string into tags which is limited by isObject.
+// A query formatted string is like "key1=value1&key2=value2".
+func Parse(s string, isObject bool) (*Tags, error) {
+ values, err := url.ParseQuery(s)
+ if err != nil {
+ return nil, err
+ }
+
+ tagging := &Tags{
+ TagSet: &tagSet{
+ tagMap: make(map[string]string),
+ isObject: isObject,
+ },
+ }
+
+ for key := range values {
+ if err := tagging.TagSet.set(key, values.Get(key), true); err != nil {
+ return nil, err
+ }
+ }
+
+ return tagging, nil
+}
+
+// ParseObjectTags decodes HTTP query formatted string into tags. A query formatted string is like "key1=value1&key2=value2".
+func ParseObjectTags(s string) (*Tags, error) {
+ return Parse(s, true)
+}
diff --git a/vendor/github.com/minio/minio-go/v7/post-policy.go b/vendor/github.com/minio/minio-go/v7/post-policy.go
new file mode 100644
index 00000000..31a7308c
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/post-policy.go
@@ -0,0 +1,327 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "encoding/base64"
+ "fmt"
+ "strings"
+ "time"
+)
+
+// expirationDateFormat date format for expiration key in json policy.
+const expirationDateFormat = "2006-01-02T15:04:05.999Z"
+
+// policyCondition explanation:
+// http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
+//
+// Example:
+//
+// policyCondition {
+// matchType: "$eq",
+// key: "$Content-Type",
+// value: "image/png",
+// }
+//
+type policyCondition struct {
+ matchType string
+ condition string
+ value string
+}
+
+// PostPolicy - Provides strict static type conversion and validation
+// for Amazon S3's POST policy JSON string.
+type PostPolicy struct {
+ // Expiration date and time of the POST policy.
+ expiration time.Time
+ // Collection of different policy conditions.
+ conditions []policyCondition
+ // ContentLengthRange minimum and maximum allowable size for the
+ // uploaded content.
+ contentLengthRange struct {
+ min int64
+ max int64
+ }
+
+ // Post form data.
+ formData map[string]string
+}
+
+// NewPostPolicy - Instantiate new post policy.
+func NewPostPolicy() *PostPolicy {
+ p := &PostPolicy{}
+ p.conditions = make([]policyCondition, 0)
+ p.formData = make(map[string]string)
+ return p
+}
+
+// SetExpires - Sets expiration time for the new policy.
+func (p *PostPolicy) SetExpires(t time.Time) error {
+ if t.IsZero() {
+ return errInvalidArgument("No expiry time set.")
+ }
+ p.expiration = t
+ return nil
+}
+
+// SetKey - Sets an object name for the policy based upload.
+func (p *PostPolicy) SetKey(key string) error {
+ if strings.TrimSpace(key) == "" || key == "" {
+ return errInvalidArgument("Object name is empty.")
+ }
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: "$key",
+ value: key,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["key"] = key
+ return nil
+}
+
+// SetKeyStartsWith - Sets an object name that an policy based upload
+// can start with.
+func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
+ if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
+ return errInvalidArgument("Object prefix is empty.")
+ }
+ policyCond := policyCondition{
+ matchType: "starts-with",
+ condition: "$key",
+ value: keyStartsWith,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["key"] = keyStartsWith
+ return nil
+}
+
+// SetBucket - Sets bucket at which objects will be uploaded to.
+func (p *PostPolicy) SetBucket(bucketName string) error {
+ if strings.TrimSpace(bucketName) == "" || bucketName == "" {
+ return errInvalidArgument("Bucket name is empty.")
+ }
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: "$bucket",
+ value: bucketName,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["bucket"] = bucketName
+ return nil
+}
+
+// SetCondition - Sets condition for credentials, date and algorithm
+func (p *PostPolicy) SetCondition(matchType, condition, value string) error {
+ if strings.TrimSpace(value) == "" || value == "" {
+ return errInvalidArgument("No value specified for condition")
+ }
+
+ policyCond := policyCondition{
+ matchType: matchType,
+ condition: "$" + condition,
+ value: value,
+ }
+ if condition == "X-Amz-Credential" || condition == "X-Amz-Date" || condition == "X-Amz-Algorithm" {
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData[condition] = value
+ return nil
+ }
+ return errInvalidArgument("Invalid condition in policy")
+}
+
+// SetContentType - Sets content-type of the object for this policy
+// based upload.
+func (p *PostPolicy) SetContentType(contentType string) error {
+ if strings.TrimSpace(contentType) == "" || contentType == "" {
+ return errInvalidArgument("No content type specified.")
+ }
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: "$Content-Type",
+ value: contentType,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["Content-Type"] = contentType
+ return nil
+}
+
+// SetContentTypeStartsWith - Sets what content-type of the object for this policy
+// based upload can start with.
+func (p *PostPolicy) SetContentTypeStartsWith(contentTypeStartsWith string) error {
+ if strings.TrimSpace(contentTypeStartsWith) == "" || contentTypeStartsWith == "" {
+ return errInvalidArgument("No content type specified.")
+ }
+ policyCond := policyCondition{
+ matchType: "starts-with",
+ condition: "$Content-Type",
+ value: contentTypeStartsWith,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["Content-Type"] = contentTypeStartsWith
+ return nil
+}
+
+// SetContentLengthRange - Set new min and max content length
+// condition for all incoming uploads.
+func (p *PostPolicy) SetContentLengthRange(min, max int64) error {
+ if min > max {
+ return errInvalidArgument("Minimum limit is larger than maximum limit.")
+ }
+ if min < 0 {
+ return errInvalidArgument("Minimum limit cannot be negative.")
+ }
+ if max < 0 {
+ return errInvalidArgument("Maximum limit cannot be negative.")
+ }
+ p.contentLengthRange.min = min
+ p.contentLengthRange.max = max
+ return nil
+}
+
+// SetSuccessActionRedirect - Sets the redirect success url of the object for this policy
+// based upload.
+func (p *PostPolicy) SetSuccessActionRedirect(redirect string) error {
+ if strings.TrimSpace(redirect) == "" || redirect == "" {
+ return errInvalidArgument("Redirect is empty")
+ }
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: "$success_action_redirect",
+ value: redirect,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["success_action_redirect"] = redirect
+ return nil
+}
+
+// SetSuccessStatusAction - Sets the status success code of the object for this policy
+// based upload.
+func (p *PostPolicy) SetSuccessStatusAction(status string) error {
+ if strings.TrimSpace(status) == "" || status == "" {
+ return errInvalidArgument("Status is empty")
+ }
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: "$success_action_status",
+ value: status,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData["success_action_status"] = status
+ return nil
+}
+
+// SetUserMetadata - Set user metadata as a key/value couple.
+// Can be retrieved through a HEAD request or an event.
+func (p *PostPolicy) SetUserMetadata(key string, value string) error {
+ if strings.TrimSpace(key) == "" || key == "" {
+ return errInvalidArgument("Key is empty")
+ }
+ if strings.TrimSpace(value) == "" || value == "" {
+ return errInvalidArgument("Value is empty")
+ }
+ headerName := fmt.Sprintf("x-amz-meta-%s", key)
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: fmt.Sprintf("$%s", headerName),
+ value: value,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData[headerName] = value
+ return nil
+}
+
+// SetUserData - Set user data as a key/value couple.
+// Can be retrieved through a HEAD request or an event.
+func (p *PostPolicy) SetUserData(key string, value string) error {
+ if key == "" {
+ return errInvalidArgument("Key is empty")
+ }
+ if value == "" {
+ return errInvalidArgument("Value is empty")
+ }
+ headerName := fmt.Sprintf("x-amz-%s", key)
+ policyCond := policyCondition{
+ matchType: "eq",
+ condition: fmt.Sprintf("$%s", headerName),
+ value: value,
+ }
+ if err := p.addNewPolicy(policyCond); err != nil {
+ return err
+ }
+ p.formData[headerName] = value
+ return nil
+}
+
+// addNewPolicy - internal helper to validate adding new policies.
+func (p *PostPolicy) addNewPolicy(policyCond policyCondition) error {
+ if policyCond.matchType == "" || policyCond.condition == "" || policyCond.value == "" {
+ return errInvalidArgument("Policy fields are empty.")
+ }
+ p.conditions = append(p.conditions, policyCond)
+ return nil
+}
+
+// String function for printing policy in json formatted string.
+func (p PostPolicy) String() string {
+ return string(p.marshalJSON())
+}
+
+// marshalJSON - Provides Marshaled JSON in bytes.
+func (p PostPolicy) marshalJSON() []byte {
+ expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
+ var conditionsStr string
+ conditions := []string{}
+ for _, po := range p.conditions {
+ conditions = append(conditions, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.condition, po.value))
+ }
+ if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
+ conditions = append(conditions, fmt.Sprintf("[\"content-length-range\", %d, %d]",
+ p.contentLengthRange.min, p.contentLengthRange.max))
+ }
+ if len(conditions) > 0 {
+ conditionsStr = `"conditions":[` + strings.Join(conditions, ",") + "]"
+ }
+ retStr := "{"
+ retStr = retStr + expirationStr + ","
+ retStr = retStr + conditionsStr
+ retStr = retStr + "}"
+ return []byte(retStr)
+}
+
+// base64 - Produces base64 of PostPolicy's Marshaled json.
+func (p PostPolicy) base64() string {
+ return base64.StdEncoding.EncodeToString(p.marshalJSON())
+}
diff --git a/vendor/github.com/minio/minio-go/v7/retry-continous.go b/vendor/github.com/minio/minio-go/v7/retry-continous.go
new file mode 100644
index 00000000..3d25883b
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/retry-continous.go
@@ -0,0 +1,69 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import "time"
+
+// newRetryTimerContinous creates a timer with exponentially increasing delays forever.
+func (c Client) newRetryTimerContinous(unit time.Duration, cap time.Duration, jitter float64, doneCh chan struct{}) <-chan int {
+ attemptCh := make(chan int)
+
+ // normalize jitter to the range [0, 1.0]
+ if jitter < NoJitter {
+ jitter = NoJitter
+ }
+ if jitter > MaxJitter {
+ jitter = MaxJitter
+ }
+
+ // computes the exponential backoff duration according to
+ // https://www.awsarchitectureblog.com/2015/03/backoff.html
+ exponentialBackoffWait := func(attempt int) time.Duration {
+ // 1<<uint(attempt) below could overflow, so limit the value of attempt
+ maxAttempt := 30
+ if attempt > maxAttempt {
+ attempt = maxAttempt
+ }
+ //sleep = random_between(0, min(cap, base * 2 ** attempt))
+ sleep := unit * time.Duration(1<<uint(attempt))
+ if sleep > cap {
+ sleep = cap
+ }
+ if jitter != NoJitter {
+ sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
+ }
+ return sleep
+ }
+
+ go func() {
+ defer close(attemptCh)
+ var nextBackoff int
+ for {
+ select {
+ // Attempts starts.
+ case attemptCh <- nextBackoff:
+ nextBackoff++
+ case <-doneCh:
+ // Stop the routine.
+ return
+ }
+ time.Sleep(exponentialBackoffWait(nextBackoff))
+ }
+ }()
+ return attemptCh
+}
diff --git a/vendor/github.com/minio/minio-go/v7/retry.go b/vendor/github.com/minio/minio-go/v7/retry.go
new file mode 100644
index 00000000..598af297
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/retry.go
@@ -0,0 +1,124 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "context"
+ "net/http"
+ "time"
+)
+
+// MaxRetry is the maximum number of retries before stopping.
+var MaxRetry = 10
+
+// MaxJitter will randomize over the full exponential backoff time
+const MaxJitter = 1.0
+
+// NoJitter disables the use of jitter for randomizing the exponential backoff time
+const NoJitter = 0.0
+
+// DefaultRetryUnit - default unit multiplicative per retry.
+// defaults to 200 * time.Millisecond
+var DefaultRetryUnit = 200 * time.Millisecond
+
+// DefaultRetryCap - Each retry attempt never waits no longer than
+// this maximum time duration.
+var DefaultRetryCap = time.Second
+
+// newRetryTimer creates a timer with exponentially increasing
+// delays until the maximum retry attempts are reached.
+func (c Client) newRetryTimer(ctx context.Context, maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int {
+ attemptCh := make(chan int)
+
+ // computes the exponential backoff duration according to
+ // https://www.awsarchitectureblog.com/2015/03/backoff.html
+ exponentialBackoffWait := func(attempt int) time.Duration {
+ // normalize jitter to the range [0, 1.0]
+ if jitter < NoJitter {
+ jitter = NoJitter
+ }
+ if jitter > MaxJitter {
+ jitter = MaxJitter
+ }
+
+ //sleep = random_between(0, min(cap, base * 2 ** attempt))
+ sleep := unit * time.Duration(1<<uint(attempt))
+ if sleep > cap {
+ sleep = cap
+ }
+ if jitter != NoJitter {
+ sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
+ }
+ return sleep
+ }
+
+ go func() {
+ defer close(attemptCh)
+ for i := 0; i < maxRetry; i++ {
+ select {
+ case attemptCh <- i + 1:
+ case <-ctx.Done():
+ return
+ }
+
+ select {
+ case <-time.After(exponentialBackoffWait(i)):
+ case <-ctx.Done():
+ return
+ }
+ }
+ }()
+ return attemptCh
+}
+
+// List of AWS S3 error codes which are retryable.
+var retryableS3Codes = map[string]struct{}{
+ "RequestError": {},
+ "RequestTimeout": {},
+ "Throttling": {},
+ "ThrottlingException": {},
+ "RequestLimitExceeded": {},
+ "RequestThrottled": {},
+ "InternalError": {},
+ "ExpiredToken": {},
+ "ExpiredTokenException": {},
+ "SlowDown": {},
+ // Add more AWS S3 codes here.
+}
+
+// isS3CodeRetryable - is s3 error code retryable.
+func isS3CodeRetryable(s3Code string) (ok bool) {
+ _, ok = retryableS3Codes[s3Code]
+ return ok
+}
+
+// List of HTTP status codes which are retryable.
+var retryableHTTPStatusCodes = map[int]struct{}{
+ 429: {}, // http.StatusTooManyRequests is not part of the Go 1.5 library, yet
+ http.StatusInternalServerError: {},
+ http.StatusBadGateway: {},
+ http.StatusServiceUnavailable: {},
+ http.StatusGatewayTimeout: {},
+ // Add more HTTP status codes here.
+}
+
+// isHTTPStatusRetryable - is HTTP error code retryable.
+func isHTTPStatusRetryable(httpStatusCode int) (ok bool) {
+ _, ok = retryableHTTPStatusCodes[httpStatusCode]
+ return ok
+}
diff --git a/vendor/github.com/minio/minio-go/v7/s3-endpoints.go b/vendor/github.com/minio/minio-go/v7/s3-endpoints.go
new file mode 100644
index 00000000..9c8f02c8
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/s3-endpoints.go
@@ -0,0 +1,57 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+// awsS3EndpointMap Amazon S3 endpoint map.
+var awsS3EndpointMap = map[string]string{
+ "us-east-1": "s3.dualstack.us-east-1.amazonaws.com",
+ "us-east-2": "s3.dualstack.us-east-2.amazonaws.com",
+ "us-west-2": "s3.dualstack.us-west-2.amazonaws.com",
+ "us-west-1": "s3.dualstack.us-west-1.amazonaws.com",
+ "ca-central-1": "s3.dualstack.ca-central-1.amazonaws.com",
+ "eu-west-1": "s3.dualstack.eu-west-1.amazonaws.com",
+ "eu-west-2": "s3.dualstack.eu-west-2.amazonaws.com",
+ "eu-west-3": "s3.dualstack.eu-west-3.amazonaws.com",
+ "eu-central-1": "s3.dualstack.eu-central-1.amazonaws.com",
+ "eu-north-1": "s3.dualstack.eu-north-1.amazonaws.com",
+ "eu-south-1": "s3.dualstack.eu-south-1.amazonaws.com",
+ "ap-east-1": "s3.dualstack.ap-east-1.amazonaws.com",
+ "ap-south-1": "s3.dualstack.ap-south-1.amazonaws.com",
+ "ap-southeast-1": "s3.dualstack.ap-southeast-1.amazonaws.com",
+ "ap-southeast-2": "s3.dualstack.ap-southeast-2.amazonaws.com",
+ "ap-northeast-1": "s3.dualstack.ap-northeast-1.amazonaws.com",
+ "ap-northeast-2": "s3.dualstack.ap-northeast-2.amazonaws.com",
+ "ap-northeast-3": "s3.dualstack.ap-northeast-3.amazonaws.com",
+ "af-south-1": "s3.dualstack.af-south-1.amazonaws.com",
+ "me-south-1": "s3.dualstack.me-south-1.amazonaws.com",
+ "sa-east-1": "s3.dualstack.sa-east-1.amazonaws.com",
+ "us-gov-west-1": "s3.dualstack.us-gov-west-1.amazonaws.com",
+ "us-gov-east-1": "s3.dualstack.us-gov-east-1.amazonaws.com",
+ "cn-north-1": "s3.dualstack.cn-north-1.amazonaws.com.cn",
+ "cn-northwest-1": "s3.dualstack.cn-northwest-1.amazonaws.com.cn",
+}
+
+// getS3Endpoint get Amazon S3 endpoint based on the bucket location.
+func getS3Endpoint(bucketLocation string) (s3Endpoint string) {
+ s3Endpoint, ok := awsS3EndpointMap[bucketLocation]
+ if !ok {
+ // Default to 's3.dualstack.us-east-1.amazonaws.com' endpoint.
+ s3Endpoint = "s3.dualstack.us-east-1.amazonaws.com"
+ }
+ return s3Endpoint
+}
diff --git a/vendor/github.com/minio/minio-go/v7/s3-error.go b/vendor/github.com/minio/minio-go/v7/s3-error.go
new file mode 100644
index 00000000..f365157e
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/s3-error.go
@@ -0,0 +1,61 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+// Non exhaustive list of AWS S3 standard error responses -
+// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
+var s3ErrorResponseMap = map[string]string{
+ "AccessDenied": "Access Denied.",
+ "BadDigest": "The Content-Md5 you specified did not match what we received.",
+ "EntityTooSmall": "Your proposed upload is smaller than the minimum allowed object size.",
+ "EntityTooLarge": "Your proposed upload exceeds the maximum allowed object size.",
+ "IncompleteBody": "You did not provide the number of bytes specified by the Content-Length HTTP header.",
+ "InternalError": "We encountered an internal error, please try again.",
+ "InvalidAccessKeyId": "The access key ID you provided does not exist in our records.",
+ "InvalidBucketName": "The specified bucket is not valid.",
+ "InvalidDigest": "The Content-Md5 you specified is not valid.",
+ "InvalidRange": "The requested range is not satisfiable",
+ "MalformedXML": "The XML you provided was not well-formed or did not validate against our published schema.",
+ "MissingContentLength": "You must provide the Content-Length HTTP header.",
+ "MissingContentMD5": "Missing required header for this request: Content-Md5.",
+ "MissingRequestBodyError": "Request body is empty.",
+ "NoSuchBucket": "The specified bucket does not exist.",
+ "NoSuchBucketPolicy": "The bucket policy does not exist",
+ "NoSuchKey": "The specified key does not exist.",
+ "NoSuchUpload": "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
+ "NotImplemented": "A header you provided implies functionality that is not implemented",
+ "PreconditionFailed": "At least one of the pre-conditions you specified did not hold",
+ "RequestTimeTooSkewed": "The difference between the request time and the server's time is too large.",
+ "SignatureDoesNotMatch": "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
+ "MethodNotAllowed": "The specified method is not allowed against this resource.",
+ "InvalidPart": "One or more of the specified parts could not be found.",
+ "InvalidPartOrder": "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
+ "InvalidObjectState": "The operation is not valid for the current state of the object.",
+ "AuthorizationHeaderMalformed": "The authorization header is malformed; the region is wrong.",
+ "MalformedPOSTRequest": "The body of your POST request is not well-formed multipart/form-data.",
+ "BucketNotEmpty": "The bucket you tried to delete is not empty",
+ "AllAccessDisabled": "All access to this bucket has been disabled.",
+ "MalformedPolicy": "Policy has invalid resource.",
+ "MissingFields": "Missing fields in request.",
+ "AuthorizationQueryParametersError": "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
+ "MalformedDate": "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
+ "BucketAlreadyOwnedByYou": "Your previous request to create the named bucket succeeded and you already own it.",
+ "InvalidDuration": "Duration provided in the request is invalid.",
+ "XAmzContentSHA256Mismatch": "The provided 'x-amz-content-sha256' header does not match what was computed.",
+ // Add new API errors here.
+}
diff --git a/vendor/github.com/minio/minio-go/v7/transport.go b/vendor/github.com/minio/minio-go/v7/transport.go
new file mode 100644
index 00000000..d5ad15b8
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/transport.go
@@ -0,0 +1,83 @@
+// +build go1.7 go1.8
+
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2017-2018 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "os"
+ "time"
+)
+
+// mustGetSystemCertPool - return system CAs or empty pool in case of error (or windows)
+func mustGetSystemCertPool() *x509.CertPool {
+ pool, err := x509.SystemCertPool()
+ if err != nil {
+ return x509.NewCertPool()
+ }
+ return pool
+}
+
+// DefaultTransport - this default transport is similar to
+// http.DefaultTransport but with additional param DisableCompression
+// is set to true to avoid decompressing content with 'gzip' encoding.
+var DefaultTransport = func(secure bool) (*http.Transport, error) {
+ tr := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ }).DialContext,
+ MaxIdleConns: 256,
+ MaxIdleConnsPerHost: 16,
+ ResponseHeaderTimeout: time.Minute,
+ IdleConnTimeout: time.Minute,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 10 * time.Second,
+ // Set this value so that the underlying transport round-tripper
+ // doesn't try to auto decode the body of objects with
+ // content-encoding set to `gzip`.
+ //
+ // Refer:
+ // https://golang.org/src/net/http/transport.go?h=roundTrip#L1843
+ DisableCompression: true,
+ }
+
+ if secure {
+ tr.TLSClientConfig = &tls.Config{
+ // Can't use SSLv3 because of POODLE and BEAST
+ // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
+ // Can't use TLSv1.1 because of RC4 cipher usage
+ MinVersion: tls.VersionTLS12,
+ }
+ if f := os.Getenv("SSL_CERT_FILE"); f != "" {
+ rootCAs := mustGetSystemCertPool()
+ data, err := ioutil.ReadFile(f)
+ if err == nil {
+ rootCAs.AppendCertsFromPEM(data)
+ }
+ tr.TLSClientConfig.RootCAs = rootCAs
+ }
+ }
+ return tr, nil
+}
diff --git a/vendor/github.com/minio/minio-go/v7/utils.go b/vendor/github.com/minio/minio-go/v7/utils.go
new file mode 100644
index 00000000..4bdf1a3c
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/utils.go
@@ -0,0 +1,488 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2015-2017 MinIO, 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.
+ */
+
+package minio
+
+import (
+ "crypto/md5"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/xml"
+ "fmt"
+ "hash"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ md5simd "github.com/minio/md5-simd"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/sha256-simd"
+)
+
+func trimEtag(etag string) string {
+ etag = strings.TrimPrefix(etag, "\"")
+ return strings.TrimSuffix(etag, "\"")
+}
+
+var expirationRegex = regexp.MustCompile(`expiry-date="(.*?)", rule-id="(.*?)"`)
+
+func amzExpirationToExpiryDateRuleID(expiration string) (time.Time, string) {
+ if matches := expirationRegex.FindStringSubmatch(expiration); len(matches) == 3 {
+ expTime, err := time.Parse(http.TimeFormat, matches[1])
+ if err != nil {
+ return time.Time{}, ""
+ }
+ return expTime, matches[2]
+ }
+ return time.Time{}, ""
+}
+
+// xmlDecoder provide decoded value in xml.
+func xmlDecoder(body io.Reader, v interface{}) error {
+ d := xml.NewDecoder(body)
+ return d.Decode(v)
+}
+
+// sum256 calculate sha256sum for an input byte array, returns hex encoded.
+func sum256Hex(data []byte) string {
+ hash := newSHA256Hasher()
+ defer hash.Close()
+ hash.Write(data)
+ return hex.EncodeToString(hash.Sum(nil))
+}
+
+// sumMD5Base64 calculate md5sum for an input byte array, returns base64 encoded.
+func sumMD5Base64(data []byte) string {
+ hash := newMd5Hasher()
+ defer hash.Close()
+ hash.Write(data)
+ return base64.StdEncoding.EncodeToString(hash.Sum(nil))
+}
+
+// getEndpointURL - construct a new endpoint.
+func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
+ if strings.Contains(endpoint, ":") {
+ host, _, err := net.SplitHostPort(endpoint)
+ if err != nil {
+ return nil, err
+ }
+ if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
+ msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
+ return nil, errInvalidArgument(msg)
+ }
+ } else {
+ if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
+ msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
+ return nil, errInvalidArgument(msg)
+ }
+ }
+ // If secure is false, use 'http' scheme.
+ scheme := "https"
+ if !secure {
+ scheme = "http"
+ }
+
+ // Construct a secured endpoint URL.
+ endpointURLStr := scheme + "://" + endpoint
+ endpointURL, err := url.Parse(endpointURLStr)
+ if err != nil {
+ return nil, err
+ }
+
+ // Validate incoming endpoint URL.
+ if err := isValidEndpointURL(*endpointURL); err != nil {
+ return nil, err
+ }
+ return endpointURL, nil
+}
+
+// closeResponse close non nil response with any response Body.
+// convenient wrapper to drain any remaining data on response body.
+//
+// Subsequently this allows golang http RoundTripper
+// to re-use the same connection for future requests.
+func closeResponse(resp *http.Response) {
+ // Callers should close resp.Body when done reading from it.
+ // If resp.Body is not closed, the Client's underlying RoundTripper
+ // (typically Transport) may not be able to re-use a persistent TCP
+ // connection to the server for a subsequent "keep-alive" request.
+ if resp != nil && resp.Body != nil {
+ // Drain any remaining Body and then close the connection.
+ // Without this closing connection would disallow re-using
+ // the same connection for future uses.
+ // - http://stackoverflow.com/a/17961593/4465767
+ io.Copy(ioutil.Discard, resp.Body)
+ resp.Body.Close()
+ }
+}
+
+var (
+ // Hex encoded string of nil sha256sum bytes.
+ emptySHA256Hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
+ // Sentinel URL is the default url value which is invalid.
+ sentinelURL = url.URL{}
+)
+
+// Verify if input endpoint URL is valid.
+func isValidEndpointURL(endpointURL url.URL) error {
+ if endpointURL == sentinelURL {
+ return errInvalidArgument("Endpoint url cannot be empty.")
+ }
+ if endpointURL.Path != "/" && endpointURL.Path != "" {
+ return errInvalidArgument("Endpoint url cannot have fully qualified paths.")
+ }
+ if strings.Contains(endpointURL.Host, ".s3.amazonaws.com") {
+ if !s3utils.IsAmazonEndpoint(endpointURL) {
+ return errInvalidArgument("Amazon S3 endpoint should be 's3.amazonaws.com'.")
+ }
+ }
+ if strings.Contains(endpointURL.Host, ".googleapis.com") {
+ if !s3utils.IsGoogleEndpoint(endpointURL) {
+ return errInvalidArgument("Google Cloud Storage endpoint should be 'storage.googleapis.com'.")
+ }
+ }
+ return nil
+}
+
+// Verify if input expires value is valid.
+func isValidExpiry(expires time.Duration) error {
+ expireSeconds := int64(expires / time.Second)
+ if expireSeconds < 1 {
+ return errInvalidArgument("Expires cannot be lesser than 1 second.")
+ }
+ if expireSeconds > 604800 {
+ return errInvalidArgument("Expires cannot be greater than 7 days.")
+ }
+ return nil
+}
+
+// Extract only necessary metadata header key/values by
+// filtering them out with a list of custom header keys.
+func extractObjMetadata(header http.Header) http.Header {
+ preserveKeys := []string{
+ "Content-Type",
+ "Cache-Control",
+ "Content-Encoding",
+ "Content-Language",
+ "Content-Disposition",
+ "X-Amz-Storage-Class",
+ "X-Amz-Object-Lock-Mode",
+ "X-Amz-Object-Lock-Retain-Until-Date",
+ "X-Amz-Object-Lock-Legal-Hold",
+ "X-Amz-Website-Redirect-Location",
+ "X-Amz-Server-Side-Encryption",
+ "X-Amz-Tagging-Count",
+ "X-Amz-Meta-",
+ // Add new headers to be preserved.
+ // if you add new headers here, please extend
+ // PutObjectOptions{} to preserve them
+ // upon upload as well.
+ }
+ filteredHeader := make(http.Header)
+ for k, v := range header {
+ var found bool
+ for _, prefix := range preserveKeys {
+ if !strings.HasPrefix(k, prefix) {
+ continue
+ }
+ found = true
+ break
+ }
+ if found {
+ filteredHeader[k] = v
+ }
+ }
+ return filteredHeader
+}
+
+// ToObjectInfo converts http header values into ObjectInfo type,
+// extracts metadata and fills in all the necessary fields in ObjectInfo.
+func ToObjectInfo(bucketName string, objectName string, h http.Header) (ObjectInfo, error) {
+ var err error
+ // Trim off the odd double quotes from ETag in the beginning and end.
+ etag := trimEtag(h.Get("ETag"))
+
+ // Parse content length is exists
+ var size int64 = -1
+ contentLengthStr := h.Get("Content-Length")
+ if contentLengthStr != "" {
+ size, err = strconv.ParseInt(contentLengthStr, 10, 64)
+ if err != nil {
+ // Content-Length is not valid
+ return ObjectInfo{}, ErrorResponse{
+ Code: "InternalError",
+ Message: fmt.Sprintf("Content-Length is not an integer, failed with %v", err),
+ BucketName: bucketName,
+ Key: objectName,
+ RequestID: h.Get("x-amz-request-id"),
+ HostID: h.Get("x-amz-id-2"),
+ Region: h.Get("x-amz-bucket-region"),
+ }
+ }
+ }
+
+ // Parse Last-Modified has http time format.
+ date, err := time.Parse(http.TimeFormat, h.Get("Last-Modified"))
+ if err != nil {
+ return ObjectInfo{}, ErrorResponse{
+ Code: "InternalError",
+ Message: fmt.Sprintf("Last-Modified time format is invalid, failed with %v", err),
+ BucketName: bucketName,
+ Key: objectName,
+ RequestID: h.Get("x-amz-request-id"),
+ HostID: h.Get("x-amz-id-2"),
+ Region: h.Get("x-amz-bucket-region"),
+ }
+ }
+
+ // Fetch content type if any present.
+ contentType := strings.TrimSpace(h.Get("Content-Type"))
+ if contentType == "" {
+ contentType = "application/octet-stream"
+ }
+
+ expiryStr := h.Get("Expires")
+ var expiry time.Time
+ if expiryStr != "" {
+ expiry, _ = time.Parse(http.TimeFormat, expiryStr)
+ }
+
+ metadata := extractObjMetadata(h)
+ userMetadata := make(map[string]string)
+ for k, v := range metadata {
+ if strings.HasPrefix(k, "X-Amz-Meta-") {
+ userMetadata[strings.TrimPrefix(k, "X-Amz-Meta-")] = v[0]
+ }
+ }
+ userTags := s3utils.TagDecode(h.Get(amzTaggingHeader))
+
+ var tagCount int
+ if count := h.Get(amzTaggingCount); count != "" {
+ tagCount, err = strconv.Atoi(count)
+ if err != nil {
+ return ObjectInfo{}, ErrorResponse{
+ Code: "InternalError",
+ Message: fmt.Sprintf("x-amz-tagging-count is not an integer, failed with %v", err),
+ BucketName: bucketName,
+ Key: objectName,
+ RequestID: h.Get("x-amz-request-id"),
+ HostID: h.Get("x-amz-id-2"),
+ Region: h.Get("x-amz-bucket-region"),
+ }
+ }
+ }
+
+ // extract lifecycle expiry date and rule ID
+ expTime, ruleID := amzExpirationToExpiryDateRuleID(h.Get(amzExpiration))
+
+ deleteMarker := h.Get(amzDeleteMarker) == "true"
+
+ // Save object metadata info.
+ return ObjectInfo{
+ ETag: etag,
+ Key: objectName,
+ Size: size,
+ LastModified: date,
+ ContentType: contentType,
+ Expires: expiry,
+ VersionID: h.Get(amzVersionID),
+ IsDeleteMarker: deleteMarker,
+ ReplicationStatus: h.Get(amzReplicationStatus),
+ Expiration: expTime,
+ ExpirationRuleID: ruleID,
+ // Extract only the relevant header keys describing the object.
+ // following function filters out a list of standard set of keys
+ // which are not part of object metadata.
+ Metadata: metadata,
+ UserMetadata: userMetadata,
+ UserTags: userTags,
+ UserTagCount: tagCount,
+ }, nil
+}
+
+var readFull = func(r io.Reader, buf []byte) (n int, err error) {
+ // ReadFull reads exactly len(buf) bytes from r into buf.
+ // It returns the number of bytes copied and an error if
+ // fewer bytes were read. The error is EOF only if no bytes
+ // were read. If an EOF happens after reading some but not
+ // all the bytes, ReadFull returns ErrUnexpectedEOF.
+ // On return, n == len(buf) if and only if err == nil.
+ // If r returns an error having read at least len(buf) bytes,
+ // the error is dropped.
+ for n < len(buf) && err == nil {
+ var nn int
+ nn, err = r.Read(buf[n:])
+ // Some spurious io.Reader's return
+ // io.ErrUnexpectedEOF when nn == 0
+ // this behavior is undocumented
+ // so we are on purpose not using io.ReadFull
+ // implementation because this can lead
+ // to custom handling, to avoid that
+ // we simply modify the original io.ReadFull
+ // implementation to avoid this issue.
+ // io.ErrUnexpectedEOF with nn == 0 really
+ // means that io.EOF
+ if err == io.ErrUnexpectedEOF && nn == 0 {
+ err = io.EOF
+ }
+ n += nn
+ }
+ if n >= len(buf) {
+ err = nil
+ } else if n > 0 && err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// regCred matches credential string in HTTP header
+var regCred = regexp.MustCompile("Credential=([A-Z0-9]+)/")
+
+// regCred matches signature string in HTTP header
+var regSign = regexp.MustCompile("Signature=([[0-9a-f]+)")
+
+// Redact out signature value from authorization string.
+func redactSignature(origAuth string) string {
+ if !strings.HasPrefix(origAuth, signV4Algorithm) {
+ // Set a temporary redacted auth
+ return "AWS **REDACTED**:**REDACTED**"
+ }
+
+ /// Signature V4 authorization header.
+
+ // Strip out accessKeyID from:
+ // Credential=<access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request
+ newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
+
+ // Strip out 256-bit signature from: Signature=<256-bit signature>
+ return regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
+}
+
+// Get default location returns the location based on the input
+// URL `u`, if region override is provided then all location
+// defaults to regionOverride.
+//
+// If no other cases match then the location is set to `us-east-1`
+// as a last resort.
+func getDefaultLocation(u url.URL, regionOverride string) (location string) {
+ if regionOverride != "" {
+ return regionOverride
+ }
+ region := s3utils.GetRegionFromURL(u)
+ if region == "" {
+ region = "us-east-1"
+ }
+ return region
+}
+
+var supportedHeaders = []string{
+ "content-type",
+ "cache-control",
+ "content-encoding",
+ "content-disposition",
+ "content-language",
+ "x-amz-website-redirect-location",
+ "x-amz-object-lock-mode",
+ "x-amz-metadata-directive",
+ "x-amz-object-lock-retain-until-date",
+ "expires",
+ "x-amz-replication-status",
+ // Add more supported headers here.
+}
+
+// isStorageClassHeader returns true if the header is a supported storage class header
+func isStorageClassHeader(headerKey string) bool {
+ return strings.EqualFold(amzStorageClass, headerKey)
+}
+
+// isStandardHeader returns true if header is a supported header and not a custom header
+func isStandardHeader(headerKey string) bool {
+ key := strings.ToLower(headerKey)
+ for _, header := range supportedHeaders {
+ if strings.ToLower(header) == key {
+ return true
+ }
+ }
+ return false
+}
+
+// sseHeaders is list of server side encryption headers
+var sseHeaders = []string{
+ "x-amz-server-side-encryption",
+ "x-amz-server-side-encryption-aws-kms-key-id",
+ "x-amz-server-side-encryption-context",
+ "x-amz-server-side-encryption-customer-algorithm",
+ "x-amz-server-side-encryption-customer-key",
+ "x-amz-server-side-encryption-customer-key-MD5",
+}
+
+// isSSEHeader returns true if header is a server side encryption header.
+func isSSEHeader(headerKey string) bool {
+ key := strings.ToLower(headerKey)
+ for _, h := range sseHeaders {
+ if strings.ToLower(h) == key {
+ return true
+ }
+ }
+ return false
+}
+
+// isAmzHeader returns true if header is a x-amz-meta-* or x-amz-acl header.
+func isAmzHeader(headerKey string) bool {
+ key := strings.ToLower(headerKey)
+
+ return strings.HasPrefix(key, "x-amz-meta-") || strings.HasPrefix(key, "x-amz-grant-") || key == "x-amz-acl" || isSSEHeader(headerKey)
+}
+
+var md5Pool = sync.Pool{New: func() interface{} { return md5.New() }}
+var sha256Pool = sync.Pool{New: func() interface{} { return sha256.New() }}
+
+func newMd5Hasher() md5simd.Hasher {
+ return hashWrapper{Hash: md5Pool.New().(hash.Hash), isMD5: true}
+}
+
+func newSHA256Hasher() md5simd.Hasher {
+ return hashWrapper{Hash: sha256Pool.New().(hash.Hash), isSHA256: true}
+}
+
+// hashWrapper implements the md5simd.Hasher interface.
+type hashWrapper struct {
+ hash.Hash
+ isMD5 bool
+ isSHA256 bool
+}
+
+// Close will put the hasher back into the pool.
+func (m hashWrapper) Close() {
+ if m.isMD5 && m.Hash != nil {
+ m.Reset()
+ md5Pool.Put(m.Hash)
+ }
+ if m.isSHA256 && m.Hash != nil {
+ m.Reset()
+ sha256Pool.Put(m.Hash)
+ }
+ m.Hash = nil
+}
diff --git a/vendor/github.com/minio/sha256-simd/.gitignore b/vendor/github.com/minio/sha256-simd/.gitignore
new file mode 100644
index 00000000..c56069fe
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/.gitignore
@@ -0,0 +1 @@
+*.test \ No newline at end of file
diff --git a/vendor/github.com/minio/sha256-simd/LICENSE b/vendor/github.com/minio/sha256-simd/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/minio/sha256-simd/README.md b/vendor/github.com/minio/sha256-simd/README.md
new file mode 100644
index 00000000..6117488d
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/README.md
@@ -0,0 +1,137 @@
+# sha256-simd
+
+Accelerate SHA256 computations in pure Go using AVX512, SHA Extensions for x86 and ARM64 for ARM.
+On AVX512 it provides an up to 8x improvement (over 3 GB/s per core).
+SHA Extensions give a performance boost of close to 4x over native.
+
+## Introduction
+
+This package is designed as a replacement for `crypto/sha256`.
+For ARM CPUs with the Cryptography Extensions, advantage is taken of the SHA2 instructions resulting in a massive performance improvement.
+
+This package uses Golang assembly.
+The AVX512 version is based on the Intel's "multi-buffer crypto library for IPSec" whereas the other Intel implementations are described in "Fast SHA-256 Implementations on Intel Architecture Processors" by J. Guilford et al.
+
+## Support for Intel SHA Extensions
+
+Support for the Intel SHA Extensions has been added by Kristofer Peterson (@svenski123), originally developed for spacemeshos [here](https://github.com/spacemeshos/POET/issues/23). On CPUs that support it (known thus far Intel Celeron J3455 and AMD Ryzen) it gives a significant boost in performance (with thanks to @AudriusButkevicius for reporting the results; full results [here](https://github.com/minio/sha256-simd/pull/37#issuecomment-451607827)).
+
+```
+$ benchcmp avx2.txt sha-ext.txt
+benchmark AVX2 MB/s SHA Ext MB/s speedup
+BenchmarkHash5M 514.40 1975.17 3.84x
+```
+
+Thanks to Kristofer Peterson, we also added additional performance changes such as optimized padding,
+endian conversions which sped up all implementations i.e. Intel SHA alone while doubled performance for small sizes,
+the other changes increased everything roughly 50%.
+
+## Support for AVX512
+
+We have added support for AVX512 which results in an up to 8x performance improvement over AVX2 (3.0 GHz Xeon Platinum 8124M CPU):
+
+```
+$ benchcmp avx2.txt avx512.txt
+benchmark AVX2 MB/s AVX512 MB/s speedup
+BenchmarkHash5M 448.62 3498.20 7.80x
+```
+
+The original code was developed by Intel as part of the [multi-buffer crypto library](https://github.com/intel/intel-ipsec-mb) for IPSec or more specifically this [AVX512](https://github.com/intel/intel-ipsec-mb/blob/master/avx512/sha256_x16_avx512.asm) implementation. The key idea behind it is to process a total of 16 checksums in parallel by โ€œtransposingโ€ 16 (independent) messages of 64 bytes between a total of 16 ZMM registers (each 64 bytes wide).
+
+Transposing the input messages means that in order to take full advantage of the speedup you need to have a (server) workload where multiple threads are doing SHA256 calculations in parallel. Unfortunately for this algorithm it is not possible for two message blocks processed in parallel to be dependent on one anotherโ€Šโ€”โ€Šbecause then the (interim) result of the first part of the message has to be an input into the processing of the second part of the message.
+
+Whereas the original Intel C implementation requires some sort of explicit scheduling of messages to be processed in parallel, for Golang it makes sense to take advantage of channels in order to group messages together and use channels as well for sending back the results (thereby effectively decoupling the calculations). We have implemented a fairly simple scheduling mechanism that seems to work well in practice.
+
+Due to this different way of scheduling, we decided to use an explicit method to instantiate the AVX512 version. Essentially one or more AVX512 processing servers ([`Avx512Server`](https://github.com/minio/sha256-simd/blob/master/sha256blockAvx512_amd64.go#L294)) have to be created whereby each server can hash over 3 GB/s on a single core. An `hash.Hash` object ([`Avx512Digest`](https://github.com/minio/sha256-simd/blob/master/sha256blockAvx512_amd64.go#L45)) is then instantiated using one of these servers and used in the regular fashion:
+
+```go
+import "github.com/minio/sha256-simd"
+
+func main() {
+ server := sha256.NewAvx512Server()
+ h512 := sha256.NewAvx512(server)
+ h512.Write(fileBlock)
+ digest := h512.Sum([]byte{})
+}
+```
+
+Note that, because of the scheduling overhead, for small messages (< 1 MB) you will be better off using the regular SHA256 hashing (but those are typically not performance critical anyway). Some other tips to get the best performance:
+* Have many go routines doing SHA256 calculations in parallel.
+* Try to Write() messages in multiples of 64 bytes.
+* Try to keep the overall length of messages to a roughly similar size ie. 5 MB (this way all 16 โ€˜lanesโ€™ in the AVX512 computations are contributing as much as possible).
+
+More detailed information can be found in this [blog](https://blog.minio.io/accelerate-sha256-up-to-8x-over-3-gb-s-per-core-with-avx512-a0b1d64f78f) post including scaling across cores.
+
+## Drop-In Replacement
+
+The following code snippet shows how you can use `github.com/minio/sha256-simd`.
+This will automatically select the fastest method for the architecture on which it will be executed.
+
+```go
+import "github.com/minio/sha256-simd"
+
+func main() {
+ ...
+ shaWriter := sha256.New()
+ io.Copy(shaWriter, file)
+ ...
+}
+```
+
+## Performance
+
+Below is the speed in MB/s for a single core (ranked fast to slow) for blocks larger than 1 MB.
+
+| Processor | SIMD | Speed (MB/s) |
+| --------------------------------- | ------- | ------------:|
+| 3.0 GHz Intel Xeon Platinum 8124M | AVX512 | 3498 |
+| 3.7 GHz AMD Ryzen 7 2700X | SHA Ext | 1979 |
+| 1.2 GHz ARM Cortex-A53 | ARM64 | 638 |
+
+## asm2plan9s
+
+In order to be able to work more easily with AVX512/AVX2 instructions, a separate tool was developed to convert SIMD instructions into the corresponding BYTE sequence as accepted by Go assembly. See [asm2plan9s](https://github.com/minio/asm2plan9s) for more information.
+
+## Why and benefits
+
+One of the most performance sensitive parts of the [Minio](https://github.com/minio/minio) object storage server is related to SHA256 hash sums calculations. For instance during multi part uploads each part that is uploaded needs to be verified for data integrity by the server.
+
+Other applications that can benefit from enhanced SHA256 performance are deduplication in storage systems, intrusion detection, version control systems, integrity checking, etc.
+
+## ARM SHA Extensions
+
+The 64-bit ARMv8 core has introduced new instructions for SHA1 and SHA2 acceleration as part of the [Cryptography Extensions](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0501f/CHDFJBCJ.html). Below you can see a small excerpt highlighting one of the rounds as is done for the SHA256 calculation process (for full code see [sha256block_arm64.s](https://github.com/minio/sha256-simd/blob/master/sha256block_arm64.s)).
+
+ ```
+ sha256h q2, q3, v9.4s
+ sha256h2 q3, q4, v9.4s
+ sha256su0 v5.4s, v6.4s
+ rev32 v8.16b, v8.16b
+ add v9.4s, v7.4s, v18.4s
+ mov v4.16b, v2.16b
+ sha256h q2, q3, v10.4s
+ sha256h2 q3, q4, v10.4s
+ sha256su0 v6.4s, v7.4s
+ sha256su1 v5.4s, v7.4s, v8.4s
+ ```
+
+### Detailed benchmarks
+
+Benchmarks generated on a 1.2 Ghz Quad-Core ARM Cortex A53 equipped [Pine64](https://www.pine64.com/).
+
+```
+minio@minio-arm:$ benchcmp golang.txt arm64.txt
+benchmark golang arm64 speedup
+BenchmarkHash8Bytes-4 0.68 MB/s 5.70 MB/s 8.38x
+BenchmarkHash1K-4 5.65 MB/s 326.30 MB/s 57.75x
+BenchmarkHash8K-4 6.00 MB/s 570.63 MB/s 95.11x
+BenchmarkHash1M-4 6.05 MB/s 638.23 MB/s 105.49x
+```
+
+## License
+
+Released under the Apache License v2.0. You can find the complete text in the file LICENSE.
+
+## Contributing
+
+Contributions are welcome, please send PRs for any enhancements.
diff --git a/vendor/github.com/minio/sha256-simd/cpuid_other.go b/vendor/github.com/minio/sha256-simd/cpuid_other.go
new file mode 100644
index 00000000..cd9fbf2d
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/cpuid_other.go
@@ -0,0 +1,46 @@
+// Minio Cloud Storage, (C) 2021 Minio, 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.
+//
+
+package sha256
+
+import (
+ "bytes"
+ "io/ioutil"
+ "runtime"
+
+ "github.com/klauspost/cpuid/v2"
+)
+
+func hasArmSha2() bool {
+ if cpuid.CPU.Has(cpuid.SHA2) {
+ return true
+ }
+ if runtime.GOARCH != "arm64" || runtime.GOOS != "linux" {
+ return false
+ }
+
+ // Fall back to hacky cpuinfo parsing...
+ const procCPUInfo = "/proc/cpuinfo"
+
+ // Feature to check for.
+ const sha256Feature = "sha2"
+
+ cpuInfo, err := ioutil.ReadFile(procCPUInfo)
+ if err != nil {
+ return false
+ }
+ return bytes.Contains(cpuInfo, []byte(sha256Feature))
+
+}
diff --git a/vendor/github.com/minio/sha256-simd/go.mod b/vendor/github.com/minio/sha256-simd/go.mod
new file mode 100644
index 00000000..89b70e04
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/go.mod
@@ -0,0 +1,5 @@
+module github.com/minio/sha256-simd
+
+go 1.13
+
+require github.com/klauspost/cpuid/v2 v2.0.4
diff --git a/vendor/github.com/minio/sha256-simd/go.sum b/vendor/github.com/minio/sha256-simd/go.sum
new file mode 100644
index 00000000..227bdb2e
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/go.sum
@@ -0,0 +1,4 @@
+github.com/klauspost/cpuid/v2 v2.0.3 h1:DNljyrHyxlkk8139OXIAAauCwV8eQGDD6Z8YqnDXdZw=
+github.com/klauspost/cpuid/v2 v2.0.3/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
diff --git a/vendor/github.com/minio/sha256-simd/sha256.go b/vendor/github.com/minio/sha256-simd/sha256.go
new file mode 100644
index 00000000..b137ead9
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256.go
@@ -0,0 +1,399 @@
+/*
+ * Minio Cloud Storage, (C) 2016 Minio, 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.
+ */
+
+package sha256
+
+import (
+ "crypto/sha256"
+ "encoding/binary"
+ "hash"
+ "runtime"
+
+ "github.com/klauspost/cpuid/v2"
+)
+
+// Size - The size of a SHA256 checksum in bytes.
+const Size = 32
+
+// BlockSize - The blocksize of SHA256 in bytes.
+const BlockSize = 64
+
+const (
+ chunk = BlockSize
+ init0 = 0x6A09E667
+ init1 = 0xBB67AE85
+ init2 = 0x3C6EF372
+ init3 = 0xA54FF53A
+ init4 = 0x510E527F
+ init5 = 0x9B05688C
+ init6 = 0x1F83D9AB
+ init7 = 0x5BE0CD19
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+ h [8]uint32
+ x [chunk]byte
+ nx int
+ len uint64
+}
+
+// Reset digest back to default
+func (d *digest) Reset() {
+ d.h[0] = init0
+ d.h[1] = init1
+ d.h[2] = init2
+ d.h[3] = init3
+ d.h[4] = init4
+ d.h[5] = init5
+ d.h[6] = init6
+ d.h[7] = init7
+ d.nx = 0
+ d.len = 0
+}
+
+type blockfuncType int
+
+const (
+ blockfuncGeneric blockfuncType = iota
+ blockfuncSha blockfuncType = iota
+ blockfuncArm blockfuncType = iota
+)
+
+var blockfunc blockfuncType
+
+func init() {
+ blockfunc = blockfuncGeneric
+ switch {
+ case hasSHAExtensions():
+ blockfunc = blockfuncSha
+ case hasArmSha2():
+ blockfunc = blockfuncArm
+ default:
+ blockfunc = blockfuncGeneric
+ }
+}
+
+var avx512 = cpuid.CPU.Supports(cpuid.AVX512F, cpuid.AVX512DQ, cpuid.AVX512BW, cpuid.AVX512VL)
+
+// hasSHAExtensions return whether the cpu supports SHA extensions.
+func hasSHAExtensions() bool {
+ return cpuid.CPU.Supports(cpuid.SHA, cpuid.SSSE3, cpuid.SSE4) && runtime.GOARCH == "amd64"
+}
+
+// New returns a new hash.Hash computing the SHA256 checksum.
+func New() hash.Hash {
+ if blockfunc != blockfuncGeneric {
+ d := new(digest)
+ d.Reset()
+ return d
+ }
+ // Fallback to the standard golang implementation
+ // if no features were found.
+ return sha256.New()
+}
+
+// Sum256 - single caller sha256 helper
+func Sum256(data []byte) (result [Size]byte) {
+ var d digest
+ d.Reset()
+ d.Write(data)
+ result = d.checkSum()
+ return
+}
+
+// Return size of checksum
+func (d *digest) Size() int { return Size }
+
+// Return blocksize of checksum
+func (d *digest) BlockSize() int { return BlockSize }
+
+// Write to digest
+func (d *digest) Write(p []byte) (nn int, err error) {
+ nn = len(p)
+ d.len += uint64(nn)
+ if d.nx > 0 {
+ n := copy(d.x[d.nx:], p)
+ d.nx += n
+ if d.nx == chunk {
+ block(d, d.x[:])
+ d.nx = 0
+ }
+ p = p[n:]
+ }
+ if len(p) >= chunk {
+ n := len(p) &^ (chunk - 1)
+ block(d, p[:n])
+ p = p[n:]
+ }
+ if len(p) > 0 {
+ d.nx = copy(d.x[:], p)
+ }
+ return
+}
+
+// Return sha256 sum in bytes
+func (d *digest) Sum(in []byte) []byte {
+ // Make a copy of d0 so that caller can keep writing and summing.
+ d0 := *d
+ hash := d0.checkSum()
+ return append(in, hash[:]...)
+}
+
+// Intermediate checksum function
+func (d *digest) checkSum() (digest [Size]byte) {
+ n := d.nx
+
+ var k [64]byte
+ copy(k[:], d.x[:n])
+
+ k[n] = 0x80
+
+ if n >= 56 {
+ block(d, k[:])
+
+ // clear block buffer - go compiles this to optimal 1x xorps + 4x movups
+ // unfortunately expressing this more succinctly results in much worse code
+ k[0] = 0
+ k[1] = 0
+ k[2] = 0
+ k[3] = 0
+ k[4] = 0
+ k[5] = 0
+ k[6] = 0
+ k[7] = 0
+ k[8] = 0
+ k[9] = 0
+ k[10] = 0
+ k[11] = 0
+ k[12] = 0
+ k[13] = 0
+ k[14] = 0
+ k[15] = 0
+ k[16] = 0
+ k[17] = 0
+ k[18] = 0
+ k[19] = 0
+ k[20] = 0
+ k[21] = 0
+ k[22] = 0
+ k[23] = 0
+ k[24] = 0
+ k[25] = 0
+ k[26] = 0
+ k[27] = 0
+ k[28] = 0
+ k[29] = 0
+ k[30] = 0
+ k[31] = 0
+ k[32] = 0
+ k[33] = 0
+ k[34] = 0
+ k[35] = 0
+ k[36] = 0
+ k[37] = 0
+ k[38] = 0
+ k[39] = 0
+ k[40] = 0
+ k[41] = 0
+ k[42] = 0
+ k[43] = 0
+ k[44] = 0
+ k[45] = 0
+ k[46] = 0
+ k[47] = 0
+ k[48] = 0
+ k[49] = 0
+ k[50] = 0
+ k[51] = 0
+ k[52] = 0
+ k[53] = 0
+ k[54] = 0
+ k[55] = 0
+ k[56] = 0
+ k[57] = 0
+ k[58] = 0
+ k[59] = 0
+ k[60] = 0
+ k[61] = 0
+ k[62] = 0
+ k[63] = 0
+ }
+ binary.BigEndian.PutUint64(k[56:64], uint64(d.len)<<3)
+ block(d, k[:])
+
+ {
+ const i = 0
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 1
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 2
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 3
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 4
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 5
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 6
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+ {
+ const i = 7
+ binary.BigEndian.PutUint32(digest[i*4:i*4+4], d.h[i])
+ }
+
+ return
+}
+
+func block(dig *digest, p []byte) {
+ if blockfunc == blockfuncSha {
+ blockShaGo(dig, p)
+ } else if blockfunc == blockfuncArm {
+ blockArmGo(dig, p)
+ } else if blockfunc == blockfuncGeneric {
+ blockGeneric(dig, p)
+ }
+}
+
+func blockGeneric(dig *digest, p []byte) {
+ var w [64]uint32
+ h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7]
+ for len(p) >= chunk {
+ // Can interlace the computation of w with the
+ // rounds below if needed for speed.
+ for i := 0; i < 16; i++ {
+ j := i * 4
+ w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3])
+ }
+ for i := 16; i < 64; i++ {
+ v1 := w[i-2]
+ t1 := (v1>>17 | v1<<(32-17)) ^ (v1>>19 | v1<<(32-19)) ^ (v1 >> 10)
+ v2 := w[i-15]
+ t2 := (v2>>7 | v2<<(32-7)) ^ (v2>>18 | v2<<(32-18)) ^ (v2 >> 3)
+ w[i] = t1 + w[i-7] + t2 + w[i-16]
+ }
+
+ a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7
+
+ for i := 0; i < 64; i++ {
+ t1 := h + ((e>>6 | e<<(32-6)) ^ (e>>11 | e<<(32-11)) ^ (e>>25 | e<<(32-25))) + ((e & f) ^ (^e & g)) + _K[i] + w[i]
+
+ t2 := ((a>>2 | a<<(32-2)) ^ (a>>13 | a<<(32-13)) ^ (a>>22 | a<<(32-22))) + ((a & b) ^ (a & c) ^ (b & c))
+
+ h = g
+ g = f
+ f = e
+ e = d + t1
+ d = c
+ c = b
+ b = a
+ a = t1 + t2
+ }
+
+ h0 += a
+ h1 += b
+ h2 += c
+ h3 += d
+ h4 += e
+ h5 += f
+ h6 += g
+ h7 += h
+
+ p = p[chunk:]
+ }
+
+ dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7
+}
+
+var _K = []uint32{
+ 0x428a2f98,
+ 0x71374491,
+ 0xb5c0fbcf,
+ 0xe9b5dba5,
+ 0x3956c25b,
+ 0x59f111f1,
+ 0x923f82a4,
+ 0xab1c5ed5,
+ 0xd807aa98,
+ 0x12835b01,
+ 0x243185be,
+ 0x550c7dc3,
+ 0x72be5d74,
+ 0x80deb1fe,
+ 0x9bdc06a7,
+ 0xc19bf174,
+ 0xe49b69c1,
+ 0xefbe4786,
+ 0x0fc19dc6,
+ 0x240ca1cc,
+ 0x2de92c6f,
+ 0x4a7484aa,
+ 0x5cb0a9dc,
+ 0x76f988da,
+ 0x983e5152,
+ 0xa831c66d,
+ 0xb00327c8,
+ 0xbf597fc7,
+ 0xc6e00bf3,
+ 0xd5a79147,
+ 0x06ca6351,
+ 0x14292967,
+ 0x27b70a85,
+ 0x2e1b2138,
+ 0x4d2c6dfc,
+ 0x53380d13,
+ 0x650a7354,
+ 0x766a0abb,
+ 0x81c2c92e,
+ 0x92722c85,
+ 0xa2bfe8a1,
+ 0xa81a664b,
+ 0xc24b8b70,
+ 0xc76c51a3,
+ 0xd192e819,
+ 0xd6990624,
+ 0xf40e3585,
+ 0x106aa070,
+ 0x19a4c116,
+ 0x1e376c08,
+ 0x2748774c,
+ 0x34b0bcb5,
+ 0x391c0cb3,
+ 0x4ed8aa4a,
+ 0x5b9cca4f,
+ 0x682e6ff3,
+ 0x748f82ee,
+ 0x78a5636f,
+ 0x84c87814,
+ 0x8cc70208,
+ 0x90befffa,
+ 0xa4506ceb,
+ 0xbef9a3f7,
+ 0xc67178f2,
+}
diff --git a/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.asm b/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.asm
new file mode 100644
index 00000000..c959b1aa
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.asm
@@ -0,0 +1,686 @@
+
+// 16x Parallel implementation of SHA256 for AVX512
+
+//
+// Minio Cloud Storage, (C) 2017 Minio, 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.
+
+//
+// This code is based on the Intel Multi-Buffer Crypto for IPSec library
+// and more specifically the following implementation:
+// https://github.com/intel/intel-ipsec-mb/blob/master/avx512/sha256_x16_avx512.asm
+//
+// For Golang it has been converted into Plan 9 assembly with the help of
+// github.com/minio/asm2plan9s to assemble the AVX512 instructions
+//
+
+// Copyright (c) 2017, Intel Corporation
+//
+// 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 Intel Corporation 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.
+
+#define SHA256_DIGEST_ROW_SIZE 64
+
+// arg1
+#define STATE rdi
+#define STATE_P9 DI
+// arg2
+#define INP_SIZE rsi
+#define INP_SIZE_P9 SI
+
+#define IDX rcx
+#define TBL rdx
+#define TBL_P9 DX
+
+#define INPUT rax
+#define INPUT_P9 AX
+
+#define inp0 r9
+#define SCRATCH_P9 R12
+#define SCRATCH r12
+#define maskp r13
+#define MASKP_P9 R13
+#define mask r14
+#define MASK_P9 R14
+
+#define A zmm0
+#define B zmm1
+#define C zmm2
+#define D zmm3
+#define E zmm4
+#define F zmm5
+#define G zmm6
+#define H zmm7
+#define T1 zmm8
+#define TMP0 zmm9
+#define TMP1 zmm10
+#define TMP2 zmm11
+#define TMP3 zmm12
+#define TMP4 zmm13
+#define TMP5 zmm14
+#define TMP6 zmm15
+
+#define W0 zmm16
+#define W1 zmm17
+#define W2 zmm18
+#define W3 zmm19
+#define W4 zmm20
+#define W5 zmm21
+#define W6 zmm22
+#define W7 zmm23
+#define W8 zmm24
+#define W9 zmm25
+#define W10 zmm26
+#define W11 zmm27
+#define W12 zmm28
+#define W13 zmm29
+#define W14 zmm30
+#define W15 zmm31
+
+
+#define TRANSPOSE16(_r0, _r1, _r2, _r3, _r4, _r5, _r6, _r7, _r8, _r9, _r10, _r11, _r12, _r13, _r14, _r15, _t0, _t1) \
+ \
+ \ // input r0 = {a15 a14 a13 a12 a11 a10 a9 a8 a7 a6 a5 a4 a3 a2 a1 a0}
+ \ // r1 = {b15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0}
+ \ // r2 = {c15 c14 c13 c12 c11 c10 c9 c8 c7 c6 c5 c4 c3 c2 c1 c0}
+ \ // r3 = {d15 d14 d13 d12 d11 d10 d9 d8 d7 d6 d5 d4 d3 d2 d1 d0}
+ \ // r4 = {e15 e14 e13 e12 e11 e10 e9 e8 e7 e6 e5 e4 e3 e2 e1 e0}
+ \ // r5 = {f15 f14 f13 f12 f11 f10 f9 f8 f7 f6 f5 f4 f3 f2 f1 f0}
+ \ // r6 = {g15 g14 g13 g12 g11 g10 g9 g8 g7 g6 g5 g4 g3 g2 g1 g0}
+ \ // r7 = {h15 h14 h13 h12 h11 h10 h9 h8 h7 h6 h5 h4 h3 h2 h1 h0}
+ \ // r8 = {i15 i14 i13 i12 i11 i10 i9 i8 i7 i6 i5 i4 i3 i2 i1 i0}
+ \ // r9 = {j15 j14 j13 j12 j11 j10 j9 j8 j7 j6 j5 j4 j3 j2 j1 j0}
+ \ // r10 = {k15 k14 k13 k12 k11 k10 k9 k8 k7 k6 k5 k4 k3 k2 k1 k0}
+ \ // r11 = {l15 l14 l13 l12 l11 l10 l9 l8 l7 l6 l5 l4 l3 l2 l1 l0}
+ \ // r12 = {m15 m14 m13 m12 m11 m10 m9 m8 m7 m6 m5 m4 m3 m2 m1 m0}
+ \ // r13 = {n15 n14 n13 n12 n11 n10 n9 n8 n7 n6 n5 n4 n3 n2 n1 n0}
+ \ // r14 = {o15 o14 o13 o12 o11 o10 o9 o8 o7 o6 o5 o4 o3 o2 o1 o0}
+ \ // r15 = {p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0}
+ \
+ \ // output r0 = { p0 o0 n0 m0 l0 k0 j0 i0 h0 g0 f0 e0 d0 c0 b0 a0}
+ \ // r1 = { p1 o1 n1 m1 l1 k1 j1 i1 h1 g1 f1 e1 d1 c1 b1 a1}
+ \ // r2 = { p2 o2 n2 m2 l2 k2 j2 i2 h2 g2 f2 e2 d2 c2 b2 a2}
+ \ // r3 = { p3 o3 n3 m3 l3 k3 j3 i3 h3 g3 f3 e3 d3 c3 b3 a3}
+ \ // r4 = { p4 o4 n4 m4 l4 k4 j4 i4 h4 g4 f4 e4 d4 c4 b4 a4}
+ \ // r5 = { p5 o5 n5 m5 l5 k5 j5 i5 h5 g5 f5 e5 d5 c5 b5 a5}
+ \ // r6 = { p6 o6 n6 m6 l6 k6 j6 i6 h6 g6 f6 e6 d6 c6 b6 a6}
+ \ // r7 = { p7 o7 n7 m7 l7 k7 j7 i7 h7 g7 f7 e7 d7 c7 b7 a7}
+ \ // r8 = { p8 o8 n8 m8 l8 k8 j8 i8 h8 g8 f8 e8 d8 c8 b8 a8}
+ \ // r9 = { p9 o9 n9 m9 l9 k9 j9 i9 h9 g9 f9 e9 d9 c9 b9 a9}
+ \ // r10 = {p10 o10 n10 m10 l10 k10 j10 i10 h10 g10 f10 e10 d10 c10 b10 a10}
+ \ // r11 = {p11 o11 n11 m11 l11 k11 j11 i11 h11 g11 f11 e11 d11 c11 b11 a11}
+ \ // r12 = {p12 o12 n12 m12 l12 k12 j12 i12 h12 g12 f12 e12 d12 c12 b12 a12}
+ \ // r13 = {p13 o13 n13 m13 l13 k13 j13 i13 h13 g13 f13 e13 d13 c13 b13 a13}
+ \ // r14 = {p14 o14 n14 m14 l14 k14 j14 i14 h14 g14 f14 e14 d14 c14 b14 a14}
+ \ // r15 = {p15 o15 n15 m15 l15 k15 j15 i15 h15 g15 f15 e15 d15 c15 b15 a15}
+ \
+ \ // process top half
+ vshufps _t0, _r0, _r1, 0x44 \ // t0 = {b13 b12 a13 a12 b9 b8 a9 a8 b5 b4 a5 a4 b1 b0 a1 a0}
+ vshufps _r0, _r0, _r1, 0xEE \ // r0 = {b15 b14 a15 a14 b11 b10 a11 a10 b7 b6 a7 a6 b3 b2 a3 a2}
+ vshufps _t1, _r2, _r3, 0x44 \ // t1 = {d13 d12 c13 c12 d9 d8 c9 c8 d5 d4 c5 c4 d1 d0 c1 c0}
+ vshufps _r2, _r2, _r3, 0xEE \ // r2 = {d15 d14 c15 c14 d11 d10 c11 c10 d7 d6 c7 c6 d3 d2 c3 c2}
+ \
+ vshufps _r3, _t0, _t1, 0xDD \ // r3 = {d13 c13 b13 a13 d9 c9 b9 a9 d5 c5 b5 a5 d1 c1 b1 a1}
+ vshufps _r1, _r0, _r2, 0x88 \ // r1 = {d14 c14 b14 a14 d10 c10 b10 a10 d6 c6 b6 a6 d2 c2 b2 a2}
+ vshufps _r0, _r0, _r2, 0xDD \ // r0 = {d15 c15 b15 a15 d11 c11 b11 a11 d7 c7 b7 a7 d3 c3 b3 a3}
+ vshufps _t0, _t0, _t1, 0x88 \ // t0 = {d12 c12 b12 a12 d8 c8 b8 a8 d4 c4 b4 a4 d0 c0 b0 a0}
+ \
+ \ // use r2 in place of t0
+ vshufps _r2, _r4, _r5, 0x44 \ // r2 = {f13 f12 e13 e12 f9 f8 e9 e8 f5 f4 e5 e4 f1 f0 e1 e0}
+ vshufps _r4, _r4, _r5, 0xEE \ // r4 = {f15 f14 e15 e14 f11 f10 e11 e10 f7 f6 e7 e6 f3 f2 e3 e2}
+ vshufps _t1, _r6, _r7, 0x44 \ // t1 = {h13 h12 g13 g12 h9 h8 g9 g8 h5 h4 g5 g4 h1 h0 g1 g0}
+ vshufps _r6, _r6, _r7, 0xEE \ // r6 = {h15 h14 g15 g14 h11 h10 g11 g10 h7 h6 g7 g6 h3 h2 g3 g2}
+ \
+ vshufps _r7, _r2, _t1, 0xDD \ // r7 = {h13 g13 f13 e13 h9 g9 f9 e9 h5 g5 f5 e5 h1 g1 f1 e1}
+ vshufps _r5, _r4, _r6, 0x88 \ // r5 = {h14 g14 f14 e14 h10 g10 f10 e10 h6 g6 f6 e6 h2 g2 f2 e2}
+ vshufps _r4, _r4, _r6, 0xDD \ // r4 = {h15 g15 f15 e15 h11 g11 f11 e11 h7 g7 f7 e7 h3 g3 f3 e3}
+ vshufps _r2, _r2, _t1, 0x88 \ // r2 = {h12 g12 f12 e12 h8 g8 f8 e8 h4 g4 f4 e4 h0 g0 f0 e0}
+ \
+ \ // use r6 in place of t0
+ vshufps _r6, _r8, _r9, 0x44 \ // r6 = {j13 j12 i13 i12 j9 j8 i9 i8 j5 j4 i5 i4 j1 j0 i1 i0}
+ vshufps _r8, _r8, _r9, 0xEE \ // r8 = {j15 j14 i15 i14 j11 j10 i11 i10 j7 j6 i7 i6 j3 j2 i3 i2}
+ vshufps _t1, _r10, _r11, 0x44 \ // t1 = {l13 l12 k13 k12 l9 l8 k9 k8 l5 l4 k5 k4 l1 l0 k1 k0}
+ vshufps _r10, _r10, _r11, 0xEE \ // r10 = {l15 l14 k15 k14 l11 l10 k11 k10 l7 l6 k7 k6 l3 l2 k3 k2}
+ \
+ vshufps _r11, _r6, _t1, 0xDD \ // r11 = {l13 k13 j13 113 l9 k9 j9 i9 l5 k5 j5 i5 l1 k1 j1 i1}
+ vshufps _r9, _r8, _r10, 0x88 \ // r9 = {l14 k14 j14 114 l10 k10 j10 i10 l6 k6 j6 i6 l2 k2 j2 i2}
+ vshufps _r8, _r8, _r10, 0xDD \ // r8 = {l15 k15 j15 115 l11 k11 j11 i11 l7 k7 j7 i7 l3 k3 j3 i3}
+ vshufps _r6, _r6, _t1, 0x88 \ // r6 = {l12 k12 j12 112 l8 k8 j8 i8 l4 k4 j4 i4 l0 k0 j0 i0}
+ \
+ \ // use r10 in place of t0
+ vshufps _r10, _r12, _r13, 0x44 \ // r10 = {n13 n12 m13 m12 n9 n8 m9 m8 n5 n4 m5 m4 n1 n0 a1 m0}
+ vshufps _r12, _r12, _r13, 0xEE \ // r12 = {n15 n14 m15 m14 n11 n10 m11 m10 n7 n6 m7 m6 n3 n2 a3 m2}
+ vshufps _t1, _r14, _r15, 0x44 \ // t1 = {p13 p12 013 012 p9 p8 09 08 p5 p4 05 04 p1 p0 01 00}
+ vshufps _r14, _r14, _r15, 0xEE \ // r14 = {p15 p14 015 014 p11 p10 011 010 p7 p6 07 06 p3 p2 03 02}
+ \
+ vshufps _r15, _r10, _t1, 0xDD \ // r15 = {p13 013 n13 m13 p9 09 n9 m9 p5 05 n5 m5 p1 01 n1 m1}
+ vshufps _r13, _r12, _r14, 0x88 \ // r13 = {p14 014 n14 m14 p10 010 n10 m10 p6 06 n6 m6 p2 02 n2 m2}
+ vshufps _r12, _r12, _r14, 0xDD \ // r12 = {p15 015 n15 m15 p11 011 n11 m11 p7 07 n7 m7 p3 03 n3 m3}
+ vshufps _r10, _r10, _t1, 0x88 \ // r10 = {p12 012 n12 m12 p8 08 n8 m8 p4 04 n4 m4 p0 00 n0 m0}
+ \
+ \ // At this point, the registers that contain interesting data are:
+ \ // t0, r3, r1, r0, r2, r7, r5, r4, r6, r11, r9, r8, r10, r15, r13, r12
+ \ // Can use t1 and r14 as scratch registers
+ LEAQ PSHUFFLE_TRANSPOSE16_MASK1<>(SB), BX \
+ LEAQ PSHUFFLE_TRANSPOSE16_MASK2<>(SB), R8 \
+ \
+ vmovdqu32 _r14, [rbx] \
+ vpermi2q _r14, _t0, _r2 \ // r14 = {h8 g8 f8 e8 d8 c8 b8 a8 h0 g0 f0 e0 d0 c0 b0 a0}
+ vmovdqu32 _t1, [r8] \
+ vpermi2q _t1, _t0, _r2 \ // t1 = {h12 g12 f12 e12 d12 c12 b12 a12 h4 g4 f4 e4 d4 c4 b4 a4}
+ \
+ vmovdqu32 _r2, [rbx] \
+ vpermi2q _r2, _r3, _r7 \ // r2 = {h9 g9 f9 e9 d9 c9 b9 a9 h1 g1 f1 e1 d1 c1 b1 a1}
+ vmovdqu32 _t0, [r8] \
+ vpermi2q _t0, _r3, _r7 \ // t0 = {h13 g13 f13 e13 d13 c13 b13 a13 h5 g5 f5 e5 d5 c5 b5 a5}
+ \
+ vmovdqu32 _r3, [rbx] \
+ vpermi2q _r3, _r1, _r5 \ // r3 = {h10 g10 f10 e10 d10 c10 b10 a10 h2 g2 f2 e2 d2 c2 b2 a2}
+ vmovdqu32 _r7, [r8] \
+ vpermi2q _r7, _r1, _r5 \ // r7 = {h14 g14 f14 e14 d14 c14 b14 a14 h6 g6 f6 e6 d6 c6 b6 a6}
+ \
+ vmovdqu32 _r1, [rbx] \
+ vpermi2q _r1, _r0, _r4 \ // r1 = {h11 g11 f11 e11 d11 c11 b11 a11 h3 g3 f3 e3 d3 c3 b3 a3}
+ vmovdqu32 _r5, [r8] \
+ vpermi2q _r5, _r0, _r4 \ // r5 = {h15 g15 f15 e15 d15 c15 b15 a15 h7 g7 f7 e7 d7 c7 b7 a7}
+ \
+ vmovdqu32 _r0, [rbx] \
+ vpermi2q _r0, _r6, _r10 \ // r0 = {p8 o8 n8 m8 l8 k8 j8 i8 p0 o0 n0 m0 l0 k0 j0 i0}
+ vmovdqu32 _r4, [r8] \
+ vpermi2q _r4, _r6, _r10 \ // r4 = {p12 o12 n12 m12 l12 k12 j12 i12 p4 o4 n4 m4 l4 k4 j4 i4}
+ \
+ vmovdqu32 _r6, [rbx] \
+ vpermi2q _r6, _r11, _r15 \ // r6 = {p9 o9 n9 m9 l9 k9 j9 i9 p1 o1 n1 m1 l1 k1 j1 i1}
+ vmovdqu32 _r10, [r8] \
+ vpermi2q _r10, _r11, _r15 \ // r10 = {p13 o13 n13 m13 l13 k13 j13 i13 p5 o5 n5 m5 l5 k5 j5 i5}
+ \
+ vmovdqu32 _r11, [rbx] \
+ vpermi2q _r11, _r9, _r13 \ // r11 = {p10 o10 n10 m10 l10 k10 j10 i10 p2 o2 n2 m2 l2 k2 j2 i2}
+ vmovdqu32 _r15, [r8] \
+ vpermi2q _r15, _r9, _r13 \ // r15 = {p14 o14 n14 m14 l14 k14 j14 i14 p6 o6 n6 m6 l6 k6 j6 i6}
+ \
+ vmovdqu32 _r9, [rbx] \
+ vpermi2q _r9, _r8, _r12 \ // r9 = {p11 o11 n11 m11 l11 k11 j11 i11 p3 o3 n3 m3 l3 k3 j3 i3}
+ vmovdqu32 _r13, [r8] \
+ vpermi2q _r13, _r8, _r12 \ // r13 = {p15 o15 n15 m15 l15 k15 j15 i15 p7 o7 n7 m7 l7 k7 j7 i7}
+ \
+ \ // At this point r8 and r12 can be used as scratch registers
+ vshuff64x2 _r8, _r14, _r0, 0xEE \ // r8 = {p8 o8 n8 m8 l8 k8 j8 i8 h8 g8 f8 e8 d8 c8 b8 a8}
+ vshuff64x2 _r0, _r14, _r0, 0x44 \ // r0 = {p0 o0 n0 m0 l0 k0 j0 i0 h0 g0 f0 e0 d0 c0 b0 a0}
+ \
+ vshuff64x2 _r12, _t1, _r4, 0xEE \ // r12 = {p12 o12 n12 m12 l12 k12 j12 i12 h12 g12 f12 e12 d12 c12 b12 a12}
+ vshuff64x2 _r4, _t1, _r4, 0x44 \ // r4 = {p4 o4 n4 m4 l4 k4 j4 i4 h4 g4 f4 e4 d4 c4 b4 a4}
+ \
+ vshuff64x2 _r14, _r7, _r15, 0xEE \ // r14 = {p14 o14 n14 m14 l14 k14 j14 i14 h14 g14 f14 e14 d14 c14 b14 a14}
+ vshuff64x2 _t1, _r7, _r15, 0x44 \ // t1 = {p6 o6 n6 m6 l6 k6 j6 i6 h6 g6 f6 e6 d6 c6 b6 a6}
+ \
+ vshuff64x2 _r15, _r5, _r13, 0xEE \ // r15 = {p15 o15 n15 m15 l15 k15 j15 i15 h15 g15 f15 e15 d15 c15 b15 a15}
+ vshuff64x2 _r7, _r5, _r13, 0x44 \ // r7 = {p7 o7 n7 m7 l7 k7 j7 i7 h7 g7 f7 e7 d7 c7 b7 a7}
+ \
+ vshuff64x2 _r13, _t0, _r10, 0xEE \ // r13 = {p13 o13 n13 m13 l13 k13 j13 i13 h13 g13 f13 e13 d13 c13 b13 a13}
+ vshuff64x2 _r5, _t0, _r10, 0x44 \ // r5 = {p5 o5 n5 m5 l5 k5 j5 i5 h5 g5 f5 e5 d5 c5 b5 a5}
+ \
+ vshuff64x2 _r10, _r3, _r11, 0xEE \ // r10 = {p10 o10 n10 m10 l10 k10 j10 i10 h10 g10 f10 e10 d10 c10 b10 a10}
+ vshuff64x2 _t0, _r3, _r11, 0x44 \ // t0 = {p2 o2 n2 m2 l2 k2 j2 i2 h2 g2 f2 e2 d2 c2 b2 a2}
+ \
+ vshuff64x2 _r11, _r1, _r9, 0xEE \ // r11 = {p11 o11 n11 m11 l11 k11 j11 i11 h11 g11 f11 e11 d11 c11 b11 a11}
+ vshuff64x2 _r3, _r1, _r9, 0x44 \ // r3 = {p3 o3 n3 m3 l3 k3 j3 i3 h3 g3 f3 e3 d3 c3 b3 a3}
+ \
+ vshuff64x2 _r9, _r2, _r6, 0xEE \ // r9 = {p9 o9 n9 m9 l9 k9 j9 i9 h9 g9 f9 e9 d9 c9 b9 a9}
+ vshuff64x2 _r1, _r2, _r6, 0x44 \ // r1 = {p1 o1 n1 m1 l1 k1 j1 i1 h1 g1 f1 e1 d1 c1 b1 a1}
+ \
+ vmovdqu32 _r2, _t0 \ // r2 = {p2 o2 n2 m2 l2 k2 j2 i2 h2 g2 f2 e2 d2 c2 b2 a2}
+ vmovdqu32 _r6, _t1 \ // r6 = {p6 o6 n6 m6 l6 k6 j6 i6 h6 g6 f6 e6 d6 c6 b6 a6}
+
+
+// CH(A, B, C) = (A&B) ^ (~A&C)
+// MAJ(E, F, G) = (E&F) ^ (E&G) ^ (F&G)
+// SIGMA0 = ROR_2 ^ ROR_13 ^ ROR_22
+// SIGMA1 = ROR_6 ^ ROR_11 ^ ROR_25
+// sigma0 = ROR_7 ^ ROR_18 ^ SHR_3
+// sigma1 = ROR_17 ^ ROR_19 ^ SHR_10
+
+// Main processing loop per round
+#define PROCESS_LOOP(_WT, _ROUND, _A, _B, _C, _D, _E, _F, _G, _H) \
+ \ // T1 = H + SIGMA1(E) + CH(E, F, G) + Kt + Wt
+ \ // T2 = SIGMA0(A) + MAJ(A, B, C)
+ \ // H=G, G=F, F=E, E=D+T1, D=C, C=B, B=A, A=T1+T2
+ \
+ \ // H becomes T2, then add T1 for A
+ \ // D becomes D + T1 for E
+ \
+ vpaddd T1, _H, TMP3 \ // T1 = H + Kt
+ vmovdqu32 TMP0, _E \
+ vprord TMP1, _E, 6 \ // ROR_6(E)
+ vprord TMP2, _E, 11 \ // ROR_11(E)
+ vprord TMP3, _E, 25 \ // ROR_25(E)
+ vpternlogd TMP0, _F, _G, 0xCA \ // TMP0 = CH(E,F,G)
+ vpaddd T1, T1, _WT \ // T1 = T1 + Wt
+ vpternlogd TMP1, TMP2, TMP3, 0x96 \ // TMP1 = SIGMA1(E)
+ vpaddd T1, T1, TMP0 \ // T1 = T1 + CH(E,F,G)
+ vpaddd T1, T1, TMP1 \ // T1 = T1 + SIGMA1(E)
+ vpaddd _D, _D, T1 \ // D = D + T1
+ \
+ vprord _H, _A, 2 \ // ROR_2(A)
+ vprord TMP2, _A, 13 \ // ROR_13(A)
+ vprord TMP3, _A, 22 \ // ROR_22(A)
+ vmovdqu32 TMP0, _A \
+ vpternlogd TMP0, _B, _C, 0xE8 \ // TMP0 = MAJ(A,B,C)
+ vpternlogd _H, TMP2, TMP3, 0x96 \ // H(T2) = SIGMA0(A)
+ vpaddd _H, _H, TMP0 \ // H(T2) = SIGMA0(A) + MAJ(A,B,C)
+ vpaddd _H, _H, T1 \ // H(A) = H(T2) + T1
+ \
+ vmovdqu32 TMP3, [TBL + ((_ROUND+1)*64)] \ // Next Kt
+
+
+#define MSG_SCHED_ROUND_16_63(_WT, _WTp1, _WTp9, _WTp14) \
+ vprord TMP4, _WTp14, 17 \ // ROR_17(Wt-2)
+ vprord TMP5, _WTp14, 19 \ // ROR_19(Wt-2)
+ vpsrld TMP6, _WTp14, 10 \ // SHR_10(Wt-2)
+ vpternlogd TMP4, TMP5, TMP6, 0x96 \ // TMP4 = sigma1(Wt-2)
+ \
+ vpaddd _WT, _WT, TMP4 \ // Wt = Wt-16 + sigma1(Wt-2)
+ vpaddd _WT, _WT, _WTp9 \ // Wt = Wt-16 + sigma1(Wt-2) + Wt-7
+ \
+ vprord TMP4, _WTp1, 7 \ // ROR_7(Wt-15)
+ vprord TMP5, _WTp1, 18 \ // ROR_18(Wt-15)
+ vpsrld TMP6, _WTp1, 3 \ // SHR_3(Wt-15)
+ vpternlogd TMP4, TMP5, TMP6, 0x96 \ // TMP4 = sigma0(Wt-15)
+ \
+ vpaddd _WT, _WT, TMP4 \ // Wt = Wt-16 + sigma1(Wt-2) +
+ \ // Wt-7 + sigma0(Wt-15) +
+
+
+// Note this is reading in a block of data for one lane
+// When all 16 are read, the data must be transposed to build msg schedule
+#define MSG_SCHED_ROUND_00_15(_WT, OFFSET, LABEL) \
+ TESTQ $(1<<OFFSET), MASK_P9 \
+ JE LABEL \
+ MOVQ OFFSET*24(INPUT_P9), R9 \
+ vmovups _WT, [inp0+IDX] \
+LABEL: \
+
+#define MASKED_LOAD(_WT, OFFSET, LABEL) \
+ TESTQ $(1<<OFFSET), MASK_P9 \
+ JE LABEL \
+ MOVQ OFFSET*24(INPUT_P9), R9 \
+ vmovups _WT,[inp0+IDX] \
+LABEL: \
+
+TEXT ยทsha256_x16_avx512(SB), 7, $0
+ MOVQ digests+0(FP), STATE_P9 //
+ MOVQ scratch+8(FP), SCRATCH_P9
+ MOVQ mask_len+32(FP), INP_SIZE_P9 // number of blocks to process
+ MOVQ mask+24(FP), MASKP_P9
+ MOVQ (MASKP_P9), MASK_P9
+ kmovq k1, mask
+ LEAQ inputs+48(FP), INPUT_P9
+
+ // Initialize digests
+ vmovdqu32 A, [STATE + 0*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 B, [STATE + 1*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 C, [STATE + 2*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 D, [STATE + 3*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 E, [STATE + 4*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 F, [STATE + 5*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 G, [STATE + 6*SHA256_DIGEST_ROW_SIZE]
+ vmovdqu32 H, [STATE + 7*SHA256_DIGEST_ROW_SIZE]
+
+ MOVQ table+16(FP), TBL_P9
+
+ xor IDX, IDX
+
+ // Read in first block of input data
+ MASKED_LOAD( W0, 0, skipInput0)
+ MASKED_LOAD( W1, 1, skipInput1)
+ MASKED_LOAD( W2, 2, skipInput2)
+ MASKED_LOAD( W3, 3, skipInput3)
+ MASKED_LOAD( W4, 4, skipInput4)
+ MASKED_LOAD( W5, 5, skipInput5)
+ MASKED_LOAD( W6, 6, skipInput6)
+ MASKED_LOAD( W7, 7, skipInput7)
+ MASKED_LOAD( W8, 8, skipInput8)
+ MASKED_LOAD( W9, 9, skipInput9)
+ MASKED_LOAD(W10, 10, skipInput10)
+ MASKED_LOAD(W11, 11, skipInput11)
+ MASKED_LOAD(W12, 12, skipInput12)
+ MASKED_LOAD(W13, 13, skipInput13)
+ MASKED_LOAD(W14, 14, skipInput14)
+ MASKED_LOAD(W15, 15, skipInput15)
+
+lloop:
+ LEAQ PSHUFFLE_BYTE_FLIP_MASK<>(SB), TBL_P9
+ vmovdqu32 TMP2, [TBL]
+
+ // Get first K from table
+ MOVQ table+16(FP), TBL_P9
+ vmovdqu32 TMP3, [TBL]
+
+ // Save digests for later addition
+ vmovdqu32 [SCRATCH + 64*0], A
+ vmovdqu32 [SCRATCH + 64*1], B
+ vmovdqu32 [SCRATCH + 64*2], C
+ vmovdqu32 [SCRATCH + 64*3], D
+ vmovdqu32 [SCRATCH + 64*4], E
+ vmovdqu32 [SCRATCH + 64*5], F
+ vmovdqu32 [SCRATCH + 64*6], G
+ vmovdqu32 [SCRATCH + 64*7], H
+
+ add IDX, 64
+
+ // Transpose input data
+ TRANSPOSE16(W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, TMP0, TMP1)
+
+ vpshufb W0, W0, TMP2
+ vpshufb W1, W1, TMP2
+ vpshufb W2, W2, TMP2
+ vpshufb W3, W3, TMP2
+ vpshufb W4, W4, TMP2
+ vpshufb W5, W5, TMP2
+ vpshufb W6, W6, TMP2
+ vpshufb W7, W7, TMP2
+ vpshufb W8, W8, TMP2
+ vpshufb W9, W9, TMP2
+ vpshufb W10, W10, TMP2
+ vpshufb W11, W11, TMP2
+ vpshufb W12, W12, TMP2
+ vpshufb W13, W13, TMP2
+ vpshufb W14, W14, TMP2
+ vpshufb W15, W15, TMP2
+
+ // MSG Schedule for W0-W15 is now complete in registers
+ // Process first 48 rounds
+ // Calculate next Wt+16 after processing is complete and Wt is unneeded
+
+ PROCESS_LOOP( W0, 0, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_16_63( W0, W1, W9, W14)
+ PROCESS_LOOP( W1, 1, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_16_63( W1, W2, W10, W15)
+ PROCESS_LOOP( W2, 2, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_16_63( W2, W3, W11, W0)
+ PROCESS_LOOP( W3, 3, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_16_63( W3, W4, W12, W1)
+ PROCESS_LOOP( W4, 4, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_16_63( W4, W5, W13, W2)
+ PROCESS_LOOP( W5, 5, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_16_63( W5, W6, W14, W3)
+ PROCESS_LOOP( W6, 6, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_16_63( W6, W7, W15, W4)
+ PROCESS_LOOP( W7, 7, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_16_63( W7, W8, W0, W5)
+ PROCESS_LOOP( W8, 8, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_16_63( W8, W9, W1, W6)
+ PROCESS_LOOP( W9, 9, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_16_63( W9, W10, W2, W7)
+ PROCESS_LOOP(W10, 10, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_16_63(W10, W11, W3, W8)
+ PROCESS_LOOP(W11, 11, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_16_63(W11, W12, W4, W9)
+ PROCESS_LOOP(W12, 12, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_16_63(W12, W13, W5, W10)
+ PROCESS_LOOP(W13, 13, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_16_63(W13, W14, W6, W11)
+ PROCESS_LOOP(W14, 14, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_16_63(W14, W15, W7, W12)
+ PROCESS_LOOP(W15, 15, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_16_63(W15, W0, W8, W13)
+ PROCESS_LOOP( W0, 16, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_16_63( W0, W1, W9, W14)
+ PROCESS_LOOP( W1, 17, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_16_63( W1, W2, W10, W15)
+ PROCESS_LOOP( W2, 18, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_16_63( W2, W3, W11, W0)
+ PROCESS_LOOP( W3, 19, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_16_63( W3, W4, W12, W1)
+ PROCESS_LOOP( W4, 20, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_16_63( W4, W5, W13, W2)
+ PROCESS_LOOP( W5, 21, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_16_63( W5, W6, W14, W3)
+ PROCESS_LOOP( W6, 22, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_16_63( W6, W7, W15, W4)
+ PROCESS_LOOP( W7, 23, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_16_63( W7, W8, W0, W5)
+ PROCESS_LOOP( W8, 24, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_16_63( W8, W9, W1, W6)
+ PROCESS_LOOP( W9, 25, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_16_63( W9, W10, W2, W7)
+ PROCESS_LOOP(W10, 26, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_16_63(W10, W11, W3, W8)
+ PROCESS_LOOP(W11, 27, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_16_63(W11, W12, W4, W9)
+ PROCESS_LOOP(W12, 28, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_16_63(W12, W13, W5, W10)
+ PROCESS_LOOP(W13, 29, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_16_63(W13, W14, W6, W11)
+ PROCESS_LOOP(W14, 30, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_16_63(W14, W15, W7, W12)
+ PROCESS_LOOP(W15, 31, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_16_63(W15, W0, W8, W13)
+ PROCESS_LOOP( W0, 32, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_16_63( W0, W1, W9, W14)
+ PROCESS_LOOP( W1, 33, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_16_63( W1, W2, W10, W15)
+ PROCESS_LOOP( W2, 34, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_16_63( W2, W3, W11, W0)
+ PROCESS_LOOP( W3, 35, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_16_63( W3, W4, W12, W1)
+ PROCESS_LOOP( W4, 36, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_16_63( W4, W5, W13, W2)
+ PROCESS_LOOP( W5, 37, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_16_63( W5, W6, W14, W3)
+ PROCESS_LOOP( W6, 38, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_16_63( W6, W7, W15, W4)
+ PROCESS_LOOP( W7, 39, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_16_63( W7, W8, W0, W5)
+ PROCESS_LOOP( W8, 40, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_16_63( W8, W9, W1, W6)
+ PROCESS_LOOP( W9, 41, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_16_63( W9, W10, W2, W7)
+ PROCESS_LOOP(W10, 42, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_16_63(W10, W11, W3, W8)
+ PROCESS_LOOP(W11, 43, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_16_63(W11, W12, W4, W9)
+ PROCESS_LOOP(W12, 44, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_16_63(W12, W13, W5, W10)
+ PROCESS_LOOP(W13, 45, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_16_63(W13, W14, W6, W11)
+ PROCESS_LOOP(W14, 46, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_16_63(W14, W15, W7, W12)
+ PROCESS_LOOP(W15, 47, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_16_63(W15, W0, W8, W13)
+
+ // Check if this is the last block
+ sub INP_SIZE, 1
+ JE lastLoop
+
+ // Load next mask for inputs
+ ADDQ $8, MASKP_P9
+ MOVQ (MASKP_P9), MASK_P9
+
+ // Process last 16 rounds
+ // Read in next block msg data for use in first 16 words of msg sched
+
+ PROCESS_LOOP( W0, 48, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_00_15( W0, 0, skipNext0)
+ PROCESS_LOOP( W1, 49, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_00_15( W1, 1, skipNext1)
+ PROCESS_LOOP( W2, 50, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_00_15( W2, 2, skipNext2)
+ PROCESS_LOOP( W3, 51, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_00_15( W3, 3, skipNext3)
+ PROCESS_LOOP( W4, 52, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_00_15( W4, 4, skipNext4)
+ PROCESS_LOOP( W5, 53, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_00_15( W5, 5, skipNext5)
+ PROCESS_LOOP( W6, 54, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_00_15( W6, 6, skipNext6)
+ PROCESS_LOOP( W7, 55, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_00_15( W7, 7, skipNext7)
+ PROCESS_LOOP( W8, 56, A, B, C, D, E, F, G, H)
+ MSG_SCHED_ROUND_00_15( W8, 8, skipNext8)
+ PROCESS_LOOP( W9, 57, H, A, B, C, D, E, F, G)
+ MSG_SCHED_ROUND_00_15( W9, 9, skipNext9)
+ PROCESS_LOOP(W10, 58, G, H, A, B, C, D, E, F)
+ MSG_SCHED_ROUND_00_15(W10, 10, skipNext10)
+ PROCESS_LOOP(W11, 59, F, G, H, A, B, C, D, E)
+ MSG_SCHED_ROUND_00_15(W11, 11, skipNext11)
+ PROCESS_LOOP(W12, 60, E, F, G, H, A, B, C, D)
+ MSG_SCHED_ROUND_00_15(W12, 12, skipNext12)
+ PROCESS_LOOP(W13, 61, D, E, F, G, H, A, B, C)
+ MSG_SCHED_ROUND_00_15(W13, 13, skipNext13)
+ PROCESS_LOOP(W14, 62, C, D, E, F, G, H, A, B)
+ MSG_SCHED_ROUND_00_15(W14, 14, skipNext14)
+ PROCESS_LOOP(W15, 63, B, C, D, E, F, G, H, A)
+ MSG_SCHED_ROUND_00_15(W15, 15, skipNext15)
+
+ // Add old digest
+ vmovdqu32 TMP2, A
+ vmovdqu32 A, [SCRATCH + 64*0]
+ vpaddd A{k1}, A, TMP2
+ vmovdqu32 TMP2, B
+ vmovdqu32 B, [SCRATCH + 64*1]
+ vpaddd B{k1}, B, TMP2
+ vmovdqu32 TMP2, C
+ vmovdqu32 C, [SCRATCH + 64*2]
+ vpaddd C{k1}, C, TMP2
+ vmovdqu32 TMP2, D
+ vmovdqu32 D, [SCRATCH + 64*3]
+ vpaddd D{k1}, D, TMP2
+ vmovdqu32 TMP2, E
+ vmovdqu32 E, [SCRATCH + 64*4]
+ vpaddd E{k1}, E, TMP2
+ vmovdqu32 TMP2, F
+ vmovdqu32 F, [SCRATCH + 64*5]
+ vpaddd F{k1}, F, TMP2
+ vmovdqu32 TMP2, G
+ vmovdqu32 G, [SCRATCH + 64*6]
+ vpaddd G{k1}, G, TMP2
+ vmovdqu32 TMP2, H
+ vmovdqu32 H, [SCRATCH + 64*7]
+ vpaddd H{k1}, H, TMP2
+
+ kmovq k1, mask
+ JMP lloop
+
+lastLoop:
+ // Process last 16 rounds
+ PROCESS_LOOP( W0, 48, A, B, C, D, E, F, G, H)
+ PROCESS_LOOP( W1, 49, H, A, B, C, D, E, F, G)
+ PROCESS_LOOP( W2, 50, G, H, A, B, C, D, E, F)
+ PROCESS_LOOP( W3, 51, F, G, H, A, B, C, D, E)
+ PROCESS_LOOP( W4, 52, E, F, G, H, A, B, C, D)
+ PROCESS_LOOP( W5, 53, D, E, F, G, H, A, B, C)
+ PROCESS_LOOP( W6, 54, C, D, E, F, G, H, A, B)
+ PROCESS_LOOP( W7, 55, B, C, D, E, F, G, H, A)
+ PROCESS_LOOP( W8, 56, A, B, C, D, E, F, G, H)
+ PROCESS_LOOP( W9, 57, H, A, B, C, D, E, F, G)
+ PROCESS_LOOP(W10, 58, G, H, A, B, C, D, E, F)
+ PROCESS_LOOP(W11, 59, F, G, H, A, B, C, D, E)
+ PROCESS_LOOP(W12, 60, E, F, G, H, A, B, C, D)
+ PROCESS_LOOP(W13, 61, D, E, F, G, H, A, B, C)
+ PROCESS_LOOP(W14, 62, C, D, E, F, G, H, A, B)
+ PROCESS_LOOP(W15, 63, B, C, D, E, F, G, H, A)
+
+ // Add old digest
+ vmovdqu32 TMP2, A
+ vmovdqu32 A, [SCRATCH + 64*0]
+ vpaddd A{k1}, A, TMP2
+ vmovdqu32 TMP2, B
+ vmovdqu32 B, [SCRATCH + 64*1]
+ vpaddd B{k1}, B, TMP2
+ vmovdqu32 TMP2, C
+ vmovdqu32 C, [SCRATCH + 64*2]
+ vpaddd C{k1}, C, TMP2
+ vmovdqu32 TMP2, D
+ vmovdqu32 D, [SCRATCH + 64*3]
+ vpaddd D{k1}, D, TMP2
+ vmovdqu32 TMP2, E
+ vmovdqu32 E, [SCRATCH + 64*4]
+ vpaddd E{k1}, E, TMP2
+ vmovdqu32 TMP2, F
+ vmovdqu32 F, [SCRATCH + 64*5]
+ vpaddd F{k1}, F, TMP2
+ vmovdqu32 TMP2, G
+ vmovdqu32 G, [SCRATCH + 64*6]
+ vpaddd G{k1}, G, TMP2
+ vmovdqu32 TMP2, H
+ vmovdqu32 H, [SCRATCH + 64*7]
+ vpaddd H{k1}, H, TMP2
+
+ // Write out digest
+ vmovdqu32 [STATE + 0*SHA256_DIGEST_ROW_SIZE], A
+ vmovdqu32 [STATE + 1*SHA256_DIGEST_ROW_SIZE], B
+ vmovdqu32 [STATE + 2*SHA256_DIGEST_ROW_SIZE], C
+ vmovdqu32 [STATE + 3*SHA256_DIGEST_ROW_SIZE], D
+ vmovdqu32 [STATE + 4*SHA256_DIGEST_ROW_SIZE], E
+ vmovdqu32 [STATE + 5*SHA256_DIGEST_ROW_SIZE], F
+ vmovdqu32 [STATE + 6*SHA256_DIGEST_ROW_SIZE], G
+ vmovdqu32 [STATE + 7*SHA256_DIGEST_ROW_SIZE], H
+
+ VZEROUPPER
+ RET
+
+//
+// Tables
+//
+
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x000(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x008(SB)/8, $0x0c0d0e0f08090a0b
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x010(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x018(SB)/8, $0x0c0d0e0f08090a0b
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x020(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x028(SB)/8, $0x0c0d0e0f08090a0b
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x030(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x038(SB)/8, $0x0c0d0e0f08090a0b
+GLOBL PSHUFFLE_BYTE_FLIP_MASK<>(SB), 8, $64
+
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x000(SB)/8, $0x0000000000000000
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x008(SB)/8, $0x0000000000000001
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x010(SB)/8, $0x0000000000000008
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x018(SB)/8, $0x0000000000000009
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x020(SB)/8, $0x0000000000000004
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x028(SB)/8, $0x0000000000000005
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x030(SB)/8, $0x000000000000000C
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x038(SB)/8, $0x000000000000000D
+GLOBL PSHUFFLE_TRANSPOSE16_MASK1<>(SB), 8, $64
+
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x000(SB)/8, $0x0000000000000002
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x008(SB)/8, $0x0000000000000003
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x010(SB)/8, $0x000000000000000A
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x018(SB)/8, $0x000000000000000B
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x020(SB)/8, $0x0000000000000006
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x028(SB)/8, $0x0000000000000007
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x030(SB)/8, $0x000000000000000E
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x038(SB)/8, $0x000000000000000F
+GLOBL PSHUFFLE_TRANSPOSE16_MASK2<>(SB), 8, $64
diff --git a/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.go b/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.go
new file mode 100644
index 00000000..b7d7c163
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.go
@@ -0,0 +1,500 @@
+//+build !noasm,!appengine,gc
+
+/*
+ * Minio Cloud Storage, (C) 2017 Minio, 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.
+ */
+
+package sha256
+
+import (
+ "encoding/binary"
+ "errors"
+ "hash"
+ "sort"
+ "sync/atomic"
+ "time"
+)
+
+//go:noescape
+func sha256X16Avx512(digests *[512]byte, scratch *[512]byte, table *[512]uint64, mask []uint64, inputs [16][]byte)
+
+// Avx512ServerUID - Do not start at 0 but next multiple of 16 so as to be able to
+// differentiate with default initialiation value of 0
+const Avx512ServerUID = 16
+
+var uidCounter uint64
+
+// NewAvx512 - initialize sha256 Avx512 implementation.
+func NewAvx512(a512srv *Avx512Server) hash.Hash {
+ uid := atomic.AddUint64(&uidCounter, 1)
+ return &Avx512Digest{uid: uid, a512srv: a512srv}
+}
+
+// Avx512Digest - Type for computing SHA256 using Avx512
+type Avx512Digest struct {
+ uid uint64
+ a512srv *Avx512Server
+ x [chunk]byte
+ nx int
+ len uint64
+ final bool
+ result [Size]byte
+}
+
+// Size - Return size of checksum
+func (d *Avx512Digest) Size() int { return Size }
+
+// BlockSize - Return blocksize of checksum
+func (d Avx512Digest) BlockSize() int { return BlockSize }
+
+// Reset - reset sha digest to its initial values
+func (d *Avx512Digest) Reset() {
+ d.a512srv.blocksCh <- blockInput{uid: d.uid, reset: true}
+ d.nx = 0
+ d.len = 0
+ d.final = false
+}
+
+// Write to digest
+func (d *Avx512Digest) Write(p []byte) (nn int, err error) {
+
+ if d.final {
+ return 0, errors.New("Avx512Digest already finalized. Reset first before writing again")
+ }
+
+ nn = len(p)
+ d.len += uint64(nn)
+ if d.nx > 0 {
+ n := copy(d.x[d.nx:], p)
+ d.nx += n
+ if d.nx == chunk {
+ d.a512srv.blocksCh <- blockInput{uid: d.uid, msg: d.x[:]}
+ d.nx = 0
+ }
+ p = p[n:]
+ }
+ if len(p) >= chunk {
+ n := len(p) &^ (chunk - 1)
+ d.a512srv.blocksCh <- blockInput{uid: d.uid, msg: p[:n]}
+ p = p[n:]
+ }
+ if len(p) > 0 {
+ d.nx = copy(d.x[:], p)
+ }
+ return
+}
+
+// Sum - Return sha256 sum in bytes
+func (d *Avx512Digest) Sum(in []byte) (result []byte) {
+
+ if d.final {
+ return append(in, d.result[:]...)
+ }
+
+ trail := make([]byte, 0, 128)
+ trail = append(trail, d.x[:d.nx]...)
+
+ len := d.len
+ // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
+ var tmp [64]byte
+ tmp[0] = 0x80
+ if len%64 < 56 {
+ trail = append(trail, tmp[0:56-len%64]...)
+ } else {
+ trail = append(trail, tmp[0:64+56-len%64]...)
+ }
+ d.nx = 0
+
+ // Length in bits.
+ len <<= 3
+ for i := uint(0); i < 8; i++ {
+ tmp[i] = byte(len >> (56 - 8*i))
+ }
+ trail = append(trail, tmp[0:8]...)
+
+ sumCh := make(chan [Size]byte)
+ d.a512srv.blocksCh <- blockInput{uid: d.uid, msg: trail, final: true, sumCh: sumCh}
+ d.result = <-sumCh
+ d.final = true
+ return append(in, d.result[:]...)
+}
+
+var table = [512]uint64{
+ 0x428a2f98428a2f98, 0x428a2f98428a2f98, 0x428a2f98428a2f98, 0x428a2f98428a2f98,
+ 0x428a2f98428a2f98, 0x428a2f98428a2f98, 0x428a2f98428a2f98, 0x428a2f98428a2f98,
+ 0x7137449171374491, 0x7137449171374491, 0x7137449171374491, 0x7137449171374491,
+ 0x7137449171374491, 0x7137449171374491, 0x7137449171374491, 0x7137449171374491,
+ 0xb5c0fbcfb5c0fbcf, 0xb5c0fbcfb5c0fbcf, 0xb5c0fbcfb5c0fbcf, 0xb5c0fbcfb5c0fbcf,
+ 0xb5c0fbcfb5c0fbcf, 0xb5c0fbcfb5c0fbcf, 0xb5c0fbcfb5c0fbcf, 0xb5c0fbcfb5c0fbcf,
+ 0xe9b5dba5e9b5dba5, 0xe9b5dba5e9b5dba5, 0xe9b5dba5e9b5dba5, 0xe9b5dba5e9b5dba5,
+ 0xe9b5dba5e9b5dba5, 0xe9b5dba5e9b5dba5, 0xe9b5dba5e9b5dba5, 0xe9b5dba5e9b5dba5,
+ 0x3956c25b3956c25b, 0x3956c25b3956c25b, 0x3956c25b3956c25b, 0x3956c25b3956c25b,
+ 0x3956c25b3956c25b, 0x3956c25b3956c25b, 0x3956c25b3956c25b, 0x3956c25b3956c25b,
+ 0x59f111f159f111f1, 0x59f111f159f111f1, 0x59f111f159f111f1, 0x59f111f159f111f1,
+ 0x59f111f159f111f1, 0x59f111f159f111f1, 0x59f111f159f111f1, 0x59f111f159f111f1,
+ 0x923f82a4923f82a4, 0x923f82a4923f82a4, 0x923f82a4923f82a4, 0x923f82a4923f82a4,
+ 0x923f82a4923f82a4, 0x923f82a4923f82a4, 0x923f82a4923f82a4, 0x923f82a4923f82a4,
+ 0xab1c5ed5ab1c5ed5, 0xab1c5ed5ab1c5ed5, 0xab1c5ed5ab1c5ed5, 0xab1c5ed5ab1c5ed5,
+ 0xab1c5ed5ab1c5ed5, 0xab1c5ed5ab1c5ed5, 0xab1c5ed5ab1c5ed5, 0xab1c5ed5ab1c5ed5,
+ 0xd807aa98d807aa98, 0xd807aa98d807aa98, 0xd807aa98d807aa98, 0xd807aa98d807aa98,
+ 0xd807aa98d807aa98, 0xd807aa98d807aa98, 0xd807aa98d807aa98, 0xd807aa98d807aa98,
+ 0x12835b0112835b01, 0x12835b0112835b01, 0x12835b0112835b01, 0x12835b0112835b01,
+ 0x12835b0112835b01, 0x12835b0112835b01, 0x12835b0112835b01, 0x12835b0112835b01,
+ 0x243185be243185be, 0x243185be243185be, 0x243185be243185be, 0x243185be243185be,
+ 0x243185be243185be, 0x243185be243185be, 0x243185be243185be, 0x243185be243185be,
+ 0x550c7dc3550c7dc3, 0x550c7dc3550c7dc3, 0x550c7dc3550c7dc3, 0x550c7dc3550c7dc3,
+ 0x550c7dc3550c7dc3, 0x550c7dc3550c7dc3, 0x550c7dc3550c7dc3, 0x550c7dc3550c7dc3,
+ 0x72be5d7472be5d74, 0x72be5d7472be5d74, 0x72be5d7472be5d74, 0x72be5d7472be5d74,
+ 0x72be5d7472be5d74, 0x72be5d7472be5d74, 0x72be5d7472be5d74, 0x72be5d7472be5d74,
+ 0x80deb1fe80deb1fe, 0x80deb1fe80deb1fe, 0x80deb1fe80deb1fe, 0x80deb1fe80deb1fe,
+ 0x80deb1fe80deb1fe, 0x80deb1fe80deb1fe, 0x80deb1fe80deb1fe, 0x80deb1fe80deb1fe,
+ 0x9bdc06a79bdc06a7, 0x9bdc06a79bdc06a7, 0x9bdc06a79bdc06a7, 0x9bdc06a79bdc06a7,
+ 0x9bdc06a79bdc06a7, 0x9bdc06a79bdc06a7, 0x9bdc06a79bdc06a7, 0x9bdc06a79bdc06a7,
+ 0xc19bf174c19bf174, 0xc19bf174c19bf174, 0xc19bf174c19bf174, 0xc19bf174c19bf174,
+ 0xc19bf174c19bf174, 0xc19bf174c19bf174, 0xc19bf174c19bf174, 0xc19bf174c19bf174,
+ 0xe49b69c1e49b69c1, 0xe49b69c1e49b69c1, 0xe49b69c1e49b69c1, 0xe49b69c1e49b69c1,
+ 0xe49b69c1e49b69c1, 0xe49b69c1e49b69c1, 0xe49b69c1e49b69c1, 0xe49b69c1e49b69c1,
+ 0xefbe4786efbe4786, 0xefbe4786efbe4786, 0xefbe4786efbe4786, 0xefbe4786efbe4786,
+ 0xefbe4786efbe4786, 0xefbe4786efbe4786, 0xefbe4786efbe4786, 0xefbe4786efbe4786,
+ 0x0fc19dc60fc19dc6, 0x0fc19dc60fc19dc6, 0x0fc19dc60fc19dc6, 0x0fc19dc60fc19dc6,
+ 0x0fc19dc60fc19dc6, 0x0fc19dc60fc19dc6, 0x0fc19dc60fc19dc6, 0x0fc19dc60fc19dc6,
+ 0x240ca1cc240ca1cc, 0x240ca1cc240ca1cc, 0x240ca1cc240ca1cc, 0x240ca1cc240ca1cc,
+ 0x240ca1cc240ca1cc, 0x240ca1cc240ca1cc, 0x240ca1cc240ca1cc, 0x240ca1cc240ca1cc,
+ 0x2de92c6f2de92c6f, 0x2de92c6f2de92c6f, 0x2de92c6f2de92c6f, 0x2de92c6f2de92c6f,
+ 0x2de92c6f2de92c6f, 0x2de92c6f2de92c6f, 0x2de92c6f2de92c6f, 0x2de92c6f2de92c6f,
+ 0x4a7484aa4a7484aa, 0x4a7484aa4a7484aa, 0x4a7484aa4a7484aa, 0x4a7484aa4a7484aa,
+ 0x4a7484aa4a7484aa, 0x4a7484aa4a7484aa, 0x4a7484aa4a7484aa, 0x4a7484aa4a7484aa,
+ 0x5cb0a9dc5cb0a9dc, 0x5cb0a9dc5cb0a9dc, 0x5cb0a9dc5cb0a9dc, 0x5cb0a9dc5cb0a9dc,
+ 0x5cb0a9dc5cb0a9dc, 0x5cb0a9dc5cb0a9dc, 0x5cb0a9dc5cb0a9dc, 0x5cb0a9dc5cb0a9dc,
+ 0x76f988da76f988da, 0x76f988da76f988da, 0x76f988da76f988da, 0x76f988da76f988da,
+ 0x76f988da76f988da, 0x76f988da76f988da, 0x76f988da76f988da, 0x76f988da76f988da,
+ 0x983e5152983e5152, 0x983e5152983e5152, 0x983e5152983e5152, 0x983e5152983e5152,
+ 0x983e5152983e5152, 0x983e5152983e5152, 0x983e5152983e5152, 0x983e5152983e5152,
+ 0xa831c66da831c66d, 0xa831c66da831c66d, 0xa831c66da831c66d, 0xa831c66da831c66d,
+ 0xa831c66da831c66d, 0xa831c66da831c66d, 0xa831c66da831c66d, 0xa831c66da831c66d,
+ 0xb00327c8b00327c8, 0xb00327c8b00327c8, 0xb00327c8b00327c8, 0xb00327c8b00327c8,
+ 0xb00327c8b00327c8, 0xb00327c8b00327c8, 0xb00327c8b00327c8, 0xb00327c8b00327c8,
+ 0xbf597fc7bf597fc7, 0xbf597fc7bf597fc7, 0xbf597fc7bf597fc7, 0xbf597fc7bf597fc7,
+ 0xbf597fc7bf597fc7, 0xbf597fc7bf597fc7, 0xbf597fc7bf597fc7, 0xbf597fc7bf597fc7,
+ 0xc6e00bf3c6e00bf3, 0xc6e00bf3c6e00bf3, 0xc6e00bf3c6e00bf3, 0xc6e00bf3c6e00bf3,
+ 0xc6e00bf3c6e00bf3, 0xc6e00bf3c6e00bf3, 0xc6e00bf3c6e00bf3, 0xc6e00bf3c6e00bf3,
+ 0xd5a79147d5a79147, 0xd5a79147d5a79147, 0xd5a79147d5a79147, 0xd5a79147d5a79147,
+ 0xd5a79147d5a79147, 0xd5a79147d5a79147, 0xd5a79147d5a79147, 0xd5a79147d5a79147,
+ 0x06ca635106ca6351, 0x06ca635106ca6351, 0x06ca635106ca6351, 0x06ca635106ca6351,
+ 0x06ca635106ca6351, 0x06ca635106ca6351, 0x06ca635106ca6351, 0x06ca635106ca6351,
+ 0x1429296714292967, 0x1429296714292967, 0x1429296714292967, 0x1429296714292967,
+ 0x1429296714292967, 0x1429296714292967, 0x1429296714292967, 0x1429296714292967,
+ 0x27b70a8527b70a85, 0x27b70a8527b70a85, 0x27b70a8527b70a85, 0x27b70a8527b70a85,
+ 0x27b70a8527b70a85, 0x27b70a8527b70a85, 0x27b70a8527b70a85, 0x27b70a8527b70a85,
+ 0x2e1b21382e1b2138, 0x2e1b21382e1b2138, 0x2e1b21382e1b2138, 0x2e1b21382e1b2138,
+ 0x2e1b21382e1b2138, 0x2e1b21382e1b2138, 0x2e1b21382e1b2138, 0x2e1b21382e1b2138,
+ 0x4d2c6dfc4d2c6dfc, 0x4d2c6dfc4d2c6dfc, 0x4d2c6dfc4d2c6dfc, 0x4d2c6dfc4d2c6dfc,
+ 0x4d2c6dfc4d2c6dfc, 0x4d2c6dfc4d2c6dfc, 0x4d2c6dfc4d2c6dfc, 0x4d2c6dfc4d2c6dfc,
+ 0x53380d1353380d13, 0x53380d1353380d13, 0x53380d1353380d13, 0x53380d1353380d13,
+ 0x53380d1353380d13, 0x53380d1353380d13, 0x53380d1353380d13, 0x53380d1353380d13,
+ 0x650a7354650a7354, 0x650a7354650a7354, 0x650a7354650a7354, 0x650a7354650a7354,
+ 0x650a7354650a7354, 0x650a7354650a7354, 0x650a7354650a7354, 0x650a7354650a7354,
+ 0x766a0abb766a0abb, 0x766a0abb766a0abb, 0x766a0abb766a0abb, 0x766a0abb766a0abb,
+ 0x766a0abb766a0abb, 0x766a0abb766a0abb, 0x766a0abb766a0abb, 0x766a0abb766a0abb,
+ 0x81c2c92e81c2c92e, 0x81c2c92e81c2c92e, 0x81c2c92e81c2c92e, 0x81c2c92e81c2c92e,
+ 0x81c2c92e81c2c92e, 0x81c2c92e81c2c92e, 0x81c2c92e81c2c92e, 0x81c2c92e81c2c92e,
+ 0x92722c8592722c85, 0x92722c8592722c85, 0x92722c8592722c85, 0x92722c8592722c85,
+ 0x92722c8592722c85, 0x92722c8592722c85, 0x92722c8592722c85, 0x92722c8592722c85,
+ 0xa2bfe8a1a2bfe8a1, 0xa2bfe8a1a2bfe8a1, 0xa2bfe8a1a2bfe8a1, 0xa2bfe8a1a2bfe8a1,
+ 0xa2bfe8a1a2bfe8a1, 0xa2bfe8a1a2bfe8a1, 0xa2bfe8a1a2bfe8a1, 0xa2bfe8a1a2bfe8a1,
+ 0xa81a664ba81a664b, 0xa81a664ba81a664b, 0xa81a664ba81a664b, 0xa81a664ba81a664b,
+ 0xa81a664ba81a664b, 0xa81a664ba81a664b, 0xa81a664ba81a664b, 0xa81a664ba81a664b,
+ 0xc24b8b70c24b8b70, 0xc24b8b70c24b8b70, 0xc24b8b70c24b8b70, 0xc24b8b70c24b8b70,
+ 0xc24b8b70c24b8b70, 0xc24b8b70c24b8b70, 0xc24b8b70c24b8b70, 0xc24b8b70c24b8b70,
+ 0xc76c51a3c76c51a3, 0xc76c51a3c76c51a3, 0xc76c51a3c76c51a3, 0xc76c51a3c76c51a3,
+ 0xc76c51a3c76c51a3, 0xc76c51a3c76c51a3, 0xc76c51a3c76c51a3, 0xc76c51a3c76c51a3,
+ 0xd192e819d192e819, 0xd192e819d192e819, 0xd192e819d192e819, 0xd192e819d192e819,
+ 0xd192e819d192e819, 0xd192e819d192e819, 0xd192e819d192e819, 0xd192e819d192e819,
+ 0xd6990624d6990624, 0xd6990624d6990624, 0xd6990624d6990624, 0xd6990624d6990624,
+ 0xd6990624d6990624, 0xd6990624d6990624, 0xd6990624d6990624, 0xd6990624d6990624,
+ 0xf40e3585f40e3585, 0xf40e3585f40e3585, 0xf40e3585f40e3585, 0xf40e3585f40e3585,
+ 0xf40e3585f40e3585, 0xf40e3585f40e3585, 0xf40e3585f40e3585, 0xf40e3585f40e3585,
+ 0x106aa070106aa070, 0x106aa070106aa070, 0x106aa070106aa070, 0x106aa070106aa070,
+ 0x106aa070106aa070, 0x106aa070106aa070, 0x106aa070106aa070, 0x106aa070106aa070,
+ 0x19a4c11619a4c116, 0x19a4c11619a4c116, 0x19a4c11619a4c116, 0x19a4c11619a4c116,
+ 0x19a4c11619a4c116, 0x19a4c11619a4c116, 0x19a4c11619a4c116, 0x19a4c11619a4c116,
+ 0x1e376c081e376c08, 0x1e376c081e376c08, 0x1e376c081e376c08, 0x1e376c081e376c08,
+ 0x1e376c081e376c08, 0x1e376c081e376c08, 0x1e376c081e376c08, 0x1e376c081e376c08,
+ 0x2748774c2748774c, 0x2748774c2748774c, 0x2748774c2748774c, 0x2748774c2748774c,
+ 0x2748774c2748774c, 0x2748774c2748774c, 0x2748774c2748774c, 0x2748774c2748774c,
+ 0x34b0bcb534b0bcb5, 0x34b0bcb534b0bcb5, 0x34b0bcb534b0bcb5, 0x34b0bcb534b0bcb5,
+ 0x34b0bcb534b0bcb5, 0x34b0bcb534b0bcb5, 0x34b0bcb534b0bcb5, 0x34b0bcb534b0bcb5,
+ 0x391c0cb3391c0cb3, 0x391c0cb3391c0cb3, 0x391c0cb3391c0cb3, 0x391c0cb3391c0cb3,
+ 0x391c0cb3391c0cb3, 0x391c0cb3391c0cb3, 0x391c0cb3391c0cb3, 0x391c0cb3391c0cb3,
+ 0x4ed8aa4a4ed8aa4a, 0x4ed8aa4a4ed8aa4a, 0x4ed8aa4a4ed8aa4a, 0x4ed8aa4a4ed8aa4a,
+ 0x4ed8aa4a4ed8aa4a, 0x4ed8aa4a4ed8aa4a, 0x4ed8aa4a4ed8aa4a, 0x4ed8aa4a4ed8aa4a,
+ 0x5b9cca4f5b9cca4f, 0x5b9cca4f5b9cca4f, 0x5b9cca4f5b9cca4f, 0x5b9cca4f5b9cca4f,
+ 0x5b9cca4f5b9cca4f, 0x5b9cca4f5b9cca4f, 0x5b9cca4f5b9cca4f, 0x5b9cca4f5b9cca4f,
+ 0x682e6ff3682e6ff3, 0x682e6ff3682e6ff3, 0x682e6ff3682e6ff3, 0x682e6ff3682e6ff3,
+ 0x682e6ff3682e6ff3, 0x682e6ff3682e6ff3, 0x682e6ff3682e6ff3, 0x682e6ff3682e6ff3,
+ 0x748f82ee748f82ee, 0x748f82ee748f82ee, 0x748f82ee748f82ee, 0x748f82ee748f82ee,
+ 0x748f82ee748f82ee, 0x748f82ee748f82ee, 0x748f82ee748f82ee, 0x748f82ee748f82ee,
+ 0x78a5636f78a5636f, 0x78a5636f78a5636f, 0x78a5636f78a5636f, 0x78a5636f78a5636f,
+ 0x78a5636f78a5636f, 0x78a5636f78a5636f, 0x78a5636f78a5636f, 0x78a5636f78a5636f,
+ 0x84c8781484c87814, 0x84c8781484c87814, 0x84c8781484c87814, 0x84c8781484c87814,
+ 0x84c8781484c87814, 0x84c8781484c87814, 0x84c8781484c87814, 0x84c8781484c87814,
+ 0x8cc702088cc70208, 0x8cc702088cc70208, 0x8cc702088cc70208, 0x8cc702088cc70208,
+ 0x8cc702088cc70208, 0x8cc702088cc70208, 0x8cc702088cc70208, 0x8cc702088cc70208,
+ 0x90befffa90befffa, 0x90befffa90befffa, 0x90befffa90befffa, 0x90befffa90befffa,
+ 0x90befffa90befffa, 0x90befffa90befffa, 0x90befffa90befffa, 0x90befffa90befffa,
+ 0xa4506ceba4506ceb, 0xa4506ceba4506ceb, 0xa4506ceba4506ceb, 0xa4506ceba4506ceb,
+ 0xa4506ceba4506ceb, 0xa4506ceba4506ceb, 0xa4506ceba4506ceb, 0xa4506ceba4506ceb,
+ 0xbef9a3f7bef9a3f7, 0xbef9a3f7bef9a3f7, 0xbef9a3f7bef9a3f7, 0xbef9a3f7bef9a3f7,
+ 0xbef9a3f7bef9a3f7, 0xbef9a3f7bef9a3f7, 0xbef9a3f7bef9a3f7, 0xbef9a3f7bef9a3f7,
+ 0xc67178f2c67178f2, 0xc67178f2c67178f2, 0xc67178f2c67178f2, 0xc67178f2c67178f2,
+ 0xc67178f2c67178f2, 0xc67178f2c67178f2, 0xc67178f2c67178f2, 0xc67178f2c67178f2}
+
+// Interface function to assembly ode
+func blockAvx512(digests *[512]byte, input [16][]byte, mask []uint64) [16][Size]byte {
+
+ scratch := [512]byte{}
+ sha256X16Avx512(digests, &scratch, &table, mask, input)
+
+ output := [16][Size]byte{}
+ for i := 0; i < 16; i++ {
+ output[i] = getDigest(i, digests[:])
+ }
+
+ return output
+}
+
+func getDigest(index int, state []byte) (sum [Size]byte) {
+ for j := 0; j < 16; j += 2 {
+ for i := index*4 + j*Size; i < index*4+(j+1)*Size; i += Size {
+ binary.BigEndian.PutUint32(sum[j*2:], binary.LittleEndian.Uint32(state[i:i+4]))
+ }
+ }
+ return
+}
+
+// Message to send across input channel
+type blockInput struct {
+ uid uint64
+ msg []byte
+ reset bool
+ final bool
+ sumCh chan [Size]byte
+}
+
+// Avx512Server - Type to implement 16x parallel handling of SHA256 invocations
+type Avx512Server struct {
+ blocksCh chan blockInput // Input channel
+ totalIn int // Total number of inputs waiting to be processed
+ lanes [16]Avx512LaneInfo // Array with info per lane (out of 16)
+ digests map[uint64][Size]byte // Map of uids to (interim) digest results
+}
+
+// Avx512LaneInfo - Info for each lane
+type Avx512LaneInfo struct {
+ uid uint64 // unique identification for this SHA processing
+ block []byte // input block to be processed
+ outputCh chan [Size]byte // channel for output result
+}
+
+// NewAvx512Server - Create new object for parallel processing handling
+func NewAvx512Server() *Avx512Server {
+ a512srv := &Avx512Server{}
+ a512srv.digests = make(map[uint64][Size]byte)
+ a512srv.blocksCh = make(chan blockInput)
+
+ // Start a single thread for reading from the input channel
+ go a512srv.Process()
+ return a512srv
+}
+
+// Process - Sole handler for reading from the input channel
+func (a512srv *Avx512Server) Process() {
+ for {
+ select {
+ case block := <-a512srv.blocksCh:
+ if block.reset {
+ a512srv.reset(block.uid)
+ continue
+ }
+ index := block.uid & 0xf
+ // fmt.Println("Adding message:", block.uid, index)
+
+ if a512srv.lanes[index].block != nil { // If slot is already filled, process all inputs
+ //fmt.Println("Invoking Blocks()")
+ a512srv.blocks()
+ }
+ a512srv.totalIn++
+ a512srv.lanes[index] = Avx512LaneInfo{uid: block.uid, block: block.msg}
+ if block.final {
+ a512srv.lanes[index].outputCh = block.sumCh
+ }
+ if a512srv.totalIn == len(a512srv.lanes) {
+ // fmt.Println("Invoking Blocks() while FULL: ")
+ a512srv.blocks()
+ }
+
+ // TODO: test with larger timeout
+ case <-time.After(1 * time.Microsecond):
+ for _, lane := range a512srv.lanes {
+ if lane.block != nil { // check if there is any input to process
+ // fmt.Println("Invoking Blocks() on TIMEOUT: ")
+ a512srv.blocks()
+ break // we are done
+ }
+ }
+ }
+ }
+}
+
+// Do a reset for this calculation
+func (a512srv *Avx512Server) reset(uid uint64) {
+
+ // Check if there is a message still waiting to be processed (and remove if so)
+ for i, lane := range a512srv.lanes {
+ if lane.uid == uid {
+ if lane.block != nil {
+ a512srv.lanes[i] = Avx512LaneInfo{} // clear message
+ a512srv.totalIn--
+ }
+ }
+ }
+
+ // Delete entry from hash map
+ delete(a512srv.digests, uid)
+}
+
+// Invoke assembly and send results back
+func (a512srv *Avx512Server) blocks() {
+
+ inputs := [16][]byte{}
+ for i := range inputs {
+ inputs[i] = a512srv.lanes[i].block
+ }
+
+ mask := expandMask(genMask(inputs))
+ outputs := blockAvx512(a512srv.getDigests(), inputs, mask)
+
+ a512srv.totalIn = 0
+ for i := 0; i < len(outputs); i++ {
+ uid, outputCh := a512srv.lanes[i].uid, a512srv.lanes[i].outputCh
+ a512srv.digests[uid] = outputs[i]
+ a512srv.lanes[i] = Avx512LaneInfo{}
+
+ if outputCh != nil {
+ // Send back result
+ outputCh <- outputs[i]
+ delete(a512srv.digests, uid) // Delete entry from hashmap
+ }
+ }
+}
+
+func (a512srv *Avx512Server) Write(uid uint64, p []byte) (nn int, err error) {
+ a512srv.blocksCh <- blockInput{uid: uid, msg: p}
+ return len(p), nil
+}
+
+// Sum - return sha256 sum in bytes for a given sum id.
+func (a512srv *Avx512Server) Sum(uid uint64, p []byte) [32]byte {
+ sumCh := make(chan [32]byte)
+ a512srv.blocksCh <- blockInput{uid: uid, msg: p, final: true, sumCh: sumCh}
+ return <-sumCh
+}
+
+func (a512srv *Avx512Server) getDigests() *[512]byte {
+ digests := [512]byte{}
+ for i, lane := range a512srv.lanes {
+ a, ok := a512srv.digests[lane.uid]
+ if ok {
+ binary.BigEndian.PutUint32(digests[(i+0*16)*4:], binary.LittleEndian.Uint32(a[0:4]))
+ binary.BigEndian.PutUint32(digests[(i+1*16)*4:], binary.LittleEndian.Uint32(a[4:8]))
+ binary.BigEndian.PutUint32(digests[(i+2*16)*4:], binary.LittleEndian.Uint32(a[8:12]))
+ binary.BigEndian.PutUint32(digests[(i+3*16)*4:], binary.LittleEndian.Uint32(a[12:16]))
+ binary.BigEndian.PutUint32(digests[(i+4*16)*4:], binary.LittleEndian.Uint32(a[16:20]))
+ binary.BigEndian.PutUint32(digests[(i+5*16)*4:], binary.LittleEndian.Uint32(a[20:24]))
+ binary.BigEndian.PutUint32(digests[(i+6*16)*4:], binary.LittleEndian.Uint32(a[24:28]))
+ binary.BigEndian.PutUint32(digests[(i+7*16)*4:], binary.LittleEndian.Uint32(a[28:32]))
+ } else {
+ binary.LittleEndian.PutUint32(digests[(i+0*16)*4:], init0)
+ binary.LittleEndian.PutUint32(digests[(i+1*16)*4:], init1)
+ binary.LittleEndian.PutUint32(digests[(i+2*16)*4:], init2)
+ binary.LittleEndian.PutUint32(digests[(i+3*16)*4:], init3)
+ binary.LittleEndian.PutUint32(digests[(i+4*16)*4:], init4)
+ binary.LittleEndian.PutUint32(digests[(i+5*16)*4:], init5)
+ binary.LittleEndian.PutUint32(digests[(i+6*16)*4:], init6)
+ binary.LittleEndian.PutUint32(digests[(i+7*16)*4:], init7)
+ }
+ }
+ return &digests
+}
+
+// Helper struct for sorting blocks based on length
+type lane struct {
+ len uint
+ pos uint
+}
+
+type lanes []lane
+
+func (lns lanes) Len() int { return len(lns) }
+func (lns lanes) Swap(i, j int) { lns[i], lns[j] = lns[j], lns[i] }
+func (lns lanes) Less(i, j int) bool { return lns[i].len < lns[j].len }
+
+// Helper struct for
+type maskRounds struct {
+ mask uint64
+ rounds uint64
+}
+
+func genMask(input [16][]byte) [16]maskRounds {
+
+ // Sort on blocks length small to large
+ var sorted [16]lane
+ for c, inpt := range input {
+ sorted[c] = lane{uint(len(inpt)), uint(c)}
+ }
+ sort.Sort(lanes(sorted[:]))
+
+ // Create mask array including 'rounds' between masks
+ m, round, index := uint64(0xffff), uint64(0), 0
+ var mr [16]maskRounds
+ for _, s := range sorted {
+ if s.len > 0 {
+ if uint64(s.len)>>6 > round {
+ mr[index] = maskRounds{m, (uint64(s.len) >> 6) - round}
+ index++
+ }
+ round = uint64(s.len) >> 6
+ }
+ m = m & ^(1 << uint(s.pos))
+ }
+
+ return mr
+}
+
+// TODO: remove function
+func expandMask(mr [16]maskRounds) []uint64 {
+ size := uint64(0)
+ for _, r := range mr {
+ size += r.rounds
+ }
+ result, index := make([]uint64, size), 0
+ for _, r := range mr {
+ for j := uint64(0); j < r.rounds; j++ {
+ result[index] = r.mask
+ index++
+ }
+ }
+ return result
+}
diff --git a/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.s b/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.s
new file mode 100644
index 00000000..275bcacb
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256blockAvx512_amd64.s
@@ -0,0 +1,267 @@
+//+build !noasm,!appengine
+
+TEXT ยทsha256X16Avx512(SB), 7, $0
+ MOVQ digests+0(FP), DI
+ MOVQ scratch+8(FP), R12
+ MOVQ mask_len+32(FP), SI
+ MOVQ mask_base+24(FP), R13
+ MOVQ (R13), R14
+ LONG $0x92fbc1c4; BYTE $0xce
+ LEAQ inputs+48(FP), AX
+ QUAD $0xf162076f487ef162; QUAD $0x7ef162014f6f487e; QUAD $0x487ef16202576f48; QUAD $0x6f487ef162035f6f; QUAD $0x6f6f487ef1620467; QUAD $0x06776f487ef16205; LONG $0x487ef162; WORD $0x7f6f; BYTE $0x07
+ MOVQ table+16(FP), DX
+ WORD $0x3148; BYTE $0xc9
+ TESTQ $(1<<0), R14
+ JE skipInput0
+ MOVQ 0*24(AX), R9
+ LONG $0x487cc162; WORD $0x0410; BYTE $0x09
+
+skipInput0:
+ TESTQ $(1<<1), R14
+ JE skipInput1
+ MOVQ 1*24(AX), R9
+ LONG $0x487cc162; WORD $0x0c10; BYTE $0x09
+
+skipInput1:
+ TESTQ $(1<<2), R14
+ JE skipInput2
+ MOVQ 2*24(AX), R9
+ LONG $0x487cc162; WORD $0x1410; BYTE $0x09
+
+skipInput2:
+ TESTQ $(1<<3), R14
+ JE skipInput3
+ MOVQ 3*24(AX), R9
+ LONG $0x487cc162; WORD $0x1c10; BYTE $0x09
+
+skipInput3:
+ TESTQ $(1<<4), R14
+ JE skipInput4
+ MOVQ 4*24(AX), R9
+ LONG $0x487cc162; WORD $0x2410; BYTE $0x09
+
+skipInput4:
+ TESTQ $(1<<5), R14
+ JE skipInput5
+ MOVQ 5*24(AX), R9
+ LONG $0x487cc162; WORD $0x2c10; BYTE $0x09
+
+skipInput5:
+ TESTQ $(1<<6), R14
+ JE skipInput6
+ MOVQ 6*24(AX), R9
+ LONG $0x487cc162; WORD $0x3410; BYTE $0x09
+
+skipInput6:
+ TESTQ $(1<<7), R14
+ JE skipInput7
+ MOVQ 7*24(AX), R9
+ LONG $0x487cc162; WORD $0x3c10; BYTE $0x09
+
+skipInput7:
+ TESTQ $(1<<8), R14
+ JE skipInput8
+ MOVQ 8*24(AX), R9
+ LONG $0x487c4162; WORD $0x0410; BYTE $0x09
+
+skipInput8:
+ TESTQ $(1<<9), R14
+ JE skipInput9
+ MOVQ 9*24(AX), R9
+ LONG $0x487c4162; WORD $0x0c10; BYTE $0x09
+
+skipInput9:
+ TESTQ $(1<<10), R14
+ JE skipInput10
+ MOVQ 10*24(AX), R9
+ LONG $0x487c4162; WORD $0x1410; BYTE $0x09
+
+skipInput10:
+ TESTQ $(1<<11), R14
+ JE skipInput11
+ MOVQ 11*24(AX), R9
+ LONG $0x487c4162; WORD $0x1c10; BYTE $0x09
+
+skipInput11:
+ TESTQ $(1<<12), R14
+ JE skipInput12
+ MOVQ 12*24(AX), R9
+ LONG $0x487c4162; WORD $0x2410; BYTE $0x09
+
+skipInput12:
+ TESTQ $(1<<13), R14
+ JE skipInput13
+ MOVQ 13*24(AX), R9
+ LONG $0x487c4162; WORD $0x2c10; BYTE $0x09
+
+skipInput13:
+ TESTQ $(1<<14), R14
+ JE skipInput14
+ MOVQ 14*24(AX), R9
+ LONG $0x487c4162; WORD $0x3410; BYTE $0x09
+
+skipInput14:
+ TESTQ $(1<<15), R14
+ JE skipInput15
+ MOVQ 15*24(AX), R9
+ LONG $0x487c4162; WORD $0x3c10; BYTE $0x09
+
+skipInput15:
+lloop:
+ LEAQ PSHUFFLE_BYTE_FLIP_MASK<>(SB), DX
+ LONG $0x487e7162; WORD $0x1a6f
+ MOVQ table+16(FP), DX
+ QUAD $0xd162226f487e7162; QUAD $0x7ed16224047f487e; QUAD $0x7ed16201244c7f48; QUAD $0x7ed1620224547f48; QUAD $0x7ed16203245c7f48; QUAD $0x7ed1620424647f48; QUAD $0x7ed16205246c7f48; QUAD $0x7ed1620624747f48; QUAD $0xc1834807247c7f48; QUAD $0x44c9c6407c316240; QUAD $0x62eec1c6407ca162; QUAD $0xa16244d3c6406c31; QUAD $0x34c162eed3c6406c; QUAD $0x407ca162dddac648; QUAD $0xc6407ca16288cac6; QUAD $0xcac648345162ddc2; QUAD $0x44d5c6405ca16288; QUAD $0x62eee5c6405ca162; QUAD $0xa16244d7c6404c31; QUAD $0x6cc162eef7c6404c; QUAD $0x405ca162ddfac640; QUAD $0xc6405ca16288eec6; QUAD $0xd2c6406cc162dde6; QUAD $0x44f1c6403c816288; QUAD $0x62eec1c6403c0162; QUAD $0x016244d3c6402c11; QUAD $0x4c4162eed3c6402c; QUAD $0x403c0162dddac640; QUAD $0xc6403c016288cac6; QUAD $0xf2c6404cc162ddc2; QUAD $0x44d5c6401c016288; QUAD $0x62eee5c6401c0162; QUAD $0x016244d7c6400c11; QUAD $0x2c4162eef7c6400c; QUAD $0x401c0162ddfac640; QUAD $0xc6401c016288eec6; QUAD $0xd2c6402c4162dde6; BYTE $0x88
+ LEAQ PSHUFFLE_TRANSPOSE16_MASK1<>(SB), BX
+ LEAQ PSHUFFLE_TRANSPOSE16_MASK2<>(SB), R8
+ QUAD $0x2262336f487e6162; QUAD $0x487e5162f27648b5; QUAD $0xd27648b53262106f; QUAD $0xa262136f487ee162; QUAD $0x487e5162d77640e5; QUAD $0xcf7640e53262086f; QUAD $0xa2621b6f487ee162; QUAD $0x487ec162dd7640f5; QUAD $0xfd7640f5a262386f; QUAD $0xa2620b6f487ee162; QUAD $0x487ec162cc7640fd; QUAD $0xec7640fda262286f; QUAD $0x8262036f487ee162; QUAD $0x487ec162c27640cd; QUAD $0xe27640cd8262206f; QUAD $0x8262336f487ee162; QUAD $0x487e4162f77640a5; QUAD $0xd77640a50262106f; QUAD $0x02621b6f487e6162; QUAD $0x487e4162dd7640b5; QUAD $0xfd7640b50262386f; QUAD $0x02620b6f487e6162; QUAD $0x487e4162cc7640bd; QUAD $0xec7640bd0262286f; QUAD $0x62eec023408d2362; QUAD $0x236244c023408da3; QUAD $0xada362eee42348ad; QUAD $0x40c5036244e42348; QUAD $0x2340c51362eef723; QUAD $0xfd2340d5036244d7; QUAD $0x44fd2340d58362ee; QUAD $0x62eeea2348b50362; QUAD $0x036244ea2348b583; QUAD $0xe51362eed32340e5; QUAD $0x40f5036244cb2340; QUAD $0x2340f58362eed923; QUAD $0xce2340ed236244d9; QUAD $0x44ce2340eda362ee; QUAD $0xc162d16f487ec162; QUAD $0x407dc262f26f487e; QUAD $0xcb004075c262c300; QUAD $0xc262d300406dc262; QUAD $0x405dc262db004065; QUAD $0xeb004055c262e300; QUAD $0xc262f300404dc262; QUAD $0x403d4262fb004045; QUAD $0xcb0040354262c300; QUAD $0x4262d300402d4262; QUAD $0x401d4262db004025; QUAD $0xeb0040154262e300; QUAD $0x4262f300400d4262; QUAD $0x48455162fb004005; QUAD $0xcc6f487e7162c4fe; QUAD $0x6206c472482df162; QUAD $0xf1620bc4724825f1; QUAD $0x55736219c472481d; QUAD $0x483d3162cace2548; QUAD $0xd42548255362c0fe; QUAD $0x62c1fe483d516296; QUAD $0x65d162c2fe483d51; QUAD $0x724845f162d8fe48; QUAD $0xc0724825f16202c0; QUAD $0x16c072481df1620d; QUAD $0x7362c86f487e7162; QUAD $0x25d362e8ca254875; QUAD $0x4845d16296fc2548; QUAD $0xf8fe4845d162f9fe; QUAD $0x6201626f487e7162; QUAD $0x916211c672481591; QUAD $0x05916213c672480d; QUAD $0x480d53620ad67248; QUAD $0xfe407dc16296ef25; QUAD $0x62c1fe407d8162c5; QUAD $0xb16207c1724815b1; QUAD $0x05b16212c172480d; QUAD $0x480d536203d17248; QUAD $0xfe407dc16296ef25; QUAD $0x62c4fe484d5162c5; QUAD $0x2df162cb6f487e71; QUAD $0x4825f16206c37248; QUAD $0x72481df1620bc372; QUAD $0xcd25485d736219c3; QUAD $0x62c1fe483d3162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xd0fe486dd162c2fe; QUAD $0x6202c772484df162; QUAD $0xf1620dc7724825f1; QUAD $0x7e716216c772481d; QUAD $0x25487d7362cf6f48; QUAD $0xf4254825d362e8c9; QUAD $0x62f1fe484dd16296; QUAD $0x7e7162f0fe484dd1; QUAD $0x4815916202626f48; QUAD $0x72480d916211c772; QUAD $0xd7724805916213c7; QUAD $0x96ef25480d53620a; QUAD $0x8162cdfe4075c162; QUAD $0x4815b162cafe4075; QUAD $0x72480db16207c272; QUAD $0xd2724805b16212c2; QUAD $0x96ef25480d536203; QUAD $0x5162cdfe4075c162; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x3162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x11c0724815b16203; QUAD $0x6213c072480db162; QUAD $0x53620ad0724805b1; QUAD $0x6dc16296ef25480d; QUAD $0xfe406d8162d5fe40; QUAD $0x07c3724815b162d3; QUAD $0x6212c372480db162; QUAD $0x536203d3724805b1; QUAD $0x6dc16296ef25480d; QUAD $0xfe485d5162d5fe40; QUAD $0x62c96f487e7162c4; QUAD $0xf16206c172482df1; QUAD $0x1df1620bc1724825; QUAD $0x486d736219c17248; QUAD $0xfe483d3162cacb25; QUAD $0x96d42548255362c3; QUAD $0x5162c1fe483d5162; QUAD $0x487dd162c2fe483d; QUAD $0xc572485df162c0fe; QUAD $0x0dc5724825f16202; QUAD $0x6216c572481df162; QUAD $0x4d7362cd6f487e71; QUAD $0x4825d362e8cf2548; QUAD $0xfe485dd16296e425; QUAD $0x62e0fe485dd162e1; QUAD $0xb16204626f487e71; QUAD $0x0db16211c1724815; QUAD $0x4805b16213c17248; QUAD $0x25480d53620ad172; QUAD $0xddfe4065c16296ef; QUAD $0xb162dcfe40658162; QUAD $0x0db16207c4724815; QUAD $0x4805b16212c47248; QUAD $0x25480d536203d472; QUAD $0xddfe4065c16296ef; QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; QUAD $0x724815b16205626f; QUAD $0xc272480db16211c2; QUAD $0x0ad2724805b16213; QUAD $0x6296ef25480d5362; QUAD $0x5d8162e5fe405dc1; QUAD $0x724815b162e5fe40; QUAD $0xc572480db16207c5; QUAD $0x03d5724805b16212; QUAD $0x6296ef25480d5362; QUAD $0x6d5162e5fe405dc1; QUAD $0x6f487e7162c4fe48; QUAD $0x06c772482df162cf; QUAD $0x620bc7724825f162; QUAD $0x736219c772481df1; QUAD $0x3d3162cac925487d; QUAD $0x2548255362c5fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x486df162f0fe484d; QUAD $0x724825f16202c372; QUAD $0xc372481df1620dc3; QUAD $0x62cb6f487e716216; QUAD $0xd362e8cd25485d73; QUAD $0x6dd16296d4254825; QUAD $0xfe486dd162d1fe48; QUAD $0x06626f487e7162d0; QUAD $0x6211c3724815b162; QUAD $0xb16213c372480db1; QUAD $0x0d53620ad3724805; QUAD $0x4055c16296ef2548; QUAD $0xeefe40558162edfe; QUAD $0x6207c6724815b162; QUAD $0xb16212c672480db1; QUAD $0x0d536203d6724805; QUAD $0x4055c16296ef2548; QUAD $0xc4fe48755162edfe; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d3162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x15b16207626f487e; QUAD $0x480db16211c47248; QUAD $0x724805b16213c472; QUAD $0xef25480d53620ad4; QUAD $0x62f5fe404dc16296; QUAD $0x15b162f7fe404d81; QUAD $0x480db16207c77248; QUAD $0x724805b16212c772; QUAD $0xef25480d536203d7; QUAD $0x62f5fe404dc16296; QUAD $0x7e7162c4fe487d51; QUAD $0x72482df162cd6f48; QUAD $0xc5724825f16206c5; QUAD $0x19c572481df1620b; QUAD $0x62cacf25484d7362; QUAD $0x255362c7fe483d31; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162e0fe485dd162; QUAD $0x25f16202c172487d; QUAD $0x481df1620dc17248; QUAD $0x6f487e716216c172; QUAD $0xe8cb25486d7362c9; QUAD $0x6296c4254825d362; QUAD $0x7dd162c1fe487dd1; QUAD $0x6f487e7162c0fe48; QUAD $0xc5724815b1620862; QUAD $0x13c572480db16211; QUAD $0x620ad5724805b162; QUAD $0xc16296ef25480d53; QUAD $0x4045a162fdfe4045; QUAD $0xc07248159162f8fe; QUAD $0x12c072480d916207; QUAD $0x6203d07248059162; QUAD $0xc16296ef25480d53; QUAD $0x48455162fdfe4045; QUAD $0xcc6f487e7162c4fe; QUAD $0x6206c472482df162; QUAD $0xf1620bc4724825f1; QUAD $0x55736219c472481d; QUAD $0x483d1162cace2548; QUAD $0xd42548255362c0fe; QUAD $0x62c1fe483d516296; QUAD $0x65d162c2fe483d51; QUAD $0x724845f162d8fe48; QUAD $0xc0724825f16202c0; QUAD $0x16c072481df1620d; QUAD $0x7362c86f487e7162; QUAD $0x25d362e8ca254875; QUAD $0x4845d16296fc2548; QUAD $0xf8fe4845d162f9fe; QUAD $0x6209626f487e7162; QUAD $0xb16211c6724815b1; QUAD $0x05b16213c672480d; QUAD $0x480d53620ad67248; QUAD $0xfe403d416296ef25; QUAD $0x62c1fe403d2162c5; QUAD $0x916207c172481591; QUAD $0x05916212c172480d; QUAD $0x480d536203d17248; QUAD $0xfe403d416296ef25; QUAD $0x62c4fe484d5162c5; QUAD $0x2df162cb6f487e71; QUAD $0x4825f16206c37248; QUAD $0x72481df1620bc372; QUAD $0xcd25485d736219c3; QUAD $0x62c1fe483d1162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xd0fe486dd162c2fe; QUAD $0x6202c772484df162; QUAD $0xf1620dc7724825f1; QUAD $0x7e716216c772481d; QUAD $0x25487d7362cf6f48; QUAD $0xf4254825d362e8c9; QUAD $0x62f1fe484dd16296; QUAD $0x7e7162f0fe484dd1; QUAD $0x4815b1620a626f48; QUAD $0x72480db16211c772; QUAD $0xd7724805b16213c7; QUAD $0x96ef25480d53620a; QUAD $0x2162cdfe40354162; QUAD $0x48159162cafe4035; QUAD $0x72480d916207c272; QUAD $0xd2724805916212c2; QUAD $0x96ef25480d536203; QUAD $0x5162cdfe40354162; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x1162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x11c072481591620b; QUAD $0x6213c072480d9162; QUAD $0x53620ad072480591; QUAD $0x2d416296ef25480d; QUAD $0xfe402d2162d5fe40; QUAD $0x07c37248159162d3; QUAD $0x6212c372480d9162; QUAD $0x536203d372480591; QUAD $0x2d416296ef25480d; QUAD $0xfe485d5162d5fe40; QUAD $0x62c96f487e7162c4; QUAD $0xf16206c172482df1; QUAD $0x1df1620bc1724825; QUAD $0x486d736219c17248; QUAD $0xfe483d1162cacb25; QUAD $0x96d42548255362c3; QUAD $0x5162c1fe483d5162; QUAD $0x487dd162c2fe483d; QUAD $0xc572485df162c0fe; QUAD $0x0dc5724825f16202; QUAD $0x6216c572481df162; QUAD $0x4d7362cd6f487e71; QUAD $0x4825d362e8cf2548; QUAD $0xfe485dd16296e425; QUAD $0x62e0fe485dd162e1; QUAD $0x91620c626f487e71; QUAD $0x0d916211c1724815; QUAD $0x4805916213c17248; QUAD $0x25480d53620ad172; QUAD $0xddfe4025416296ef; QUAD $0x9162dcfe40252162; QUAD $0x0d916207c4724815; QUAD $0x4805916212c47248; QUAD $0x25480d536203d472; QUAD $0xddfe4025416296ef; QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; QUAD $0x72481591620d626f; QUAD $0xc272480d916211c2; QUAD $0x0ad2724805916213; QUAD $0x6296ef25480d5362; QUAD $0x1d2162e5fe401d41; QUAD $0x7248159162e5fe40; QUAD $0xc572480d916207c5; QUAD $0x03d5724805916212; QUAD $0x6296ef25480d5362; QUAD $0x6d5162e5fe401d41; QUAD $0x6f487e7162c4fe48; QUAD $0x06c772482df162cf; QUAD $0x620bc7724825f162; QUAD $0x736219c772481df1; QUAD $0x3d1162cac925487d; QUAD $0x2548255362c5fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x486df162f0fe484d; QUAD $0x724825f16202c372; QUAD $0xc372481df1620dc3; QUAD $0x62cb6f487e716216; QUAD $0xd362e8cd25485d73; QUAD $0x6dd16296d4254825; QUAD $0xfe486dd162d1fe48; QUAD $0x0e626f487e7162d0; QUAD $0x6211c37248159162; QUAD $0x916213c372480d91; QUAD $0x0d53620ad3724805; QUAD $0x4015416296ef2548; QUAD $0xeefe40152162edfe; QUAD $0x6207c67248159162; QUAD $0x916212c672480d91; QUAD $0x0d536203d6724805; QUAD $0x4015416296ef2548; QUAD $0xc4fe48755162edfe; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d1162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x1591620f626f487e; QUAD $0x480d916211c47248; QUAD $0x724805916213c472; QUAD $0xef25480d53620ad4; QUAD $0x62f5fe400d416296; QUAD $0x159162f7fe400d21; QUAD $0x480d916207c77248; QUAD $0x724805916212c772; QUAD $0xef25480d536203d7; QUAD $0x62f5fe400d416296; QUAD $0x7e7162c4fe487d51; QUAD $0x72482df162cd6f48; QUAD $0xc5724825f16206c5; QUAD $0x19c572481df1620b; QUAD $0x62cacf25484d7362; QUAD $0x255362c7fe483d11; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162e0fe485dd162; QUAD $0x25f16202c172487d; QUAD $0x481df1620dc17248; QUAD $0x6f487e716216c172; QUAD $0xe8cb25486d7362c9; QUAD $0x6296c4254825d362; QUAD $0x7dd162c1fe487dd1; QUAD $0x6f487e7162c0fe48; QUAD $0xc572481591621062; QUAD $0x13c572480d916211; QUAD $0x620ad57248059162; QUAD $0x416296ef25480d53; QUAD $0x40050162fdfe4005; QUAD $0xc0724815b162f8fe; QUAD $0x12c072480db16207; QUAD $0x6203d0724805b162; QUAD $0x416296ef25480d53; QUAD $0x48455162fdfe4005; QUAD $0xcc6f487e7162c4fe; QUAD $0x6206c472482df162; QUAD $0xf1620bc4724825f1; QUAD $0x55736219c472481d; QUAD $0x483d3162cace2548; QUAD $0xd42548255362c0fe; QUAD $0x62c1fe483d516296; QUAD $0x65d162c2fe483d51; QUAD $0x724845f162d8fe48; QUAD $0xc0724825f16202c0; QUAD $0x16c072481df1620d; QUAD $0x7362c86f487e7162; QUAD $0x25d362e8ca254875; QUAD $0x4845d16296fc2548; QUAD $0xf8fe4845d162f9fe; QUAD $0x6211626f487e7162; QUAD $0x916211c672481591; QUAD $0x05916213c672480d; QUAD $0x480d53620ad67248; QUAD $0xfe407dc16296ef25; QUAD $0x62c1fe407d8162c5; QUAD $0xb16207c1724815b1; QUAD $0x05b16212c172480d; QUAD $0x480d536203d17248; QUAD $0xfe407dc16296ef25; QUAD $0x62c4fe484d5162c5; QUAD $0x2df162cb6f487e71; QUAD $0x4825f16206c37248; QUAD $0x72481df1620bc372; QUAD $0xcd25485d736219c3; QUAD $0x62c1fe483d3162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xd0fe486dd162c2fe; QUAD $0x6202c772484df162; QUAD $0xf1620dc7724825f1; QUAD $0x7e716216c772481d; QUAD $0x25487d7362cf6f48; QUAD $0xf4254825d362e8c9; QUAD $0x62f1fe484dd16296; QUAD $0x7e7162f0fe484dd1; QUAD $0x4815916212626f48; QUAD $0x72480d916211c772; QUAD $0xd7724805916213c7; QUAD $0x96ef25480d53620a; QUAD $0x8162cdfe4075c162; QUAD $0x4815b162cafe4075; QUAD $0x72480db16207c272; QUAD $0xd2724805b16212c2; QUAD $0x96ef25480d536203; QUAD $0x5162cdfe4075c162; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x3162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x11c0724815b16213; QUAD $0x6213c072480db162; QUAD $0x53620ad0724805b1; QUAD $0x6dc16296ef25480d; QUAD $0xfe406d8162d5fe40; QUAD $0x07c3724815b162d3; QUAD $0x6212c372480db162; QUAD $0x536203d3724805b1; QUAD $0x6dc16296ef25480d; QUAD $0xfe485d5162d5fe40; QUAD $0x62c96f487e7162c4; QUAD $0xf16206c172482df1; QUAD $0x1df1620bc1724825; QUAD $0x486d736219c17248; QUAD $0xfe483d3162cacb25; QUAD $0x96d42548255362c3; QUAD $0x5162c1fe483d5162; QUAD $0x487dd162c2fe483d; QUAD $0xc572485df162c0fe; QUAD $0x0dc5724825f16202; QUAD $0x6216c572481df162; QUAD $0x4d7362cd6f487e71; QUAD $0x4825d362e8cf2548; QUAD $0xfe485dd16296e425; QUAD $0x62e0fe485dd162e1; QUAD $0xb16214626f487e71; QUAD $0x0db16211c1724815; QUAD $0x4805b16213c17248; QUAD $0x25480d53620ad172; QUAD $0xddfe4065c16296ef; QUAD $0xb162dcfe40658162; QUAD $0x0db16207c4724815; QUAD $0x4805b16212c47248; QUAD $0x25480d536203d472; QUAD $0xddfe4065c16296ef; QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; QUAD $0x724815b16215626f; QUAD $0xc272480db16211c2; QUAD $0x0ad2724805b16213; QUAD $0x6296ef25480d5362; QUAD $0x5d8162e5fe405dc1; QUAD $0x724815b162e5fe40; QUAD $0xc572480db16207c5; QUAD $0x03d5724805b16212; QUAD $0x6296ef25480d5362; QUAD $0x6d5162e5fe405dc1; QUAD $0x6f487e7162c4fe48; QUAD $0x06c772482df162cf; QUAD $0x620bc7724825f162; QUAD $0x736219c772481df1; QUAD $0x3d3162cac925487d; QUAD $0x2548255362c5fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x486df162f0fe484d; QUAD $0x724825f16202c372; QUAD $0xc372481df1620dc3; QUAD $0x62cb6f487e716216; QUAD $0xd362e8cd25485d73; QUAD $0x6dd16296d4254825; QUAD $0xfe486dd162d1fe48; QUAD $0x16626f487e7162d0; QUAD $0x6211c3724815b162; QUAD $0xb16213c372480db1; QUAD $0x0d53620ad3724805; QUAD $0x4055c16296ef2548; QUAD $0xeefe40558162edfe; QUAD $0x6207c6724815b162; QUAD $0xb16212c672480db1; QUAD $0x0d536203d6724805; QUAD $0x4055c16296ef2548; QUAD $0xc4fe48755162edfe; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d3162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x15b16217626f487e; QUAD $0x480db16211c47248; QUAD $0x724805b16213c472; QUAD $0xef25480d53620ad4; QUAD $0x62f5fe404dc16296; QUAD $0x15b162f7fe404d81; QUAD $0x480db16207c77248; QUAD $0x724805b16212c772; QUAD $0xef25480d536203d7; QUAD $0x62f5fe404dc16296; QUAD $0x7e7162c4fe487d51; QUAD $0x72482df162cd6f48; QUAD $0xc5724825f16206c5; QUAD $0x19c572481df1620b; QUAD $0x62cacf25484d7362; QUAD $0x255362c7fe483d31; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162e0fe485dd162; QUAD $0x25f16202c172487d; QUAD $0x481df1620dc17248; QUAD $0x6f487e716216c172; QUAD $0xe8cb25486d7362c9; QUAD $0x6296c4254825d362; QUAD $0x7dd162c1fe487dd1; QUAD $0x6f487e7162c0fe48; QUAD $0xc5724815b1621862; QUAD $0x13c572480db16211; QUAD $0x620ad5724805b162; QUAD $0xc16296ef25480d53; QUAD $0x4045a162fdfe4045; QUAD $0xc07248159162f8fe; QUAD $0x12c072480d916207; QUAD $0x6203d07248059162; QUAD $0xc16296ef25480d53; QUAD $0x48455162fdfe4045; QUAD $0xcc6f487e7162c4fe; QUAD $0x6206c472482df162; QUAD $0xf1620bc4724825f1; QUAD $0x55736219c472481d; QUAD $0x483d1162cace2548; QUAD $0xd42548255362c0fe; QUAD $0x62c1fe483d516296; QUAD $0x65d162c2fe483d51; QUAD $0x724845f162d8fe48; QUAD $0xc0724825f16202c0; QUAD $0x16c072481df1620d; QUAD $0x7362c86f487e7162; QUAD $0x25d362e8ca254875; QUAD $0x4845d16296fc2548; QUAD $0xf8fe4845d162f9fe; QUAD $0x6219626f487e7162; QUAD $0xb16211c6724815b1; QUAD $0x05b16213c672480d; QUAD $0x480d53620ad67248; QUAD $0xfe403d416296ef25; QUAD $0x62c1fe403d2162c5; QUAD $0x916207c172481591; QUAD $0x05916212c172480d; QUAD $0x480d536203d17248; QUAD $0xfe403d416296ef25; QUAD $0x62c4fe484d5162c5; QUAD $0x2df162cb6f487e71; QUAD $0x4825f16206c37248; QUAD $0x72481df1620bc372; QUAD $0xcd25485d736219c3; QUAD $0x62c1fe483d1162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xd0fe486dd162c2fe; QUAD $0x6202c772484df162; QUAD $0xf1620dc7724825f1; QUAD $0x7e716216c772481d; QUAD $0x25487d7362cf6f48; QUAD $0xf4254825d362e8c9; QUAD $0x62f1fe484dd16296; QUAD $0x7e7162f0fe484dd1; QUAD $0x4815b1621a626f48; QUAD $0x72480db16211c772; QUAD $0xd7724805b16213c7; QUAD $0x96ef25480d53620a; QUAD $0x2162cdfe40354162; QUAD $0x48159162cafe4035; QUAD $0x72480d916207c272; QUAD $0xd2724805916212c2; QUAD $0x96ef25480d536203; QUAD $0x5162cdfe40354162; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x1162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x11c072481591621b; QUAD $0x6213c072480d9162; QUAD $0x53620ad072480591; QUAD $0x2d416296ef25480d; QUAD $0xfe402d2162d5fe40; QUAD $0x07c37248159162d3; QUAD $0x6212c372480d9162; QUAD $0x536203d372480591; QUAD $0x2d416296ef25480d; QUAD $0xfe485d5162d5fe40; QUAD $0x62c96f487e7162c4; QUAD $0xf16206c172482df1; QUAD $0x1df1620bc1724825; QUAD $0x486d736219c17248; QUAD $0xfe483d1162cacb25; QUAD $0x96d42548255362c3; QUAD $0x5162c1fe483d5162; QUAD $0x487dd162c2fe483d; QUAD $0xc572485df162c0fe; QUAD $0x0dc5724825f16202; QUAD $0x6216c572481df162; QUAD $0x4d7362cd6f487e71; QUAD $0x4825d362e8cf2548; QUAD $0xfe485dd16296e425; QUAD $0x62e0fe485dd162e1; QUAD $0x91621c626f487e71; QUAD $0x0d916211c1724815; QUAD $0x4805916213c17248; QUAD $0x25480d53620ad172; QUAD $0xddfe4025416296ef; QUAD $0x9162dcfe40252162; QUAD $0x0d916207c4724815; QUAD $0x4805916212c47248; QUAD $0x25480d536203d472; QUAD $0xddfe4025416296ef; QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; QUAD $0x72481591621d626f; QUAD $0xc272480d916211c2; QUAD $0x0ad2724805916213; QUAD $0x6296ef25480d5362; QUAD $0x1d2162e5fe401d41; QUAD $0x7248159162e5fe40; QUAD $0xc572480d916207c5; QUAD $0x03d5724805916212; QUAD $0x6296ef25480d5362; QUAD $0x6d5162e5fe401d41; QUAD $0x6f487e7162c4fe48; QUAD $0x06c772482df162cf; QUAD $0x620bc7724825f162; QUAD $0x736219c772481df1; QUAD $0x3d1162cac925487d; QUAD $0x2548255362c5fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x486df162f0fe484d; QUAD $0x724825f16202c372; QUAD $0xc372481df1620dc3; QUAD $0x62cb6f487e716216; QUAD $0xd362e8cd25485d73; QUAD $0x6dd16296d4254825; QUAD $0xfe486dd162d1fe48; QUAD $0x1e626f487e7162d0; QUAD $0x6211c37248159162; QUAD $0x916213c372480d91; QUAD $0x0d53620ad3724805; QUAD $0x4015416296ef2548; QUAD $0xeefe40152162edfe; QUAD $0x6207c67248159162; QUAD $0x916212c672480d91; QUAD $0x0d536203d6724805; QUAD $0x4015416296ef2548; QUAD $0xc4fe48755162edfe; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d1162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x1591621f626f487e; QUAD $0x480d916211c47248; QUAD $0x724805916213c472; QUAD $0xef25480d53620ad4; QUAD $0x62f5fe400d416296; QUAD $0x159162f7fe400d21; QUAD $0x480d916207c77248; QUAD $0x724805916212c772; QUAD $0xef25480d536203d7; QUAD $0x62f5fe400d416296; QUAD $0x7e7162c4fe487d51; QUAD $0x72482df162cd6f48; QUAD $0xc5724825f16206c5; QUAD $0x19c572481df1620b; QUAD $0x62cacf25484d7362; QUAD $0x255362c7fe483d11; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162e0fe485dd162; QUAD $0x25f16202c172487d; QUAD $0x481df1620dc17248; QUAD $0x6f487e716216c172; QUAD $0xe8cb25486d7362c9; QUAD $0x6296c4254825d362; QUAD $0x7dd162c1fe487dd1; QUAD $0x6f487e7162c0fe48; QUAD $0xc572481591622062; QUAD $0x13c572480d916211; QUAD $0x620ad57248059162; QUAD $0x416296ef25480d53; QUAD $0x40050162fdfe4005; QUAD $0xc0724815b162f8fe; QUAD $0x12c072480db16207; QUAD $0x6203d0724805b162; QUAD $0x416296ef25480d53; QUAD $0x48455162fdfe4005; QUAD $0xcc6f487e7162c4fe; QUAD $0x6206c472482df162; QUAD $0xf1620bc4724825f1; QUAD $0x55736219c472481d; QUAD $0x483d3162cace2548; QUAD $0xd42548255362c0fe; QUAD $0x62c1fe483d516296; QUAD $0x65d162c2fe483d51; QUAD $0x724845f162d8fe48; QUAD $0xc0724825f16202c0; QUAD $0x16c072481df1620d; QUAD $0x7362c86f487e7162; QUAD $0x25d362e8ca254875; QUAD $0x4845d16296fc2548; QUAD $0xf8fe4845d162f9fe; QUAD $0x6221626f487e7162; QUAD $0x916211c672481591; QUAD $0x05916213c672480d; QUAD $0x480d53620ad67248; QUAD $0xfe407dc16296ef25; QUAD $0x62c1fe407d8162c5; QUAD $0xb16207c1724815b1; QUAD $0x05b16212c172480d; QUAD $0x480d536203d17248; QUAD $0xfe407dc16296ef25; QUAD $0x62c4fe484d5162c5; QUAD $0x2df162cb6f487e71; QUAD $0x4825f16206c37248; QUAD $0x72481df1620bc372; QUAD $0xcd25485d736219c3; QUAD $0x62c1fe483d3162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xd0fe486dd162c2fe; QUAD $0x6202c772484df162; QUAD $0xf1620dc7724825f1; QUAD $0x7e716216c772481d; QUAD $0x25487d7362cf6f48; QUAD $0xf4254825d362e8c9; QUAD $0x62f1fe484dd16296; QUAD $0x7e7162f0fe484dd1; QUAD $0x4815916222626f48; QUAD $0x72480d916211c772; QUAD $0xd7724805916213c7; QUAD $0x96ef25480d53620a; QUAD $0x8162cdfe4075c162; QUAD $0x4815b162cafe4075; QUAD $0x72480db16207c272; QUAD $0xd2724805b16212c2; QUAD $0x96ef25480d536203; QUAD $0x5162cdfe4075c162; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x3162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x11c0724815b16223; QUAD $0x6213c072480db162; QUAD $0x53620ad0724805b1; QUAD $0x6dc16296ef25480d; QUAD $0xfe406d8162d5fe40; QUAD $0x07c3724815b162d3; QUAD $0x6212c372480db162; QUAD $0x536203d3724805b1; QUAD $0x6dc16296ef25480d; QUAD $0xfe485d5162d5fe40; QUAD $0x62c96f487e7162c4; QUAD $0xf16206c172482df1; QUAD $0x1df1620bc1724825; QUAD $0x486d736219c17248; QUAD $0xfe483d3162cacb25; QUAD $0x96d42548255362c3; QUAD $0x5162c1fe483d5162; QUAD $0x487dd162c2fe483d; QUAD $0xc572485df162c0fe; QUAD $0x0dc5724825f16202; QUAD $0x6216c572481df162; QUAD $0x4d7362cd6f487e71; QUAD $0x4825d362e8cf2548; QUAD $0xfe485dd16296e425; QUAD $0x62e0fe485dd162e1; QUAD $0xb16224626f487e71; QUAD $0x0db16211c1724815; QUAD $0x4805b16213c17248; QUAD $0x25480d53620ad172; QUAD $0xddfe4065c16296ef; QUAD $0xb162dcfe40658162; QUAD $0x0db16207c4724815; QUAD $0x4805b16212c47248; QUAD $0x25480d536203d472; QUAD $0xddfe4065c16296ef; QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; QUAD $0x724815b16225626f; QUAD $0xc272480db16211c2; QUAD $0x0ad2724805b16213; QUAD $0x6296ef25480d5362; QUAD $0x5d8162e5fe405dc1; QUAD $0x724815b162e5fe40; QUAD $0xc572480db16207c5; QUAD $0x03d5724805b16212; QUAD $0x6296ef25480d5362; QUAD $0x6d5162e5fe405dc1; QUAD $0x6f487e7162c4fe48; QUAD $0x06c772482df162cf; QUAD $0x620bc7724825f162; QUAD $0x736219c772481df1; QUAD $0x3d3162cac925487d; QUAD $0x2548255362c5fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x486df162f0fe484d; QUAD $0x724825f16202c372; QUAD $0xc372481df1620dc3; QUAD $0x62cb6f487e716216; QUAD $0xd362e8cd25485d73; QUAD $0x6dd16296d4254825; QUAD $0xfe486dd162d1fe48; QUAD $0x26626f487e7162d0; QUAD $0x6211c3724815b162; QUAD $0xb16213c372480db1; QUAD $0x0d53620ad3724805; QUAD $0x4055c16296ef2548; QUAD $0xeefe40558162edfe; QUAD $0x6207c6724815b162; QUAD $0xb16212c672480db1; QUAD $0x0d536203d6724805; QUAD $0x4055c16296ef2548; QUAD $0xc4fe48755162edfe; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d3162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x15b16227626f487e; QUAD $0x480db16211c47248; QUAD $0x724805b16213c472; QUAD $0xef25480d53620ad4; QUAD $0x62f5fe404dc16296; QUAD $0x15b162f7fe404d81; QUAD $0x480db16207c77248; QUAD $0x724805b16212c772; QUAD $0xef25480d536203d7; QUAD $0x62f5fe404dc16296; QUAD $0x7e7162c4fe487d51; QUAD $0x72482df162cd6f48; QUAD $0xc5724825f16206c5; QUAD $0x19c572481df1620b; QUAD $0x62cacf25484d7362; QUAD $0x255362c7fe483d31; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162e0fe485dd162; QUAD $0x25f16202c172487d; QUAD $0x481df1620dc17248; QUAD $0x6f487e716216c172; QUAD $0xe8cb25486d7362c9; QUAD $0x6296c4254825d362; QUAD $0x7dd162c1fe487dd1; QUAD $0x6f487e7162c0fe48; QUAD $0xc5724815b1622862; QUAD $0x13c572480db16211; QUAD $0x620ad5724805b162; QUAD $0xc16296ef25480d53; QUAD $0x4045a162fdfe4045; QUAD $0xc07248159162f8fe; QUAD $0x12c072480d916207; QUAD $0x6203d07248059162; QUAD $0xc16296ef25480d53; QUAD $0x48455162fdfe4045; QUAD $0xcc6f487e7162c4fe; QUAD $0x6206c472482df162; QUAD $0xf1620bc4724825f1; QUAD $0x55736219c472481d; QUAD $0x483d1162cace2548; QUAD $0xd42548255362c0fe; QUAD $0x62c1fe483d516296; QUAD $0x65d162c2fe483d51; QUAD $0x724845f162d8fe48; QUAD $0xc0724825f16202c0; QUAD $0x16c072481df1620d; QUAD $0x7362c86f487e7162; QUAD $0x25d362e8ca254875; QUAD $0x4845d16296fc2548; QUAD $0xf8fe4845d162f9fe; QUAD $0x6229626f487e7162; QUAD $0xb16211c6724815b1; QUAD $0x05b16213c672480d; QUAD $0x480d53620ad67248; QUAD $0xfe403d416296ef25; QUAD $0x62c1fe403d2162c5; QUAD $0x916207c172481591; QUAD $0x05916212c172480d; QUAD $0x480d536203d17248; QUAD $0xfe403d416296ef25; QUAD $0x62c4fe484d5162c5; QUAD $0x2df162cb6f487e71; QUAD $0x4825f16206c37248; QUAD $0x72481df1620bc372; QUAD $0xcd25485d736219c3; QUAD $0x62c1fe483d1162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xd0fe486dd162c2fe; QUAD $0x6202c772484df162; QUAD $0xf1620dc7724825f1; QUAD $0x7e716216c772481d; QUAD $0x25487d7362cf6f48; QUAD $0xf4254825d362e8c9; QUAD $0x62f1fe484dd16296; QUAD $0x7e7162f0fe484dd1; QUAD $0x4815b1622a626f48; QUAD $0x72480db16211c772; QUAD $0xd7724805b16213c7; QUAD $0x96ef25480d53620a; QUAD $0x2162cdfe40354162; QUAD $0x48159162cafe4035; QUAD $0x72480d916207c272; QUAD $0xd2724805916212c2; QUAD $0x96ef25480d536203; QUAD $0x5162cdfe40354162; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x1162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x11c072481591622b; QUAD $0x6213c072480d9162; QUAD $0x53620ad072480591; QUAD $0x2d416296ef25480d; QUAD $0xfe402d2162d5fe40; QUAD $0x07c37248159162d3; QUAD $0x6212c372480d9162; QUAD $0x536203d372480591; QUAD $0x2d416296ef25480d; QUAD $0xfe485d5162d5fe40; QUAD $0x62c96f487e7162c4; QUAD $0xf16206c172482df1; QUAD $0x1df1620bc1724825; QUAD $0x486d736219c17248; QUAD $0xfe483d1162cacb25; QUAD $0x96d42548255362c3; QUAD $0x5162c1fe483d5162; QUAD $0x487dd162c2fe483d; QUAD $0xc572485df162c0fe; QUAD $0x0dc5724825f16202; QUAD $0x6216c572481df162; QUAD $0x4d7362cd6f487e71; QUAD $0x4825d362e8cf2548; QUAD $0xfe485dd16296e425; QUAD $0x62e0fe485dd162e1; QUAD $0x91622c626f487e71; QUAD $0x0d916211c1724815; QUAD $0x4805916213c17248; QUAD $0x25480d53620ad172; QUAD $0xddfe4025416296ef; QUAD $0x9162dcfe40252162; QUAD $0x0d916207c4724815; QUAD $0x4805916212c47248; QUAD $0x25480d536203d472; QUAD $0xddfe4025416296ef; QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; QUAD $0x72481591622d626f; QUAD $0xc272480d916211c2; QUAD $0x0ad2724805916213; QUAD $0x6296ef25480d5362; QUAD $0x1d2162e5fe401d41; QUAD $0x7248159162e5fe40; QUAD $0xc572480d916207c5; QUAD $0x03d5724805916212; QUAD $0x6296ef25480d5362; QUAD $0x6d5162e5fe401d41; QUAD $0x6f487e7162c4fe48; QUAD $0x06c772482df162cf; QUAD $0x620bc7724825f162; QUAD $0x736219c772481df1; QUAD $0x3d1162cac925487d; QUAD $0x2548255362c5fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x486df162f0fe484d; QUAD $0x724825f16202c372; QUAD $0xc372481df1620dc3; QUAD $0x62cb6f487e716216; QUAD $0xd362e8cd25485d73; QUAD $0x6dd16296d4254825; QUAD $0xfe486dd162d1fe48; QUAD $0x2e626f487e7162d0; QUAD $0x6211c37248159162; QUAD $0x916213c372480d91; QUAD $0x0d53620ad3724805; QUAD $0x4015416296ef2548; QUAD $0xeefe40152162edfe; QUAD $0x6207c67248159162; QUAD $0x916212c672480d91; QUAD $0x0d536203d6724805; QUAD $0x4015416296ef2548; QUAD $0xc4fe48755162edfe; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d1162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x1591622f626f487e; QUAD $0x480d916211c47248; QUAD $0x724805916213c472; QUAD $0xef25480d53620ad4; QUAD $0x62f5fe400d416296; QUAD $0x159162f7fe400d21; QUAD $0x480d916207c77248; QUAD $0x724805916212c772; QUAD $0xef25480d536203d7; QUAD $0x62f5fe400d416296; QUAD $0x7e7162c4fe487d51; QUAD $0x72482df162cd6f48; QUAD $0xc5724825f16206c5; QUAD $0x19c572481df1620b; QUAD $0x62cacf25484d7362; QUAD $0x255362c7fe483d11; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162e0fe485dd162; QUAD $0x25f16202c172487d; QUAD $0x481df1620dc17248; QUAD $0x6f487e716216c172; QUAD $0xe8cb25486d7362c9; QUAD $0x6296c4254825d362; QUAD $0x7dd162c1fe487dd1; QUAD $0x6f487e7162c0fe48; QUAD $0xc572481591623062; QUAD $0x13c572480d916211; QUAD $0x620ad57248059162; QUAD $0x416296ef25480d53; QUAD $0x40050162fdfe4005; QUAD $0xc0724815b162f8fe; QUAD $0x12c072480db16207; QUAD $0x6203d0724805b162; QUAD $0x416296ef25480d53; QUAD $0x01ee8348fdfe4005
+ JE lastLoop
+ ADDQ $8, R13
+ MOVQ (R13), R14
+ QUAD $0x7162c4fe48455162; QUAD $0x482df162cc6f487e; QUAD $0x724825f16206c472; QUAD $0xc472481df1620bc4; QUAD $0xcace254855736219; QUAD $0x5362c0fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62d8fe4865d162c2; QUAD $0xf16202c0724845f1; QUAD $0x1df1620dc0724825; QUAD $0x487e716216c07248; QUAD $0xca2548757362c86f; QUAD $0x96fc254825d362e8; QUAD $0xd162f9fe4845d162; QUAD $0x487e7162f8fe4845; WORD $0x626f; BYTE $0x31
+ TESTQ $(1<<0), R14
+ JE skipNext0
+ MOVQ 0*24(AX), R9
+ LONG $0x487cc162; WORD $0x0410; BYTE $0x09
+
+skipNext0:
+ QUAD $0x7162c4fe484d5162; QUAD $0x482df162cb6f487e; QUAD $0x724825f16206c372; QUAD $0xc372481df1620bc3; QUAD $0xcacd25485d736219; QUAD $0x5362c1fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62d0fe486dd162c2; QUAD $0xf16202c772484df1; QUAD $0x1df1620dc7724825; QUAD $0x487e716216c77248; QUAD $0xc925487d7362cf6f; QUAD $0x96f4254825d362e8; QUAD $0xd162f1fe484dd162; QUAD $0x487e7162f0fe484d; WORD $0x626f; BYTE $0x32
+ TESTQ $(1<<1), R14
+ JE skipNext1
+ MOVQ 1*24(AX), R9
+ LONG $0x487cc162; WORD $0x0c10; BYTE $0x09
+
+skipNext1:
+ QUAD $0x7162c4fe48555162; QUAD $0x482df162ca6f487e; QUAD $0x724825f16206c272; QUAD $0xc272481df1620bc2; QUAD $0xcacc254865736219; QUAD $0x5362c2fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62c8fe4875d162c2; QUAD $0xf16202c6724855f1; QUAD $0x1df1620dc6724825; QUAD $0x487e716216c67248; QUAD $0xc82548457362ce6f; QUAD $0x96ec254825d362e8; QUAD $0xd162e9fe4855d162; QUAD $0x487e7162e8fe4855; WORD $0x626f; BYTE $0x33
+ TESTQ $(1<<2), R14
+ JE skipNext2
+ MOVQ 2*24(AX), R9
+ LONG $0x487cc162; WORD $0x1410; BYTE $0x09
+
+skipNext2:
+ QUAD $0x7162c4fe485d5162; QUAD $0x482df162c96f487e; QUAD $0x724825f16206c172; QUAD $0xc172481df1620bc1; QUAD $0xcacb25486d736219; QUAD $0x5362c3fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62c0fe487dd162c2; QUAD $0xf16202c572485df1; QUAD $0x1df1620dc5724825; QUAD $0x487e716216c57248; QUAD $0xcf25484d7362cd6f; QUAD $0x96e4254825d362e8; QUAD $0xd162e1fe485dd162; QUAD $0x487e7162e0fe485d; WORD $0x626f; BYTE $0x34
+ TESTQ $(1<<3), R14
+ JE skipNext3
+ MOVQ 3*24(AX), R9
+ LONG $0x487cc162; WORD $0x1c10; BYTE $0x09
+
+skipNext3:
+ QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; WORD $0x626f; BYTE $0x35
+ TESTQ $(1<<4), R14
+ JE skipNext4
+ MOVQ 4*24(AX), R9
+ LONG $0x487cc162; WORD $0x2410; BYTE $0x09
+
+skipNext4:
+ QUAD $0x7162c4fe486d5162; QUAD $0x482df162cf6f487e; QUAD $0x724825f16206c772; QUAD $0xc772481df1620bc7; QUAD $0xcac925487d736219; QUAD $0x5362c5fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f0fe484dd162c2; QUAD $0xf16202c372486df1; QUAD $0x1df1620dc3724825; QUAD $0x487e716216c37248; QUAD $0xcd25485d7362cb6f; QUAD $0x96d4254825d362e8; QUAD $0xd162d1fe486dd162; QUAD $0x487e7162d0fe486d; WORD $0x626f; BYTE $0x36
+ TESTQ $(1<<5), R14
+ JE skipNext5
+ MOVQ 5*24(AX), R9
+ LONG $0x487cc162; WORD $0x2c10; BYTE $0x09
+
+skipNext5:
+ QUAD $0x7162c4fe48755162; QUAD $0x482df162ce6f487e; QUAD $0x724825f16206c672; QUAD $0xc672481df1620bc6; QUAD $0xcac8254845736219; QUAD $0x5362c6fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62e8fe4855d162c2; QUAD $0xf16202c2724875f1; QUAD $0x1df1620dc2724825; QUAD $0x487e716216c27248; QUAD $0xcc2548657362ca6f; QUAD $0x96cc254825d362e8; QUAD $0xd162c9fe4875d162; QUAD $0x487e7162c8fe4875; WORD $0x626f; BYTE $0x37
+ TESTQ $(1<<6), R14
+ JE skipNext6
+ MOVQ 6*24(AX), R9
+ LONG $0x487cc162; WORD $0x3410; BYTE $0x09
+
+skipNext6:
+ QUAD $0x7162c4fe487d5162; QUAD $0x482df162cd6f487e; QUAD $0x724825f16206c572; QUAD $0xc572481df1620bc5; QUAD $0xcacf25484d736219; QUAD $0x5362c7fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62e0fe485dd162c2; QUAD $0xf16202c172487df1; QUAD $0x1df1620dc1724825; QUAD $0x487e716216c17248; QUAD $0xcb25486d7362c96f; QUAD $0x96c4254825d362e8; QUAD $0xd162c1fe487dd162; QUAD $0x487e7162c0fe487d; WORD $0x626f; BYTE $0x38
+ TESTQ $(1<<7), R14
+ JE skipNext7
+ MOVQ 7*24(AX), R9
+ LONG $0x487cc162; WORD $0x3c10; BYTE $0x09
+
+skipNext7:
+ QUAD $0x7162c4fe48455162; QUAD $0x482df162cc6f487e; QUAD $0x724825f16206c472; QUAD $0xc472481df1620bc4; QUAD $0xcace254855736219; QUAD $0x5362c0fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62d8fe4865d162c2; QUAD $0xf16202c0724845f1; QUAD $0x1df1620dc0724825; QUAD $0x487e716216c07248; QUAD $0xca2548757362c86f; QUAD $0x96fc254825d362e8; QUAD $0xd162f9fe4845d162; QUAD $0x487e7162f8fe4845; WORD $0x626f; BYTE $0x39
+ TESTQ $(1<<8), R14
+ JE skipNext8
+ MOVQ 8*24(AX), R9
+ LONG $0x487c4162; WORD $0x0410; BYTE $0x09
+
+skipNext8:
+ QUAD $0x7162c4fe484d5162; QUAD $0x482df162cb6f487e; QUAD $0x724825f16206c372; QUAD $0xc372481df1620bc3; QUAD $0xcacd25485d736219; QUAD $0x5362c1fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62d0fe486dd162c2; QUAD $0xf16202c772484df1; QUAD $0x1df1620dc7724825; QUAD $0x487e716216c77248; QUAD $0xc925487d7362cf6f; QUAD $0x96f4254825d362e8; QUAD $0xd162f1fe484dd162; QUAD $0x487e7162f0fe484d; WORD $0x626f; BYTE $0x3a
+ TESTQ $(1<<9), R14
+ JE skipNext9
+ MOVQ 9*24(AX), R9
+ LONG $0x487c4162; WORD $0x0c10; BYTE $0x09
+
+skipNext9:
+ QUAD $0x7162c4fe48555162; QUAD $0x482df162ca6f487e; QUAD $0x724825f16206c272; QUAD $0xc272481df1620bc2; QUAD $0xcacc254865736219; QUAD $0x5362c2fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62c8fe4875d162c2; QUAD $0xf16202c6724855f1; QUAD $0x1df1620dc6724825; QUAD $0x487e716216c67248; QUAD $0xc82548457362ce6f; QUAD $0x96ec254825d362e8; QUAD $0xd162e9fe4855d162; QUAD $0x487e7162e8fe4855; WORD $0x626f; BYTE $0x3b
+ TESTQ $(1<<10), R14
+ JE skipNext10
+ MOVQ 10*24(AX), R9
+ LONG $0x487c4162; WORD $0x1410; BYTE $0x09
+
+skipNext10:
+ QUAD $0x7162c4fe485d5162; QUAD $0x482df162c96f487e; QUAD $0x724825f16206c172; QUAD $0xc172481df1620bc1; QUAD $0xcacb25486d736219; QUAD $0x5362c3fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62c0fe487dd162c2; QUAD $0xf16202c572485df1; QUAD $0x1df1620dc5724825; QUAD $0x487e716216c57248; QUAD $0xcf25484d7362cd6f; QUAD $0x96e4254825d362e8; QUAD $0xd162e1fe485dd162; QUAD $0x487e7162e0fe485d; WORD $0x626f; BYTE $0x3c
+ TESTQ $(1<<11), R14
+ JE skipNext11
+ MOVQ 11*24(AX), R9
+ LONG $0x487c4162; WORD $0x1c10; BYTE $0x09
+
+skipNext11:
+ QUAD $0x7162c4fe48655162; QUAD $0x482df162c86f487e; QUAD $0x724825f16206c072; QUAD $0xc072481df1620bc0; QUAD $0xcaca254875736219; QUAD $0x5362c4fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f8fe4845d162c2; QUAD $0xf16202c4724865f1; QUAD $0x1df1620dc4724825; QUAD $0x487e716216c47248; QUAD $0xce2548557362cc6f; QUAD $0x96dc254825d362e8; QUAD $0xd162d9fe4865d162; QUAD $0x487e7162d8fe4865; WORD $0x626f; BYTE $0x3d
+ TESTQ $(1<<12), R14
+ JE skipNext12
+ MOVQ 12*24(AX), R9
+ LONG $0x487c4162; WORD $0x2410; BYTE $0x09
+
+skipNext12:
+ QUAD $0x7162c4fe486d5162; QUAD $0x482df162cf6f487e; QUAD $0x724825f16206c772; QUAD $0xc772481df1620bc7; QUAD $0xcac925487d736219; QUAD $0x5362c5fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62f0fe484dd162c2; QUAD $0xf16202c372486df1; QUAD $0x1df1620dc3724825; QUAD $0x487e716216c37248; QUAD $0xcd25485d7362cb6f; QUAD $0x96d4254825d362e8; QUAD $0xd162d1fe486dd162; QUAD $0x487e7162d0fe486d; WORD $0x626f; BYTE $0x3e
+ TESTQ $(1<<13), R14
+ JE skipNext13
+ MOVQ 13*24(AX), R9
+ LONG $0x487c4162; WORD $0x2c10; BYTE $0x09
+
+skipNext13:
+ QUAD $0x7162c4fe48755162; QUAD $0x482df162ce6f487e; QUAD $0x724825f16206c672; QUAD $0xc672481df1620bc6; QUAD $0xcac8254845736219; QUAD $0x5362c6fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62e8fe4855d162c2; QUAD $0xf16202c2724875f1; QUAD $0x1df1620dc2724825; QUAD $0x487e716216c27248; QUAD $0xcc2548657362ca6f; QUAD $0x96cc254825d362e8; QUAD $0xd162c9fe4875d162; QUAD $0x487e7162c8fe4875; WORD $0x626f; BYTE $0x3f
+ TESTQ $(1<<14), R14
+ JE skipNext14
+ MOVQ 14*24(AX), R9
+ LONG $0x487c4162; WORD $0x3410; BYTE $0x09
+
+skipNext14:
+ QUAD $0x7162c4fe487d5162; QUAD $0x482df162cd6f487e; QUAD $0x724825f16206c572; QUAD $0xc572481df1620bc5; QUAD $0xcacf25484d736219; QUAD $0x5362c7fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62e0fe485dd162c2; QUAD $0xf16202c172487df1; QUAD $0x1df1620dc1724825; QUAD $0x487e716216c17248; QUAD $0xcb25486d7362c96f; QUAD $0x96c4254825d362e8; QUAD $0xd162c1fe487dd162; QUAD $0x487e7162c0fe487d; WORD $0x626f; BYTE $0x40
+ TESTQ $(1<<15), R14
+ JE skipNext15
+ MOVQ 15*24(AX), R9
+ LONG $0x487c4162; WORD $0x3c10; BYTE $0x09
+
+skipNext15:
+ QUAD $0xd162d86f487e7162; QUAD $0x7dd16224046f487e; QUAD $0x6f487e7162c3fe49; QUAD $0x244c6f487ed162d9; QUAD $0x62cbfe4975d16201; QUAD $0x7ed162da6f487e71; QUAD $0x6dd1620224546f48; QUAD $0x6f487e7162d3fe49; QUAD $0x245c6f487ed162db; QUAD $0x62dbfe4965d16203; QUAD $0x7ed162dc6f487e71; QUAD $0x5dd1620424646f48; QUAD $0x6f487e7162e3fe49; QUAD $0x246c6f487ed162dd; QUAD $0x62ebfe4955d16205; QUAD $0x7ed162de6f487e71; QUAD $0x4dd1620624746f48; QUAD $0x6f487e7162f3fe49; QUAD $0x247c6f487ed162df; QUAD $0xc4fbfe4945d16207; LONG $0xce92fbc1
+ JMP lloop
+
+lastLoop:
+ QUAD $0x7162c4fe48455162; QUAD $0x482df162cc6f487e; QUAD $0x724825f16206c472; QUAD $0xc472481df1620bc4; QUAD $0xcace254855736219; QUAD $0x5362c0fe483d3162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62d8fe4865d162c2; QUAD $0xf16202c0724845f1; QUAD $0x1df1620dc0724825; QUAD $0x487e716216c07248; QUAD $0xca2548757362c86f; QUAD $0x96fc254825d362e8; QUAD $0xd162f9fe4845d162; QUAD $0x487e7162f8fe4845; QUAD $0xfe484d516231626f; QUAD $0x62cb6f487e7162c4; QUAD $0xf16206c372482df1; QUAD $0x1df1620bc3724825; QUAD $0x485d736219c37248; QUAD $0xfe483d3162cacd25; QUAD $0x96d42548255362c1; QUAD $0x5162c1fe483d5162; QUAD $0x486dd162c2fe483d; QUAD $0xc772484df162d0fe; QUAD $0x0dc7724825f16202; QUAD $0x6216c772481df162; QUAD $0x7d7362cf6f487e71; QUAD $0x4825d362e8c92548; QUAD $0xfe484dd16296f425; QUAD $0x62f0fe484dd162f1; QUAD $0x516232626f487e71; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x3162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x62c4fe485d516233; QUAD $0x2df162c96f487e71; QUAD $0x4825f16206c17248; QUAD $0x72481df1620bc172; QUAD $0xcb25486d736219c1; QUAD $0x62c3fe483d3162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xc0fe487dd162c2fe; QUAD $0x6202c572485df162; QUAD $0xf1620dc5724825f1; QUAD $0x7e716216c572481d; QUAD $0x25484d7362cd6f48; QUAD $0xe4254825d362e8cf; QUAD $0x62e1fe485dd16296; QUAD $0x7e7162e0fe485dd1; QUAD $0x4865516234626f48; QUAD $0xc86f487e7162c4fe; QUAD $0x6206c072482df162; QUAD $0xf1620bc0724825f1; QUAD $0x75736219c072481d; QUAD $0x483d3162caca2548; QUAD $0xd42548255362c4fe; QUAD $0x62c1fe483d516296; QUAD $0x45d162c2fe483d51; QUAD $0x724865f162f8fe48; QUAD $0xc4724825f16202c4; QUAD $0x16c472481df1620d; QUAD $0x7362cc6f487e7162; QUAD $0x25d362e8ce254855; QUAD $0x4865d16296dc2548; QUAD $0xd8fe4865d162d9fe; QUAD $0x6235626f487e7162; QUAD $0x7e7162c4fe486d51; QUAD $0x72482df162cf6f48; QUAD $0xc7724825f16206c7; QUAD $0x19c772481df1620b; QUAD $0x62cac925487d7362; QUAD $0x255362c5fe483d31; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162f0fe484dd162; QUAD $0x25f16202c372486d; QUAD $0x481df1620dc37248; QUAD $0x6f487e716216c372; QUAD $0xe8cd25485d7362cb; QUAD $0x6296d4254825d362; QUAD $0x6dd162d1fe486dd1; QUAD $0x6f487e7162d0fe48; QUAD $0xc4fe487551623662; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d3162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x7d516237626f487e; QUAD $0x6f487e7162c4fe48; QUAD $0x06c572482df162cd; QUAD $0x620bc5724825f162; QUAD $0x736219c572481df1; QUAD $0x3d3162cacf25484d; QUAD $0x2548255362c7fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x487df162e0fe485d; QUAD $0x724825f16202c172; QUAD $0xc172481df1620dc1; QUAD $0x62c96f487e716216; QUAD $0xd362e8cb25486d73; QUAD $0x7dd16296c4254825; QUAD $0xfe487dd162c1fe48; QUAD $0x38626f487e7162c0; QUAD $0x7162c4fe48455162; QUAD $0x482df162cc6f487e; QUAD $0x724825f16206c472; QUAD $0xc472481df1620bc4; QUAD $0xcace254855736219; QUAD $0x5362c0fe483d1162; QUAD $0x3d516296d4254825; QUAD $0xfe483d5162c1fe48; QUAD $0x62d8fe4865d162c2; QUAD $0xf16202c0724845f1; QUAD $0x1df1620dc0724825; QUAD $0x487e716216c07248; QUAD $0xca2548757362c86f; QUAD $0x96fc254825d362e8; QUAD $0xd162f9fe4845d162; QUAD $0x487e7162f8fe4845; QUAD $0xfe484d516239626f; QUAD $0x62cb6f487e7162c4; QUAD $0xf16206c372482df1; QUAD $0x1df1620bc3724825; QUAD $0x485d736219c37248; QUAD $0xfe483d1162cacd25; QUAD $0x96d42548255362c1; QUAD $0x5162c1fe483d5162; QUAD $0x486dd162c2fe483d; QUAD $0xc772484df162d0fe; QUAD $0x0dc7724825f16202; QUAD $0x6216c772481df162; QUAD $0x7d7362cf6f487e71; QUAD $0x4825d362e8c92548; QUAD $0xfe484dd16296f425; QUAD $0x62f0fe484dd162f1; QUAD $0x51623a626f487e71; QUAD $0x487e7162c4fe4855; QUAD $0xc272482df162ca6f; QUAD $0x0bc2724825f16206; QUAD $0x6219c272481df162; QUAD $0x1162cacc25486573; QUAD $0x48255362c2fe483d; QUAD $0xfe483d516296d425; QUAD $0x62c2fe483d5162c1; QUAD $0x55f162c8fe4875d1; QUAD $0x4825f16202c67248; QUAD $0x72481df1620dc672; QUAD $0xce6f487e716216c6; QUAD $0x62e8c82548457362; QUAD $0xd16296ec254825d3; QUAD $0x4855d162e9fe4855; QUAD $0x626f487e7162e8fe; QUAD $0x62c4fe485d51623b; QUAD $0x2df162c96f487e71; QUAD $0x4825f16206c17248; QUAD $0x72481df1620bc172; QUAD $0xcb25486d736219c1; QUAD $0x62c3fe483d1162ca; QUAD $0x516296d425482553; QUAD $0x483d5162c1fe483d; QUAD $0xc0fe487dd162c2fe; QUAD $0x6202c572485df162; QUAD $0xf1620dc5724825f1; QUAD $0x7e716216c572481d; QUAD $0x25484d7362cd6f48; QUAD $0xe4254825d362e8cf; QUAD $0x62e1fe485dd16296; QUAD $0x7e7162e0fe485dd1; QUAD $0x486551623c626f48; QUAD $0xc86f487e7162c4fe; QUAD $0x6206c072482df162; QUAD $0xf1620bc0724825f1; QUAD $0x75736219c072481d; QUAD $0x483d1162caca2548; QUAD $0xd42548255362c4fe; QUAD $0x62c1fe483d516296; QUAD $0x45d162c2fe483d51; QUAD $0x724865f162f8fe48; QUAD $0xc4724825f16202c4; QUAD $0x16c472481df1620d; QUAD $0x7362cc6f487e7162; QUAD $0x25d362e8ce254855; QUAD $0x4865d16296dc2548; QUAD $0xd8fe4865d162d9fe; QUAD $0x623d626f487e7162; QUAD $0x7e7162c4fe486d51; QUAD $0x72482df162cf6f48; QUAD $0xc7724825f16206c7; QUAD $0x19c772481df1620b; QUAD $0x62cac925487d7362; QUAD $0x255362c5fe483d11; QUAD $0x483d516296d42548; QUAD $0xc2fe483d5162c1fe; QUAD $0xf162f0fe484dd162; QUAD $0x25f16202c372486d; QUAD $0x481df1620dc37248; QUAD $0x6f487e716216c372; QUAD $0xe8cd25485d7362cb; QUAD $0x6296d4254825d362; QUAD $0x6dd162d1fe486dd1; QUAD $0x6f487e7162d0fe48; QUAD $0xc4fe487551623e62; QUAD $0xf162ce6f487e7162; QUAD $0x25f16206c672482d; QUAD $0x481df1620bc67248; QUAD $0x254845736219c672; QUAD $0xc6fe483d1162cac8; QUAD $0x6296d42548255362; QUAD $0x3d5162c1fe483d51; QUAD $0xfe4855d162c2fe48; QUAD $0x02c2724875f162e8; QUAD $0x620dc2724825f162; QUAD $0x716216c272481df1; QUAD $0x48657362ca6f487e; QUAD $0x254825d362e8cc25; QUAD $0xc9fe4875d16296cc; QUAD $0x7162c8fe4875d162; QUAD $0x7d51623f626f487e; QUAD $0x6f487e7162c4fe48; QUAD $0x06c572482df162cd; QUAD $0x620bc5724825f162; QUAD $0x736219c572481df1; QUAD $0x3d1162cacf25484d; QUAD $0x2548255362c7fe48; QUAD $0xc1fe483d516296d4; QUAD $0xd162c2fe483d5162; QUAD $0x487df162e0fe485d; QUAD $0x724825f16202c172; QUAD $0xc172481df1620dc1; QUAD $0x62c96f487e716216; QUAD $0xd362e8cb25486d73; QUAD $0x7dd16296c4254825; QUAD $0xfe487dd162c1fe48; QUAD $0x40626f487e7162c0; QUAD $0xd162d86f487e7162; QUAD $0x7dd16224046f487e; QUAD $0x6f487e7162c3fe49; QUAD $0x244c6f487ed162d9; QUAD $0x62cbfe4975d16201; QUAD $0x7ed162da6f487e71; QUAD $0x6dd1620224546f48; QUAD $0x6f487e7162d3fe49; QUAD $0x245c6f487ed162db; QUAD $0x62dbfe4965d16203; QUAD $0x7ed162dc6f487e71; QUAD $0x5dd1620424646f48; QUAD $0x6f487e7162e3fe49; QUAD $0x246c6f487ed162dd; QUAD $0x62ebfe4955d16205; QUAD $0x7ed162de6f487e71; QUAD $0x4dd1620624746f48; QUAD $0x6f487e7162f3fe49; QUAD $0x247c6f487ed162df; QUAD $0x62fbfe4945d16207; QUAD $0x7ef162077f487ef1; QUAD $0x487ef162014f7f48; QUAD $0x7f487ef16202577f; QUAD $0x677f487ef162035f; QUAD $0x056f7f487ef16204; QUAD $0x6206777f487ef162; LONG $0x7f487ef1; WORD $0x077f
+ VZEROUPPER
+ RET
+
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x000(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x008(SB)/8, $0x0c0d0e0f08090a0b
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x010(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x018(SB)/8, $0x0c0d0e0f08090a0b
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x020(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x028(SB)/8, $0x0c0d0e0f08090a0b
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x030(SB)/8, $0x0405060700010203
+DATA PSHUFFLE_BYTE_FLIP_MASK<>+0x038(SB)/8, $0x0c0d0e0f08090a0b
+GLOBL PSHUFFLE_BYTE_FLIP_MASK<>(SB), 8, $64
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x000(SB)/8, $0x0000000000000000
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x008(SB)/8, $0x0000000000000001
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x010(SB)/8, $0x0000000000000008
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x018(SB)/8, $0x0000000000000009
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x020(SB)/8, $0x0000000000000004
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x028(SB)/8, $0x0000000000000005
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x030(SB)/8, $0x000000000000000C
+DATA PSHUFFLE_TRANSPOSE16_MASK1<>+0x038(SB)/8, $0x000000000000000D
+GLOBL PSHUFFLE_TRANSPOSE16_MASK1<>(SB), 8, $64
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x000(SB)/8, $0x0000000000000002
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x008(SB)/8, $0x0000000000000003
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x010(SB)/8, $0x000000000000000A
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x018(SB)/8, $0x000000000000000B
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x020(SB)/8, $0x0000000000000006
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x028(SB)/8, $0x0000000000000007
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x030(SB)/8, $0x000000000000000E
+DATA PSHUFFLE_TRANSPOSE16_MASK2<>+0x038(SB)/8, $0x000000000000000F
+GLOBL PSHUFFLE_TRANSPOSE16_MASK2<>(SB), 8, $64
diff --git a/vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.go b/vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.go
new file mode 100644
index 00000000..bef94941
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.go
@@ -0,0 +1,6 @@
+//+build !noasm,!appengine,gc
+
+package sha256
+
+//go:noescape
+func blockSha(h *[8]uint32, message []uint8)
diff --git a/vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.s b/vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.s
new file mode 100644
index 00000000..909fc0ef
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256blockSha_amd64.s
@@ -0,0 +1,266 @@
+//+build !noasm,!appengine
+
+// SHA intrinsic version of SHA256
+
+// Kristofer Peterson, (C) 2018.
+//
+// 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.
+//
+
+#include "textflag.h"
+
+DATA K<>+0x00(SB)/4, $0x428a2f98
+DATA K<>+0x04(SB)/4, $0x71374491
+DATA K<>+0x08(SB)/4, $0xb5c0fbcf
+DATA K<>+0x0c(SB)/4, $0xe9b5dba5
+DATA K<>+0x10(SB)/4, $0x3956c25b
+DATA K<>+0x14(SB)/4, $0x59f111f1
+DATA K<>+0x18(SB)/4, $0x923f82a4
+DATA K<>+0x1c(SB)/4, $0xab1c5ed5
+DATA K<>+0x20(SB)/4, $0xd807aa98
+DATA K<>+0x24(SB)/4, $0x12835b01
+DATA K<>+0x28(SB)/4, $0x243185be
+DATA K<>+0x2c(SB)/4, $0x550c7dc3
+DATA K<>+0x30(SB)/4, $0x72be5d74
+DATA K<>+0x34(SB)/4, $0x80deb1fe
+DATA K<>+0x38(SB)/4, $0x9bdc06a7
+DATA K<>+0x3c(SB)/4, $0xc19bf174
+DATA K<>+0x40(SB)/4, $0xe49b69c1
+DATA K<>+0x44(SB)/4, $0xefbe4786
+DATA K<>+0x48(SB)/4, $0x0fc19dc6
+DATA K<>+0x4c(SB)/4, $0x240ca1cc
+DATA K<>+0x50(SB)/4, $0x2de92c6f
+DATA K<>+0x54(SB)/4, $0x4a7484aa
+DATA K<>+0x58(SB)/4, $0x5cb0a9dc
+DATA K<>+0x5c(SB)/4, $0x76f988da
+DATA K<>+0x60(SB)/4, $0x983e5152
+DATA K<>+0x64(SB)/4, $0xa831c66d
+DATA K<>+0x68(SB)/4, $0xb00327c8
+DATA K<>+0x6c(SB)/4, $0xbf597fc7
+DATA K<>+0x70(SB)/4, $0xc6e00bf3
+DATA K<>+0x74(SB)/4, $0xd5a79147
+DATA K<>+0x78(SB)/4, $0x06ca6351
+DATA K<>+0x7c(SB)/4, $0x14292967
+DATA K<>+0x80(SB)/4, $0x27b70a85
+DATA K<>+0x84(SB)/4, $0x2e1b2138
+DATA K<>+0x88(SB)/4, $0x4d2c6dfc
+DATA K<>+0x8c(SB)/4, $0x53380d13
+DATA K<>+0x90(SB)/4, $0x650a7354
+DATA K<>+0x94(SB)/4, $0x766a0abb
+DATA K<>+0x98(SB)/4, $0x81c2c92e
+DATA K<>+0x9c(SB)/4, $0x92722c85
+DATA K<>+0xa0(SB)/4, $0xa2bfe8a1
+DATA K<>+0xa4(SB)/4, $0xa81a664b
+DATA K<>+0xa8(SB)/4, $0xc24b8b70
+DATA K<>+0xac(SB)/4, $0xc76c51a3
+DATA K<>+0xb0(SB)/4, $0xd192e819
+DATA K<>+0xb4(SB)/4, $0xd6990624
+DATA K<>+0xb8(SB)/4, $0xf40e3585
+DATA K<>+0xbc(SB)/4, $0x106aa070
+DATA K<>+0xc0(SB)/4, $0x19a4c116
+DATA K<>+0xc4(SB)/4, $0x1e376c08
+DATA K<>+0xc8(SB)/4, $0x2748774c
+DATA K<>+0xcc(SB)/4, $0x34b0bcb5
+DATA K<>+0xd0(SB)/4, $0x391c0cb3
+DATA K<>+0xd4(SB)/4, $0x4ed8aa4a
+DATA K<>+0xd8(SB)/4, $0x5b9cca4f
+DATA K<>+0xdc(SB)/4, $0x682e6ff3
+DATA K<>+0xe0(SB)/4, $0x748f82ee
+DATA K<>+0xe4(SB)/4, $0x78a5636f
+DATA K<>+0xe8(SB)/4, $0x84c87814
+DATA K<>+0xec(SB)/4, $0x8cc70208
+DATA K<>+0xf0(SB)/4, $0x90befffa
+DATA K<>+0xf4(SB)/4, $0xa4506ceb
+DATA K<>+0xf8(SB)/4, $0xbef9a3f7
+DATA K<>+0xfc(SB)/4, $0xc67178f2
+GLOBL K<>(SB), RODATA|NOPTR, $256
+
+DATA SHUF_MASK<>+0x00(SB)/8, $0x0405060700010203
+DATA SHUF_MASK<>+0x08(SB)/8, $0x0c0d0e0f08090a0b
+GLOBL SHUF_MASK<>(SB), RODATA|NOPTR, $16
+
+// Register Usage
+// BX base address of constant table (constant)
+// DX hash_state (constant)
+// SI hash_data.data
+// DI hash_data.data + hash_data.length - 64 (constant)
+// X0 scratch
+// X1 scratch
+// X2 working hash state // ABEF
+// X3 working hash state // CDGH
+// X4 first 16 bytes of block
+// X5 second 16 bytes of block
+// X6 third 16 bytes of block
+// X7 fourth 16 bytes of block
+// X12 saved hash state // ABEF
+// X13 saved hash state // CDGH
+// X15 data shuffle mask (constant)
+
+TEXT ยทblockSha(SB), NOSPLIT, $0-32
+ MOVQ h+0(FP), DX
+ MOVQ message_base+8(FP), SI
+ MOVQ message_len+16(FP), DI
+ LEAQ -64(SI)(DI*1), DI
+ MOVOU (DX), X2
+ MOVOU 16(DX), X1
+ MOVO X2, X3
+ PUNPCKLLQ X1, X2
+ PUNPCKHLQ X1, X3
+ PSHUFD $0x27, X2, X2
+ PSHUFD $0x27, X3, X3
+ MOVO SHUF_MASK<>(SB), X15
+ LEAQ K<>(SB), BX
+
+ JMP TEST
+
+LOOP:
+ MOVO X2, X12
+ MOVO X3, X13
+
+ // load block and shuffle
+ MOVOU (SI), X4
+ MOVOU 16(SI), X5
+ MOVOU 32(SI), X6
+ MOVOU 48(SI), X7
+ PSHUFB X15, X4
+ PSHUFB X15, X5
+ PSHUFB X15, X6
+ PSHUFB X15, X7
+
+#define ROUND456 \
+ PADDL X5, X0 \
+ LONG $0xdacb380f \ // SHA256RNDS2 XMM3, XMM2
+ MOVO X5, X1 \
+ LONG $0x0f3a0f66; WORD $0x04cc \ // PALIGNR XMM1, XMM4, 4
+ PADDL X1, X6 \
+ LONG $0xf5cd380f \ // SHA256MSG2 XMM6, XMM5
+ PSHUFD $0x4e, X0, X0 \
+ LONG $0xd3cb380f \ // SHA256RNDS2 XMM2, XMM3
+ LONG $0xe5cc380f // SHA256MSG1 XMM4, XMM5
+
+#define ROUND567 \
+ PADDL X6, X0 \
+ LONG $0xdacb380f \ // SHA256RNDS2 XMM3, XMM2
+ MOVO X6, X1 \
+ LONG $0x0f3a0f66; WORD $0x04cd \ // PALIGNR XMM1, XMM5, 4
+ PADDL X1, X7 \
+ LONG $0xfecd380f \ // SHA256MSG2 XMM7, XMM6
+ PSHUFD $0x4e, X0, X0 \
+ LONG $0xd3cb380f \ // SHA256RNDS2 XMM2, XMM3
+ LONG $0xeecc380f // SHA256MSG1 XMM5, XMM6
+
+#define ROUND674 \
+ PADDL X7, X0 \
+ LONG $0xdacb380f \ // SHA256RNDS2 XMM3, XMM2
+ MOVO X7, X1 \
+ LONG $0x0f3a0f66; WORD $0x04ce \ // PALIGNR XMM1, XMM6, 4
+ PADDL X1, X4 \
+ LONG $0xe7cd380f \ // SHA256MSG2 XMM4, XMM7
+ PSHUFD $0x4e, X0, X0 \
+ LONG $0xd3cb380f \ // SHA256RNDS2 XMM2, XMM3
+ LONG $0xf7cc380f // SHA256MSG1 XMM6, XMM7
+
+#define ROUND745 \
+ PADDL X4, X0 \
+ LONG $0xdacb380f \ // SHA256RNDS2 XMM3, XMM2
+ MOVO X4, X1 \
+ LONG $0x0f3a0f66; WORD $0x04cf \ // PALIGNR XMM1, XMM7, 4
+ PADDL X1, X5 \
+ LONG $0xeccd380f \ // SHA256MSG2 XMM5, XMM4
+ PSHUFD $0x4e, X0, X0 \
+ LONG $0xd3cb380f \ // SHA256RNDS2 XMM2, XMM3
+ LONG $0xfccc380f // SHA256MSG1 XMM7, XMM4
+
+ // rounds 0-3
+ MOVO (BX), X0
+ PADDL X4, X0
+ LONG $0xdacb380f // SHA256RNDS2 XMM3, XMM2
+ PSHUFD $0x4e, X0, X0
+ LONG $0xd3cb380f // SHA256RNDS2 XMM2, XMM3
+
+ // rounds 4-7
+ MOVO 1*16(BX), X0
+ PADDL X5, X0
+ LONG $0xdacb380f // SHA256RNDS2 XMM3, XMM2
+ PSHUFD $0x4e, X0, X0
+ LONG $0xd3cb380f // SHA256RNDS2 XMM2, XMM3
+ LONG $0xe5cc380f // SHA256MSG1 XMM4, XMM5
+
+ // rounds 8-11
+ MOVO 2*16(BX), X0
+ PADDL X6, X0
+ LONG $0xdacb380f // SHA256RNDS2 XMM3, XMM2
+ PSHUFD $0x4e, X0, X0
+ LONG $0xd3cb380f // SHA256RNDS2 XMM2, XMM3
+ LONG $0xeecc380f // SHA256MSG1 XMM5, XMM6
+
+ MOVO 3*16(BX), X0; ROUND674 // rounds 12-15
+ MOVO 4*16(BX), X0; ROUND745 // rounds 16-19
+ MOVO 5*16(BX), X0; ROUND456 // rounds 20-23
+ MOVO 6*16(BX), X0; ROUND567 // rounds 24-27
+ MOVO 7*16(BX), X0; ROUND674 // rounds 28-31
+ MOVO 8*16(BX), X0; ROUND745 // rounds 32-35
+ MOVO 9*16(BX), X0; ROUND456 // rounds 36-39
+ MOVO 10*16(BX), X0; ROUND567 // rounds 40-43
+ MOVO 11*16(BX), X0; ROUND674 // rounds 44-47
+ MOVO 12*16(BX), X0; ROUND745 // rounds 48-51
+
+ // rounds 52-55
+ MOVO 13*16(BX), X0
+ PADDL X5, X0
+ LONG $0xdacb380f // SHA256RNDS2 XMM3, XMM2
+ MOVO X5, X1
+ LONG $0x0f3a0f66; WORD $0x04cc // PALIGNR XMM1, XMM4, 4
+ PADDL X1, X6
+ LONG $0xf5cd380f // SHA256MSG2 XMM6, XMM5
+ PSHUFD $0x4e, X0, X0
+ LONG $0xd3cb380f // SHA256RNDS2 XMM2, XMM3
+
+ // rounds 56-59
+ MOVO 14*16(BX), X0
+ PADDL X6, X0
+ LONG $0xdacb380f // SHA256RNDS2 XMM3, XMM2
+ MOVO X6, X1
+ LONG $0x0f3a0f66; WORD $0x04cd // PALIGNR XMM1, XMM5, 4
+ PADDL X1, X7
+ LONG $0xfecd380f // SHA256MSG2 XMM7, XMM6
+ PSHUFD $0x4e, X0, X0
+ LONG $0xd3cb380f // SHA256RNDS2 XMM2, XMM3
+
+ // rounds 60-63
+ MOVO 15*16(BX), X0
+ PADDL X7, X0
+ LONG $0xdacb380f // SHA256RNDS2 XMM3, XMM2
+ PSHUFD $0x4e, X0, X0
+ LONG $0xd3cb380f // SHA256RNDS2 XMM2, XMM3
+
+ PADDL X12, X2
+ PADDL X13, X3
+
+ ADDQ $64, SI
+
+TEST:
+ CMPQ SI, DI
+ JBE LOOP
+
+ PSHUFD $0x4e, X3, X0
+ LONG $0x0e3a0f66; WORD $0xf0c2 // PBLENDW XMM0, XMM2, 0xf0
+ PSHUFD $0x4e, X2, X1
+ LONG $0x0e3a0f66; WORD $0x0fcb // PBLENDW XMM1, XMM3, 0x0f
+ PSHUFD $0x1b, X0, X0
+ PSHUFD $0x1b, X1, X1
+
+ MOVOU X0, (DX)
+ MOVOU X1, 16(DX)
+
+ RET
diff --git a/vendor/github.com/minio/sha256-simd/sha256block_amd64.go b/vendor/github.com/minio/sha256-simd/sha256block_amd64.go
new file mode 100644
index 00000000..0c48d45f
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256block_amd64.go
@@ -0,0 +1,27 @@
+//+build !noasm,!appengine,gc
+
+/*
+ * Minio Cloud Storage, (C) 2016 Minio, 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.
+ */
+
+package sha256
+
+func blockArmGo(dig *digest, p []byte) {
+ panic("blockArmGo called unexpectedly")
+}
+
+func blockShaGo(dig *digest, p []byte) {
+ blockSha(&dig.h, p)
+}
diff --git a/vendor/github.com/minio/sha256-simd/sha256block_arm64.go b/vendor/github.com/minio/sha256-simd/sha256block_arm64.go
new file mode 100644
index 00000000..58ccf6eb
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256block_arm64.go
@@ -0,0 +1,36 @@
+//+build !noasm,!appengine,gc
+
+/*
+ * Minio Cloud Storage, (C) 2016 Minio, 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.
+ */
+
+package sha256
+
+func blockShaGo(dig *digest, p []byte) {
+ panic("blockShaGoc called unexpectedly")
+}
+
+//go:noescape
+func blockArm(h []uint32, message []uint8)
+
+func blockArmGo(dig *digest, p []byte) {
+
+ h := []uint32{dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7]}
+
+ blockArm(h[:], p[:])
+
+ dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h[0], h[1], h[2], h[3], h[4],
+ h[5], h[6], h[7]
+}
diff --git a/vendor/github.com/minio/sha256-simd/sha256block_arm64.s b/vendor/github.com/minio/sha256-simd/sha256block_arm64.s
new file mode 100644
index 00000000..c6ddb371
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256block_arm64.s
@@ -0,0 +1,192 @@
+//+build !noasm,!appengine
+
+// ARM64 version of SHA256
+
+//
+// Minio Cloud Storage, (C) 2016 Minio, 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.
+//
+
+//
+// Based on implementation as found in https://github.com/jocover/sha256-armv8
+//
+// Use github.com/minio/asm2plan9s on this file to assemble ARM instructions to
+// their Plan9 equivalents
+//
+
+TEXT ยทblockArm(SB), 7, $0
+ MOVD h+0(FP), R0
+ MOVD message+24(FP), R1
+ MOVD message_len+32(FP), R2 // length of message
+ SUBS $64, R2
+ BMI complete
+
+ // Load constants table pointer
+ MOVD $ยทconstants(SB), R3
+
+ // Cache constants table in registers v16 - v31
+ WORD $0x4cdf2870 // ld1 {v16.4s-v19.4s}, [x3], #64
+ WORD $0x4cdf7800 // ld1 {v0.4s}, [x0], #16
+ WORD $0x4cdf2874 // ld1 {v20.4s-v23.4s}, [x3], #64
+
+ WORD $0x4c407801 // ld1 {v1.4s}, [x0]
+ WORD $0x4cdf2878 // ld1 {v24.4s-v27.4s}, [x3], #64
+ WORD $0xd1004000 // sub x0, x0, #0x10
+ WORD $0x4cdf287c // ld1 {v28.4s-v31.4s}, [x3], #64
+
+loop:
+ // Main loop
+ WORD $0x4cdf2025 // ld1 {v5.16b-v8.16b}, [x1], #64
+ WORD $0x4ea01c02 // mov v2.16b, v0.16b
+ WORD $0x4ea11c23 // mov v3.16b, v1.16b
+ WORD $0x6e2008a5 // rev32 v5.16b, v5.16b
+ WORD $0x6e2008c6 // rev32 v6.16b, v6.16b
+ WORD $0x4eb084a9 // add v9.4s, v5.4s, v16.4s
+ WORD $0x6e2008e7 // rev32 v7.16b, v7.16b
+ WORD $0x4eb184ca // add v10.4s, v6.4s, v17.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e2828c5 // sha256su0 v5.4s, v6.4s
+ WORD $0x6e200908 // rev32 v8.16b, v8.16b
+ WORD $0x4eb284e9 // add v9.4s, v7.4s, v18.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x5e2828e6 // sha256su0 v6.4s, v7.4s
+ WORD $0x5e0860e5 // sha256su1 v5.4s, v7.4s, v8.4s
+ WORD $0x4eb3850a // add v10.4s, v8.4s, v19.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e282907 // sha256su0 v7.4s, v8.4s
+ WORD $0x5e056106 // sha256su1 v6.4s, v8.4s, v5.4s
+ WORD $0x4eb484a9 // add v9.4s, v5.4s, v20.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x5e2828a8 // sha256su0 v8.4s, v5.4s
+ WORD $0x5e0660a7 // sha256su1 v7.4s, v5.4s, v6.4s
+ WORD $0x4eb584ca // add v10.4s, v6.4s, v21.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e2828c5 // sha256su0 v5.4s, v6.4s
+ WORD $0x5e0760c8 // sha256su1 v8.4s, v6.4s, v7.4s
+ WORD $0x4eb684e9 // add v9.4s, v7.4s, v22.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x5e2828e6 // sha256su0 v6.4s, v7.4s
+ WORD $0x5e0860e5 // sha256su1 v5.4s, v7.4s, v8.4s
+ WORD $0x4eb7850a // add v10.4s, v8.4s, v23.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e282907 // sha256su0 v7.4s, v8.4s
+ WORD $0x5e056106 // sha256su1 v6.4s, v8.4s, v5.4s
+ WORD $0x4eb884a9 // add v9.4s, v5.4s, v24.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x5e2828a8 // sha256su0 v8.4s, v5.4s
+ WORD $0x5e0660a7 // sha256su1 v7.4s, v5.4s, v6.4s
+ WORD $0x4eb984ca // add v10.4s, v6.4s, v25.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e2828c5 // sha256su0 v5.4s, v6.4s
+ WORD $0x5e0760c8 // sha256su1 v8.4s, v6.4s, v7.4s
+ WORD $0x4eba84e9 // add v9.4s, v7.4s, v26.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x5e2828e6 // sha256su0 v6.4s, v7.4s
+ WORD $0x5e0860e5 // sha256su1 v5.4s, v7.4s, v8.4s
+ WORD $0x4ebb850a // add v10.4s, v8.4s, v27.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e282907 // sha256su0 v7.4s, v8.4s
+ WORD $0x5e056106 // sha256su1 v6.4s, v8.4s, v5.4s
+ WORD $0x4ebc84a9 // add v9.4s, v5.4s, v28.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x5e2828a8 // sha256su0 v8.4s, v5.4s
+ WORD $0x5e0660a7 // sha256su1 v7.4s, v5.4s, v6.4s
+ WORD $0x4ebd84ca // add v10.4s, v6.4s, v29.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x5e0760c8 // sha256su1 v8.4s, v6.4s, v7.4s
+ WORD $0x4ebe84e9 // add v9.4s, v7.4s, v30.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x4ebf850a // add v10.4s, v8.4s, v31.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e094062 // sha256h q2, q3, v9.4s
+ WORD $0x5e095083 // sha256h2 q3, q4, v9.4s
+ WORD $0x4ea21c44 // mov v4.16b, v2.16b
+ WORD $0x5e0a4062 // sha256h q2, q3, v10.4s
+ WORD $0x5e0a5083 // sha256h2 q3, q4, v10.4s
+ WORD $0x4ea38421 // add v1.4s, v1.4s, v3.4s
+ WORD $0x4ea28400 // add v0.4s, v0.4s, v2.4s
+
+ SUBS $64, R2
+ BPL loop
+
+ // Store result
+ WORD $0x4c00a800 // st1 {v0.4s, v1.4s}, [x0]
+
+complete:
+ RET
+
+// Constants table
+DATA ยทconstants+0x0(SB)/8, $0x71374491428a2f98
+DATA ยทconstants+0x8(SB)/8, $0xe9b5dba5b5c0fbcf
+DATA ยทconstants+0x10(SB)/8, $0x59f111f13956c25b
+DATA ยทconstants+0x18(SB)/8, $0xab1c5ed5923f82a4
+DATA ยทconstants+0x20(SB)/8, $0x12835b01d807aa98
+DATA ยทconstants+0x28(SB)/8, $0x550c7dc3243185be
+DATA ยทconstants+0x30(SB)/8, $0x80deb1fe72be5d74
+DATA ยทconstants+0x38(SB)/8, $0xc19bf1749bdc06a7
+DATA ยทconstants+0x40(SB)/8, $0xefbe4786e49b69c1
+DATA ยทconstants+0x48(SB)/8, $0x240ca1cc0fc19dc6
+DATA ยทconstants+0x50(SB)/8, $0x4a7484aa2de92c6f
+DATA ยทconstants+0x58(SB)/8, $0x76f988da5cb0a9dc
+DATA ยทconstants+0x60(SB)/8, $0xa831c66d983e5152
+DATA ยทconstants+0x68(SB)/8, $0xbf597fc7b00327c8
+DATA ยทconstants+0x70(SB)/8, $0xd5a79147c6e00bf3
+DATA ยทconstants+0x78(SB)/8, $0x1429296706ca6351
+DATA ยทconstants+0x80(SB)/8, $0x2e1b213827b70a85
+DATA ยทconstants+0x88(SB)/8, $0x53380d134d2c6dfc
+DATA ยทconstants+0x90(SB)/8, $0x766a0abb650a7354
+DATA ยทconstants+0x98(SB)/8, $0x92722c8581c2c92e
+DATA ยทconstants+0xa0(SB)/8, $0xa81a664ba2bfe8a1
+DATA ยทconstants+0xa8(SB)/8, $0xc76c51a3c24b8b70
+DATA ยทconstants+0xb0(SB)/8, $0xd6990624d192e819
+DATA ยทconstants+0xb8(SB)/8, $0x106aa070f40e3585
+DATA ยทconstants+0xc0(SB)/8, $0x1e376c0819a4c116
+DATA ยทconstants+0xc8(SB)/8, $0x34b0bcb52748774c
+DATA ยทconstants+0xd0(SB)/8, $0x4ed8aa4a391c0cb3
+DATA ยทconstants+0xd8(SB)/8, $0x682e6ff35b9cca4f
+DATA ยทconstants+0xe0(SB)/8, $0x78a5636f748f82ee
+DATA ยทconstants+0xe8(SB)/8, $0x8cc7020884c87814
+DATA ยทconstants+0xf0(SB)/8, $0xa4506ceb90befffa
+DATA ยทconstants+0xf8(SB)/8, $0xc67178f2bef9a3f7
+
+GLOBL ยทconstants(SB), 8, $256
+
diff --git a/vendor/github.com/minio/sha256-simd/sha256block_other.go b/vendor/github.com/minio/sha256-simd/sha256block_other.go
new file mode 100644
index 00000000..ec586c06
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/sha256block_other.go
@@ -0,0 +1,28 @@
+//+build appengine noasm !amd64,!arm64 !gc
+
+/*
+ * Minio Cloud Storage, (C) 2019 Minio, 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.
+ */
+
+package sha256
+
+func blockShaGo(dig *digest, p []byte) {
+ panic("blockShaGo called unexpectedly")
+
+}
+
+func blockArmGo(dig *digest, p []byte) {
+ panic("blockArmGo called unexpectedly")
+}
diff --git a/vendor/github.com/minio/sha256-simd/test-architectures.sh b/vendor/github.com/minio/sha256-simd/test-architectures.sh
new file mode 100644
index 00000000..50150eaa
--- /dev/null
+++ b/vendor/github.com/minio/sha256-simd/test-architectures.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+set -e
+
+go tool dist list | while IFS=/ read os arch; do
+ echo "Checking $os/$arch..."
+ echo " normal"
+ GOARCH=$arch GOOS=$os go build -o /dev/null ./...
+ echo " noasm"
+ GOARCH=$arch GOOS=$os go build -tags noasm -o /dev/null ./...
+ echo " appengine"
+ GOARCH=$arch GOOS=$os go build -tags appengine -o /dev/null ./...
+ echo " noasm,appengine"
+ GOARCH=$arch GOOS=$os go build -tags 'appengine noasm' -o /dev/null ./...
+done
diff --git a/vendor/github.com/mitchellh/go-homedir/LICENSE b/vendor/github.com/mitchellh/go-homedir/LICENSE
new file mode 100644
index 00000000..f9c841a5
--- /dev/null
+++ b/vendor/github.com/mitchellh/go-homedir/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Mitchell Hashimoto
+
+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/mitchellh/go-homedir/README.md b/vendor/github.com/mitchellh/go-homedir/README.md
new file mode 100644
index 00000000..d70706d5
--- /dev/null
+++ b/vendor/github.com/mitchellh/go-homedir/README.md
@@ -0,0 +1,14 @@
+# go-homedir
+
+This is a Go library for detecting the user's home directory without
+the use of cgo, so the library can be used in cross-compilation environments.
+
+Usage is incredibly simple, just call `homedir.Dir()` to get the home directory
+for a user, and `homedir.Expand()` to expand the `~` in a path to the home
+directory.
+
+**Why not just use `os/user`?** The built-in `os/user` package requires
+cgo on Darwin systems. This means that any Go code that uses that package
+cannot cross compile. But 99% of the time the use for `os/user` is just to
+retrieve the home directory, which we can do for the current user without
+cgo. This library does that, enabling cross-compilation.
diff --git a/vendor/github.com/mitchellh/go-homedir/go.mod b/vendor/github.com/mitchellh/go-homedir/go.mod
new file mode 100644
index 00000000..7efa09a0
--- /dev/null
+++ b/vendor/github.com/mitchellh/go-homedir/go.mod
@@ -0,0 +1 @@
+module github.com/mitchellh/go-homedir
diff --git a/vendor/github.com/mitchellh/go-homedir/homedir.go b/vendor/github.com/mitchellh/go-homedir/homedir.go
new file mode 100644
index 00000000..25378537
--- /dev/null
+++ b/vendor/github.com/mitchellh/go-homedir/homedir.go
@@ -0,0 +1,167 @@
+package homedir
+
+import (
+ "bytes"
+ "errors"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+// DisableCache will disable caching of the home directory. Caching is enabled
+// by default.
+var DisableCache bool
+
+var homedirCache string
+var cacheLock sync.RWMutex
+
+// Dir returns the home directory for the executing user.
+//
+// This uses an OS-specific method for discovering the home directory.
+// An error is returned if a home directory cannot be detected.
+func Dir() (string, error) {
+ if !DisableCache {
+ cacheLock.RLock()
+ cached := homedirCache
+ cacheLock.RUnlock()
+ if cached != "" {
+ return cached, nil
+ }
+ }
+
+ cacheLock.Lock()
+ defer cacheLock.Unlock()
+
+ var result string
+ var err error
+ if runtime.GOOS == "windows" {
+ result, err = dirWindows()
+ } else {
+ // Unix-like system, so just assume Unix
+ result, err = dirUnix()
+ }
+
+ if err != nil {
+ return "", err
+ }
+ homedirCache = result
+ return result, nil
+}
+
+// Expand expands the path to include the home directory if the path
+// is prefixed with `~`. If it isn't prefixed with `~`, the path is
+// returned as-is.
+func Expand(path string) (string, error) {
+ if len(path) == 0 {
+ return path, nil
+ }
+
+ if path[0] != '~' {
+ return path, nil
+ }
+
+ if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
+ return "", errors.New("cannot expand user-specific home dir")
+ }
+
+ dir, err := Dir()
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(dir, path[1:]), nil
+}
+
+// Reset clears the cache, forcing the next call to Dir to re-detect
+// the home directory. This generally never has to be called, but can be
+// useful in tests if you're modifying the home directory via the HOME
+// env var or something.
+func Reset() {
+ cacheLock.Lock()
+ defer cacheLock.Unlock()
+ homedirCache = ""
+}
+
+func dirUnix() (string, error) {
+ homeEnv := "HOME"
+ if runtime.GOOS == "plan9" {
+ // On plan9, env vars are lowercase.
+ homeEnv = "home"
+ }
+
+ // First prefer the HOME environmental variable
+ if home := os.Getenv(homeEnv); home != "" {
+ return home, nil
+ }
+
+ var stdout bytes.Buffer
+
+ // If that fails, try OS specific commands
+ if runtime.GOOS == "darwin" {
+ cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`)
+ cmd.Stdout = &stdout
+ if err := cmd.Run(); err == nil {
+ result := strings.TrimSpace(stdout.String())
+ if result != "" {
+ return result, nil
+ }
+ }
+ } else {
+ cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
+ cmd.Stdout = &stdout
+ if err := cmd.Run(); err != nil {
+ // If the error is ErrNotFound, we ignore it. Otherwise, return it.
+ if err != exec.ErrNotFound {
+ return "", err
+ }
+ } else {
+ if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
+ // username:password:uid:gid:gecos:home:shell
+ passwdParts := strings.SplitN(passwd, ":", 7)
+ if len(passwdParts) > 5 {
+ return passwdParts[5], nil
+ }
+ }
+ }
+ }
+
+ // If all else fails, try the shell
+ stdout.Reset()
+ cmd := exec.Command("sh", "-c", "cd && pwd")
+ cmd.Stdout = &stdout
+ if err := cmd.Run(); err != nil {
+ return "", err
+ }
+
+ result := strings.TrimSpace(stdout.String())
+ if result == "" {
+ return "", errors.New("blank output when reading home directory")
+ }
+
+ return result, nil
+}
+
+func dirWindows() (string, error) {
+ // First prefer the HOME environmental variable
+ if home := os.Getenv("HOME"); home != "" {
+ return home, nil
+ }
+
+ // Prefer standard environment variable USERPROFILE
+ if home := os.Getenv("USERPROFILE"); home != "" {
+ return home, nil
+ }
+
+ drive := os.Getenv("HOMEDRIVE")
+ path := os.Getenv("HOMEPATH")
+ home := drive + path
+ if drive == "" || path == "" {
+ return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
+ }
+
+ return home, nil
+}
diff --git a/vendor/github.com/modern-go/concurrent/.gitignore b/vendor/github.com/modern-go/concurrent/.gitignore
new file mode 100644
index 00000000..3f2bc474
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/.gitignore
@@ -0,0 +1 @@
+/coverage.txt
diff --git a/vendor/github.com/modern-go/concurrent/.travis.yml b/vendor/github.com/modern-go/concurrent/.travis.yml
new file mode 100644
index 00000000..449e67cd
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+
+go:
+ - 1.8.x
+ - 1.x
+
+before_install:
+ - go get -t -v ./...
+
+script:
+ - ./test.sh
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
diff --git a/vendor/github.com/modern-go/concurrent/LICENSE b/vendor/github.com/modern-go/concurrent/LICENSE
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/modern-go/concurrent/README.md b/vendor/github.com/modern-go/concurrent/README.md
new file mode 100644
index 00000000..acab3200
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/README.md
@@ -0,0 +1,49 @@
+# concurrent
+
+[![Sourcegraph](https://sourcegraph.com/github.com/modern-go/concurrent/-/badge.svg)](https://sourcegraph.com/github.com/modern-go/concurrent?badge)
+[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/modern-go/concurrent)
+[![Build Status](https://travis-ci.org/modern-go/concurrent.svg?branch=master)](https://travis-ci.org/modern-go/concurrent)
+[![codecov](https://codecov.io/gh/modern-go/concurrent/branch/master/graph/badge.svg)](https://codecov.io/gh/modern-go/concurrent)
+[![rcard](https://goreportcard.com/badge/github.com/modern-go/concurrent)](https://goreportcard.com/report/github.com/modern-go/concurrent)
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/modern-go/concurrent/master/LICENSE)
+
+* concurrent.Map: backport sync.Map for go below 1.9
+* concurrent.Executor: goroutine with explicit ownership and cancellable
+
+# concurrent.Map
+
+because sync.Map is only available in go 1.9, we can use concurrent.Map to make code portable
+
+```go
+m := concurrent.NewMap()
+m.Store("hello", "world")
+elem, found := m.Load("hello")
+// elem will be "world"
+// found will be true
+```
+
+# concurrent.Executor
+
+```go
+executor := concurrent.NewUnboundedExecutor()
+executor.Go(func(ctx context.Context) {
+ everyMillisecond := time.NewTicker(time.Millisecond)
+ for {
+ select {
+ case <-ctx.Done():
+ fmt.Println("goroutine exited")
+ return
+ case <-everyMillisecond.C:
+ // do something
+ }
+ }
+})
+time.Sleep(time.Second)
+executor.StopAndWaitForever()
+fmt.Println("executor stopped")
+```
+
+attach goroutine to executor instance, so that we can
+
+* cancel it by stop the executor with Stop/StopAndWait/StopAndWaitForever
+* handle panic by callback: the default behavior will no longer crash your application \ No newline at end of file
diff --git a/vendor/github.com/modern-go/concurrent/executor.go b/vendor/github.com/modern-go/concurrent/executor.go
new file mode 100644
index 00000000..623dba1a
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/executor.go
@@ -0,0 +1,14 @@
+package concurrent
+
+import "context"
+
+// Executor replace go keyword to start a new goroutine
+// the goroutine should cancel itself if the context passed in has been cancelled
+// the goroutine started by the executor, is owned by the executor
+// we can cancel all executors owned by the executor just by stop the executor itself
+// however Executor interface does not Stop method, the one starting and owning executor
+// should use the concrete type of executor, instead of this interface.
+type Executor interface {
+ // Go starts a new goroutine controlled by the context
+ Go(handler func(ctx context.Context))
+}
diff --git a/vendor/github.com/modern-go/concurrent/go_above_19.go b/vendor/github.com/modern-go/concurrent/go_above_19.go
new file mode 100644
index 00000000..aeabf8c4
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/go_above_19.go
@@ -0,0 +1,15 @@
+//+build go1.9
+
+package concurrent
+
+import "sync"
+
+// Map is a wrapper for sync.Map introduced in go1.9
+type Map struct {
+ sync.Map
+}
+
+// NewMap creates a thread safe Map
+func NewMap() *Map {
+ return &Map{}
+}
diff --git a/vendor/github.com/modern-go/concurrent/go_below_19.go b/vendor/github.com/modern-go/concurrent/go_below_19.go
new file mode 100644
index 00000000..b9c8df7f
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/go_below_19.go
@@ -0,0 +1,33 @@
+//+build !go1.9
+
+package concurrent
+
+import "sync"
+
+// Map implements a thread safe map for go version below 1.9 using mutex
+type Map struct {
+ lock sync.RWMutex
+ data map[interface{}]interface{}
+}
+
+// NewMap creates a thread safe map
+func NewMap() *Map {
+ return &Map{
+ data: make(map[interface{}]interface{}, 32),
+ }
+}
+
+// Load is same as sync.Map Load
+func (m *Map) Load(key interface{}) (elem interface{}, found bool) {
+ m.lock.RLock()
+ elem, found = m.data[key]
+ m.lock.RUnlock()
+ return
+}
+
+// Load is same as sync.Map Store
+func (m *Map) Store(key interface{}, elem interface{}) {
+ m.lock.Lock()
+ m.data[key] = elem
+ m.lock.Unlock()
+}
diff --git a/vendor/github.com/modern-go/concurrent/log.go b/vendor/github.com/modern-go/concurrent/log.go
new file mode 100644
index 00000000..9756fcc7
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/log.go
@@ -0,0 +1,13 @@
+package concurrent
+
+import (
+ "os"
+ "log"
+ "io/ioutil"
+)
+
+// ErrorLogger is used to print out error, can be set to writer other than stderr
+var ErrorLogger = log.New(os.Stderr, "", 0)
+
+// InfoLogger is used to print informational message, default to off
+var InfoLogger = log.New(ioutil.Discard, "", 0) \ No newline at end of file
diff --git a/vendor/github.com/modern-go/concurrent/test.sh b/vendor/github.com/modern-go/concurrent/test.sh
new file mode 100644
index 00000000..d1e6b2ec
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/test.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+echo "" > coverage.txt
+
+for d in $(go list ./... | grep -v vendor); do
+ go test -coverprofile=profile.out -coverpkg=github.com/modern-go/concurrent $d
+ if [ -f profile.out ]; then
+ cat profile.out >> coverage.txt
+ rm profile.out
+ fi
+done
diff --git a/vendor/github.com/modern-go/concurrent/unbounded_executor.go b/vendor/github.com/modern-go/concurrent/unbounded_executor.go
new file mode 100644
index 00000000..05a77dce
--- /dev/null
+++ b/vendor/github.com/modern-go/concurrent/unbounded_executor.go
@@ -0,0 +1,119 @@
+package concurrent
+
+import (
+ "context"
+ "fmt"
+ "runtime"
+ "runtime/debug"
+ "sync"
+ "time"
+ "reflect"
+)
+
+// HandlePanic logs goroutine panic by default
+var HandlePanic = func(recovered interface{}, funcName string) {
+ ErrorLogger.Println(fmt.Sprintf("%s panic: %v", funcName, recovered))
+ ErrorLogger.Println(string(debug.Stack()))
+}
+
+// UnboundedExecutor is a executor without limits on counts of alive goroutines
+// it tracks the goroutine started by it, and can cancel them when shutdown
+type UnboundedExecutor struct {
+ ctx context.Context
+ cancel context.CancelFunc
+ activeGoroutinesMutex *sync.Mutex
+ activeGoroutines map[string]int
+ HandlePanic func(recovered interface{}, funcName string)
+}
+
+// GlobalUnboundedExecutor has the life cycle of the program itself
+// any goroutine want to be shutdown before main exit can be started from this executor
+// GlobalUnboundedExecutor expects the main function to call stop
+// it does not magically knows the main function exits
+var GlobalUnboundedExecutor = NewUnboundedExecutor()
+
+// NewUnboundedExecutor creates a new UnboundedExecutor,
+// UnboundedExecutor can not be created by &UnboundedExecutor{}
+// HandlePanic can be set with a callback to override global HandlePanic
+func NewUnboundedExecutor() *UnboundedExecutor {
+ ctx, cancel := context.WithCancel(context.TODO())
+ return &UnboundedExecutor{
+ ctx: ctx,
+ cancel: cancel,
+ activeGoroutinesMutex: &sync.Mutex{},
+ activeGoroutines: map[string]int{},
+ }
+}
+
+// Go starts a new goroutine and tracks its lifecycle.
+// Panic will be recovered and logged automatically, except for StopSignal
+func (executor *UnboundedExecutor) Go(handler func(ctx context.Context)) {
+ pc := reflect.ValueOf(handler).Pointer()
+ f := runtime.FuncForPC(pc)
+ funcName := f.Name()
+ file, line := f.FileLine(pc)
+ executor.activeGoroutinesMutex.Lock()
+ defer executor.activeGoroutinesMutex.Unlock()
+ startFrom := fmt.Sprintf("%s:%d", file, line)
+ executor.activeGoroutines[startFrom] += 1
+ go func() {
+ defer func() {
+ recovered := recover()
+ // if you want to quit a goroutine without trigger HandlePanic
+ // use runtime.Goexit() to quit
+ if recovered != nil {
+ if executor.HandlePanic == nil {
+ HandlePanic(recovered, funcName)
+ } else {
+ executor.HandlePanic(recovered, funcName)
+ }
+ }
+ executor.activeGoroutinesMutex.Lock()
+ executor.activeGoroutines[startFrom] -= 1
+ executor.activeGoroutinesMutex.Unlock()
+ }()
+ handler(executor.ctx)
+ }()
+}
+
+// Stop cancel all goroutines started by this executor without wait
+func (executor *UnboundedExecutor) Stop() {
+ executor.cancel()
+}
+
+// StopAndWaitForever cancel all goroutines started by this executor and
+// wait until all goroutines exited
+func (executor *UnboundedExecutor) StopAndWaitForever() {
+ executor.StopAndWait(context.Background())
+}
+
+// StopAndWait cancel all goroutines started by this executor and wait.
+// Wait can be cancelled by the context passed in.
+func (executor *UnboundedExecutor) StopAndWait(ctx context.Context) {
+ executor.cancel()
+ for {
+ oneHundredMilliseconds := time.NewTimer(time.Millisecond * 100)
+ select {
+ case <-oneHundredMilliseconds.C:
+ if executor.checkNoActiveGoroutines() {
+ return
+ }
+ case <-ctx.Done():
+ return
+ }
+ }
+}
+
+func (executor *UnboundedExecutor) checkNoActiveGoroutines() bool {
+ executor.activeGoroutinesMutex.Lock()
+ defer executor.activeGoroutinesMutex.Unlock()
+ for startFrom, count := range executor.activeGoroutines {
+ if count > 0 {
+ InfoLogger.Println("UnboundedExecutor is still waiting goroutines to quit",
+ "startFrom", startFrom,
+ "count", count)
+ return false
+ }
+ }
+ return true
+}
diff --git a/vendor/github.com/modern-go/reflect2/.gitignore b/vendor/github.com/modern-go/reflect2/.gitignore
new file mode 100644
index 00000000..7b26c946
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/coverage.txt
diff --git a/vendor/github.com/modern-go/reflect2/.travis.yml b/vendor/github.com/modern-go/reflect2/.travis.yml
new file mode 100644
index 00000000..fbb43744
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+
+go:
+ - 1.8.x
+ - 1.x
+
+before_install:
+ - go get -t -v ./...
+ - go get -t -v github.com/modern-go/reflect2-tests/...
+
+script:
+ - ./test.sh
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
diff --git a/vendor/github.com/modern-go/reflect2/Gopkg.lock b/vendor/github.com/modern-go/reflect2/Gopkg.lock
new file mode 100644
index 00000000..2a3a6989
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/Gopkg.lock
@@ -0,0 +1,15 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/modern-go/concurrent"
+ packages = ["."]
+ revision = "e0a39a4cb4216ea8db28e22a69f4ec25610d513a"
+ version = "1.0.0"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "daee8a88b3498b61c5640056665b8b9eea062006f5e596bbb6a3ed9119a11ec7"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/vendor/github.com/modern-go/reflect2/Gopkg.toml b/vendor/github.com/modern-go/reflect2/Gopkg.toml
new file mode 100644
index 00000000..2f4f4dbd
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/Gopkg.toml
@@ -0,0 +1,35 @@
+# Gopkg.toml example
+#
+# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+ignored = []
+
+[[constraint]]
+ name = "github.com/modern-go/concurrent"
+ version = "1.0.0"
+
+[prune]
+ go-tests = true
+ unused-packages = true
diff --git a/vendor/github.com/modern-go/reflect2/LICENSE b/vendor/github.com/modern-go/reflect2/LICENSE
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/modern-go/reflect2/README.md b/vendor/github.com/modern-go/reflect2/README.md
new file mode 100644
index 00000000..6f968aab
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/README.md
@@ -0,0 +1,71 @@
+# reflect2
+
+[![Sourcegraph](https://sourcegraph.com/github.com/modern-go/reflect2/-/badge.svg)](https://sourcegraph.com/github.com/modern-go/reflect2?badge)
+[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/modern-go/reflect2)
+[![Build Status](https://travis-ci.org/modern-go/reflect2.svg?branch=master)](https://travis-ci.org/modern-go/reflect2)
+[![codecov](https://codecov.io/gh/modern-go/reflect2/branch/master/graph/badge.svg)](https://codecov.io/gh/modern-go/reflect2)
+[![rcard](https://goreportcard.com/badge/github.com/modern-go/reflect2)](https://goreportcard.com/report/github.com/modern-go/reflect2)
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/modern-go/reflect2/master/LICENSE)
+
+reflect api that avoids runtime reflect.Value cost
+
+* reflect get/set interface{}, with type checking
+* reflect get/set unsafe.Pointer, without type checking
+* `reflect2.TypeByName` works like `Class.forName` found in java
+
+[json-iterator](https://github.com/json-iterator/go) use this package to save runtime dispatching cost.
+This package is designed for low level libraries to optimize reflection performance.
+General application should still use reflect standard library.
+
+# reflect2.TypeByName
+
+```go
+// given package is github.com/your/awesome-package
+type MyStruct struct {
+ // ...
+}
+
+// will return the type
+reflect2.TypeByName("awesome-package.MyStruct")
+// however, if the type has not been used
+// it will be eliminated by compiler, so we can not get it in runtime
+```
+
+# reflect2 get/set interface{}
+
+```go
+valType := reflect2.TypeOf(1)
+i := 1
+j := 10
+valType.Set(&i, &j)
+// i will be 10
+```
+
+to get set `type`, always use its pointer `*type`
+
+# reflect2 get/set unsafe.Pointer
+
+```go
+valType := reflect2.TypeOf(1)
+i := 1
+j := 10
+valType.UnsafeSet(unsafe.Pointer(&i), unsafe.Pointer(&j))
+// i will be 10
+```
+
+to get set `type`, always use its pointer `*type`
+
+# benchmark
+
+Benchmark is not necessary for this package. It does nothing actually.
+As it is just a thin wrapper to make go runtime public.
+Both `reflect2` and `reflect` call same function
+provided by `runtime` package exposed by go language.
+
+# unsafe safety
+
+Instead of casting `[]byte` to `sliceHeader` in your application using unsafe.
+We can use reflect2 instead. This way, if `sliceHeader` changes in the future,
+only reflect2 need to be upgraded.
+
+reflect2 tries its best to keep the implementation same as reflect (by testing). \ No newline at end of file
diff --git a/vendor/github.com/modern-go/reflect2/go_above_17.go b/vendor/github.com/modern-go/reflect2/go_above_17.go
new file mode 100644
index 00000000..5c1cea86
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/go_above_17.go
@@ -0,0 +1,8 @@
+//+build go1.7
+
+package reflect2
+
+import "unsafe"
+
+//go:linkname resolveTypeOff reflect.resolveTypeOff
+func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer
diff --git a/vendor/github.com/modern-go/reflect2/go_above_19.go b/vendor/github.com/modern-go/reflect2/go_above_19.go
new file mode 100644
index 00000000..c7e3b780
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/go_above_19.go
@@ -0,0 +1,14 @@
+//+build go1.9
+
+package reflect2
+
+import (
+ "unsafe"
+)
+
+//go:linkname makemap reflect.makemap
+func makemap(rtype unsafe.Pointer, cap int) (m unsafe.Pointer)
+
+func makeMapWithSize(rtype unsafe.Pointer, cap int) unsafe.Pointer {
+ return makemap(rtype, cap)
+}
diff --git a/vendor/github.com/modern-go/reflect2/go_below_17.go b/vendor/github.com/modern-go/reflect2/go_below_17.go
new file mode 100644
index 00000000..65a93c88
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/go_below_17.go
@@ -0,0 +1,9 @@
+//+build !go1.7
+
+package reflect2
+
+import "unsafe"
+
+func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer {
+ return nil
+}
diff --git a/vendor/github.com/modern-go/reflect2/go_below_19.go b/vendor/github.com/modern-go/reflect2/go_below_19.go
new file mode 100644
index 00000000..b050ef70
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/go_below_19.go
@@ -0,0 +1,14 @@
+//+build !go1.9
+
+package reflect2
+
+import (
+ "unsafe"
+)
+
+//go:linkname makemap reflect.makemap
+func makemap(rtype unsafe.Pointer) (m unsafe.Pointer)
+
+func makeMapWithSize(rtype unsafe.Pointer, cap int) unsafe.Pointer {
+ return makemap(rtype)
+}
diff --git a/vendor/github.com/modern-go/reflect2/reflect2.go b/vendor/github.com/modern-go/reflect2/reflect2.go
new file mode 100644
index 00000000..63b49c79
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/reflect2.go
@@ -0,0 +1,298 @@
+package reflect2
+
+import (
+ "github.com/modern-go/concurrent"
+ "reflect"
+ "unsafe"
+)
+
+type Type interface {
+ Kind() reflect.Kind
+ // New return pointer to data of this type
+ New() interface{}
+ // UnsafeNew return the allocated space pointed by unsafe.Pointer
+ UnsafeNew() unsafe.Pointer
+ // PackEFace cast a unsafe pointer to object represented pointer
+ PackEFace(ptr unsafe.Pointer) interface{}
+ // Indirect dereference object represented pointer to this type
+ Indirect(obj interface{}) interface{}
+ // UnsafeIndirect dereference pointer to this type
+ UnsafeIndirect(ptr unsafe.Pointer) interface{}
+ // Type1 returns reflect.Type
+ Type1() reflect.Type
+ Implements(thatType Type) bool
+ String() string
+ RType() uintptr
+ // interface{} of this type has pointer like behavior
+ LikePtr() bool
+ IsNullable() bool
+ IsNil(obj interface{}) bool
+ UnsafeIsNil(ptr unsafe.Pointer) bool
+ Set(obj interface{}, val interface{})
+ UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer)
+ AssignableTo(anotherType Type) bool
+}
+
+type ListType interface {
+ Type
+ Elem() Type
+ SetIndex(obj interface{}, index int, elem interface{})
+ UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer)
+ GetIndex(obj interface{}, index int) interface{}
+ UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer
+}
+
+type ArrayType interface {
+ ListType
+ Len() int
+}
+
+type SliceType interface {
+ ListType
+ MakeSlice(length int, cap int) interface{}
+ UnsafeMakeSlice(length int, cap int) unsafe.Pointer
+ Grow(obj interface{}, newLength int)
+ UnsafeGrow(ptr unsafe.Pointer, newLength int)
+ Append(obj interface{}, elem interface{})
+ UnsafeAppend(obj unsafe.Pointer, elem unsafe.Pointer)
+ LengthOf(obj interface{}) int
+ UnsafeLengthOf(ptr unsafe.Pointer) int
+ SetNil(obj interface{})
+ UnsafeSetNil(ptr unsafe.Pointer)
+ Cap(obj interface{}) int
+ UnsafeCap(ptr unsafe.Pointer) int
+}
+
+type StructType interface {
+ Type
+ NumField() int
+ Field(i int) StructField
+ FieldByName(name string) StructField
+ FieldByIndex(index []int) StructField
+ FieldByNameFunc(match func(string) bool) StructField
+}
+
+type StructField interface {
+ Offset() uintptr
+ Name() string
+ PkgPath() string
+ Type() Type
+ Tag() reflect.StructTag
+ Index() []int
+ Anonymous() bool
+ Set(obj interface{}, value interface{})
+ UnsafeSet(obj unsafe.Pointer, value unsafe.Pointer)
+ Get(obj interface{}) interface{}
+ UnsafeGet(obj unsafe.Pointer) unsafe.Pointer
+}
+
+type MapType interface {
+ Type
+ Key() Type
+ Elem() Type
+ MakeMap(cap int) interface{}
+ UnsafeMakeMap(cap int) unsafe.Pointer
+ SetIndex(obj interface{}, key interface{}, elem interface{})
+ UnsafeSetIndex(obj unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer)
+ TryGetIndex(obj interface{}, key interface{}) (interface{}, bool)
+ GetIndex(obj interface{}, key interface{}) interface{}
+ UnsafeGetIndex(obj unsafe.Pointer, key unsafe.Pointer) unsafe.Pointer
+ Iterate(obj interface{}) MapIterator
+ UnsafeIterate(obj unsafe.Pointer) MapIterator
+}
+
+type MapIterator interface {
+ HasNext() bool
+ Next() (key interface{}, elem interface{})
+ UnsafeNext() (key unsafe.Pointer, elem unsafe.Pointer)
+}
+
+type PtrType interface {
+ Type
+ Elem() Type
+}
+
+type InterfaceType interface {
+ NumMethod() int
+}
+
+type Config struct {
+ UseSafeImplementation bool
+}
+
+type API interface {
+ TypeOf(obj interface{}) Type
+ Type2(type1 reflect.Type) Type
+}
+
+var ConfigUnsafe = Config{UseSafeImplementation: false}.Froze()
+var ConfigSafe = Config{UseSafeImplementation: true}.Froze()
+
+type frozenConfig struct {
+ useSafeImplementation bool
+ cache *concurrent.Map
+}
+
+func (cfg Config) Froze() *frozenConfig {
+ return &frozenConfig{
+ useSafeImplementation: cfg.UseSafeImplementation,
+ cache: concurrent.NewMap(),
+ }
+}
+
+func (cfg *frozenConfig) TypeOf(obj interface{}) Type {
+ cacheKey := uintptr(unpackEFace(obj).rtype)
+ typeObj, found := cfg.cache.Load(cacheKey)
+ if found {
+ return typeObj.(Type)
+ }
+ return cfg.Type2(reflect.TypeOf(obj))
+}
+
+func (cfg *frozenConfig) Type2(type1 reflect.Type) Type {
+ if type1 == nil {
+ return nil
+ }
+ cacheKey := uintptr(unpackEFace(type1).data)
+ typeObj, found := cfg.cache.Load(cacheKey)
+ if found {
+ return typeObj.(Type)
+ }
+ type2 := cfg.wrapType(type1)
+ cfg.cache.Store(cacheKey, type2)
+ return type2
+}
+
+func (cfg *frozenConfig) wrapType(type1 reflect.Type) Type {
+ safeType := safeType{Type: type1, cfg: cfg}
+ switch type1.Kind() {
+ case reflect.Struct:
+ if cfg.useSafeImplementation {
+ return &safeStructType{safeType}
+ }
+ return newUnsafeStructType(cfg, type1)
+ case reflect.Array:
+ if cfg.useSafeImplementation {
+ return &safeSliceType{safeType}
+ }
+ return newUnsafeArrayType(cfg, type1)
+ case reflect.Slice:
+ if cfg.useSafeImplementation {
+ return &safeSliceType{safeType}
+ }
+ return newUnsafeSliceType(cfg, type1)
+ case reflect.Map:
+ if cfg.useSafeImplementation {
+ return &safeMapType{safeType}
+ }
+ return newUnsafeMapType(cfg, type1)
+ case reflect.Ptr, reflect.Chan, reflect.Func:
+ if cfg.useSafeImplementation {
+ return &safeMapType{safeType}
+ }
+ return newUnsafePtrType(cfg, type1)
+ case reflect.Interface:
+ if cfg.useSafeImplementation {
+ return &safeMapType{safeType}
+ }
+ if type1.NumMethod() == 0 {
+ return newUnsafeEFaceType(cfg, type1)
+ }
+ return newUnsafeIFaceType(cfg, type1)
+ default:
+ if cfg.useSafeImplementation {
+ return &safeType
+ }
+ return newUnsafeType(cfg, type1)
+ }
+}
+
+func TypeOf(obj interface{}) Type {
+ return ConfigUnsafe.TypeOf(obj)
+}
+
+func TypeOfPtr(obj interface{}) PtrType {
+ return TypeOf(obj).(PtrType)
+}
+
+func Type2(type1 reflect.Type) Type {
+ if type1 == nil {
+ return nil
+ }
+ return ConfigUnsafe.Type2(type1)
+}
+
+func PtrTo(typ Type) Type {
+ return Type2(reflect.PtrTo(typ.Type1()))
+}
+
+func PtrOf(obj interface{}) unsafe.Pointer {
+ return unpackEFace(obj).data
+}
+
+func RTypeOf(obj interface{}) uintptr {
+ return uintptr(unpackEFace(obj).rtype)
+}
+
+func IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ return unpackEFace(obj).data == nil
+}
+
+func IsNullable(kind reflect.Kind) bool {
+ switch kind {
+ case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Func, reflect.Slice, reflect.Interface:
+ return true
+ }
+ return false
+}
+
+func likePtrKind(kind reflect.Kind) bool {
+ switch kind {
+ case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Func:
+ return true
+ }
+ return false
+}
+
+func likePtrType(typ reflect.Type) bool {
+ if likePtrKind(typ.Kind()) {
+ return true
+ }
+ if typ.Kind() == reflect.Struct {
+ if typ.NumField() != 1 {
+ return false
+ }
+ return likePtrType(typ.Field(0).Type)
+ }
+ if typ.Kind() == reflect.Array {
+ if typ.Len() != 1 {
+ return false
+ }
+ return likePtrType(typ.Elem())
+ }
+ return false
+}
+
+// NoEscape hides a pointer from escape analysis. noescape is
+// the identity function but escape analysis doesn't think the
+// output depends on the input. noescape is inlined and currently
+// compiles down to zero instructions.
+// USE CAREFULLY!
+//go:nosplit
+func NoEscape(p unsafe.Pointer) unsafe.Pointer {
+ x := uintptr(p)
+ return unsafe.Pointer(x ^ 0)
+}
+
+func UnsafeCastString(str string) []byte {
+ stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
+ sliceHeader := &reflect.SliceHeader{
+ Data: stringHeader.Data,
+ Cap: stringHeader.Len,
+ Len: stringHeader.Len,
+ }
+ return *(*[]byte)(unsafe.Pointer(sliceHeader))
+}
diff --git a/vendor/github.com/modern-go/reflect2/reflect2_amd64.s b/vendor/github.com/modern-go/reflect2/reflect2_amd64.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/reflect2_amd64.s
diff --git a/vendor/github.com/modern-go/reflect2/reflect2_kind.go b/vendor/github.com/modern-go/reflect2/reflect2_kind.go
new file mode 100644
index 00000000..62f299e4
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/reflect2_kind.go
@@ -0,0 +1,30 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+// DefaultTypeOfKind return the non aliased default type for the kind
+func DefaultTypeOfKind(kind reflect.Kind) Type {
+ return kindTypes[kind]
+}
+
+var kindTypes = map[reflect.Kind]Type{
+ reflect.Bool: TypeOf(true),
+ reflect.Uint8: TypeOf(uint8(0)),
+ reflect.Int8: TypeOf(int8(0)),
+ reflect.Uint16: TypeOf(uint16(0)),
+ reflect.Int16: TypeOf(int16(0)),
+ reflect.Uint32: TypeOf(uint32(0)),
+ reflect.Int32: TypeOf(int32(0)),
+ reflect.Uint64: TypeOf(uint64(0)),
+ reflect.Int64: TypeOf(int64(0)),
+ reflect.Uint: TypeOf(uint(0)),
+ reflect.Int: TypeOf(int(0)),
+ reflect.Float32: TypeOf(float32(0)),
+ reflect.Float64: TypeOf(float64(0)),
+ reflect.Uintptr: TypeOf(uintptr(0)),
+ reflect.String: TypeOf(""),
+ reflect.UnsafePointer: TypeOf(unsafe.Pointer(nil)),
+}
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_386.s b/vendor/github.com/modern-go/reflect2/relfect2_386.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_386.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_amd64p32.s b/vendor/github.com/modern-go/reflect2/relfect2_amd64p32.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_amd64p32.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_arm.s b/vendor/github.com/modern-go/reflect2/relfect2_arm.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_arm.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_arm64.s b/vendor/github.com/modern-go/reflect2/relfect2_arm64.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_arm64.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_mips64x.s b/vendor/github.com/modern-go/reflect2/relfect2_mips64x.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_mips64x.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_mipsx.s b/vendor/github.com/modern-go/reflect2/relfect2_mipsx.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_mipsx.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_ppc64x.s b/vendor/github.com/modern-go/reflect2/relfect2_ppc64x.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_ppc64x.s
diff --git a/vendor/github.com/modern-go/reflect2/relfect2_s390x.s b/vendor/github.com/modern-go/reflect2/relfect2_s390x.s
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/relfect2_s390x.s
diff --git a/vendor/github.com/modern-go/reflect2/safe_field.go b/vendor/github.com/modern-go/reflect2/safe_field.go
new file mode 100644
index 00000000..d4ba1f4f
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/safe_field.go
@@ -0,0 +1,58 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type safeField struct {
+ reflect.StructField
+}
+
+func (field *safeField) Offset() uintptr {
+ return field.StructField.Offset
+}
+
+func (field *safeField) Name() string {
+ return field.StructField.Name
+}
+
+func (field *safeField) PkgPath() string {
+ return field.StructField.PkgPath
+}
+
+func (field *safeField) Type() Type {
+ panic("not implemented")
+}
+
+func (field *safeField) Tag() reflect.StructTag {
+ return field.StructField.Tag
+}
+
+func (field *safeField) Index() []int {
+ return field.StructField.Index
+}
+
+func (field *safeField) Anonymous() bool {
+ return field.StructField.Anonymous
+}
+
+func (field *safeField) Set(obj interface{}, value interface{}) {
+ val := reflect.ValueOf(obj).Elem()
+ val.FieldByIndex(field.Index()).Set(reflect.ValueOf(value).Elem())
+}
+
+func (field *safeField) UnsafeSet(obj unsafe.Pointer, value unsafe.Pointer) {
+ panic("unsafe operation is not supported")
+}
+
+func (field *safeField) Get(obj interface{}) interface{} {
+ val := reflect.ValueOf(obj).Elem().FieldByIndex(field.Index())
+ ptr := reflect.New(val.Type())
+ ptr.Elem().Set(val)
+ return ptr.Interface()
+}
+
+func (field *safeField) UnsafeGet(obj unsafe.Pointer) unsafe.Pointer {
+ panic("does not support unsafe operation")
+}
diff --git a/vendor/github.com/modern-go/reflect2/safe_map.go b/vendor/github.com/modern-go/reflect2/safe_map.go
new file mode 100644
index 00000000..88362205
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/safe_map.go
@@ -0,0 +1,101 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type safeMapType struct {
+ safeType
+}
+
+func (type2 *safeMapType) Key() Type {
+ return type2.safeType.cfg.Type2(type2.Type.Key())
+}
+
+func (type2 *safeMapType) MakeMap(cap int) interface{} {
+ ptr := reflect.New(type2.Type)
+ ptr.Elem().Set(reflect.MakeMap(type2.Type))
+ return ptr.Interface()
+}
+
+func (type2 *safeMapType) UnsafeMakeMap(cap int) unsafe.Pointer {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeMapType) SetIndex(obj interface{}, key interface{}, elem interface{}) {
+ keyVal := reflect.ValueOf(key)
+ elemVal := reflect.ValueOf(elem)
+ val := reflect.ValueOf(obj)
+ val.Elem().SetMapIndex(keyVal.Elem(), elemVal.Elem())
+}
+
+func (type2 *safeMapType) UnsafeSetIndex(obj unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer) {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeMapType) TryGetIndex(obj interface{}, key interface{}) (interface{}, bool) {
+ keyVal := reflect.ValueOf(key)
+ if key == nil {
+ keyVal = reflect.New(type2.Type.Key()).Elem()
+ }
+ val := reflect.ValueOf(obj).MapIndex(keyVal)
+ if !val.IsValid() {
+ return nil, false
+ }
+ return val.Interface(), true
+}
+
+func (type2 *safeMapType) GetIndex(obj interface{}, key interface{}) interface{} {
+ val := reflect.ValueOf(obj).Elem()
+ keyVal := reflect.ValueOf(key).Elem()
+ elemVal := val.MapIndex(keyVal)
+ if !elemVal.IsValid() {
+ ptr := reflect.New(reflect.PtrTo(val.Type().Elem()))
+ return ptr.Elem().Interface()
+ }
+ ptr := reflect.New(elemVal.Type())
+ ptr.Elem().Set(elemVal)
+ return ptr.Interface()
+}
+
+func (type2 *safeMapType) UnsafeGetIndex(obj unsafe.Pointer, key unsafe.Pointer) unsafe.Pointer {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeMapType) Iterate(obj interface{}) MapIterator {
+ m := reflect.ValueOf(obj).Elem()
+ return &safeMapIterator{
+ m: m,
+ keys: m.MapKeys(),
+ }
+}
+
+func (type2 *safeMapType) UnsafeIterate(obj unsafe.Pointer) MapIterator {
+ panic("does not support unsafe operation")
+}
+
+type safeMapIterator struct {
+ i int
+ m reflect.Value
+ keys []reflect.Value
+}
+
+func (iter *safeMapIterator) HasNext() bool {
+ return iter.i != len(iter.keys)
+}
+
+func (iter *safeMapIterator) Next() (interface{}, interface{}) {
+ key := iter.keys[iter.i]
+ elem := iter.m.MapIndex(key)
+ iter.i += 1
+ keyPtr := reflect.New(key.Type())
+ keyPtr.Elem().Set(key)
+ elemPtr := reflect.New(elem.Type())
+ elemPtr.Elem().Set(elem)
+ return keyPtr.Interface(), elemPtr.Interface()
+}
+
+func (iter *safeMapIterator) UnsafeNext() (unsafe.Pointer, unsafe.Pointer) {
+ panic("does not support unsafe operation")
+}
diff --git a/vendor/github.com/modern-go/reflect2/safe_slice.go b/vendor/github.com/modern-go/reflect2/safe_slice.go
new file mode 100644
index 00000000..bcce6fd2
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/safe_slice.go
@@ -0,0 +1,92 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type safeSliceType struct {
+ safeType
+}
+
+func (type2 *safeSliceType) SetIndex(obj interface{}, index int, value interface{}) {
+ val := reflect.ValueOf(obj).Elem()
+ elem := reflect.ValueOf(value).Elem()
+ val.Index(index).Set(elem)
+}
+
+func (type2 *safeSliceType) UnsafeSetIndex(obj unsafe.Pointer, index int, value unsafe.Pointer) {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) GetIndex(obj interface{}, index int) interface{} {
+ val := reflect.ValueOf(obj).Elem()
+ elem := val.Index(index)
+ ptr := reflect.New(elem.Type())
+ ptr.Elem().Set(elem)
+ return ptr.Interface()
+}
+
+func (type2 *safeSliceType) UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) MakeSlice(length int, cap int) interface{} {
+ val := reflect.MakeSlice(type2.Type, length, cap)
+ ptr := reflect.New(val.Type())
+ ptr.Elem().Set(val)
+ return ptr.Interface()
+}
+
+func (type2 *safeSliceType) UnsafeMakeSlice(length int, cap int) unsafe.Pointer {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) Grow(obj interface{}, newLength int) {
+ oldCap := type2.Cap(obj)
+ oldSlice := reflect.ValueOf(obj).Elem()
+ delta := newLength - oldCap
+ deltaVals := make([]reflect.Value, delta)
+ newSlice := reflect.Append(oldSlice, deltaVals...)
+ oldSlice.Set(newSlice)
+}
+
+func (type2 *safeSliceType) UnsafeGrow(ptr unsafe.Pointer, newLength int) {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) Append(obj interface{}, elem interface{}) {
+ val := reflect.ValueOf(obj).Elem()
+ elemVal := reflect.ValueOf(elem).Elem()
+ newVal := reflect.Append(val, elemVal)
+ val.Set(newVal)
+}
+
+func (type2 *safeSliceType) UnsafeAppend(obj unsafe.Pointer, elem unsafe.Pointer) {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) SetNil(obj interface{}) {
+ val := reflect.ValueOf(obj).Elem()
+ val.Set(reflect.Zero(val.Type()))
+}
+
+func (type2 *safeSliceType) UnsafeSetNil(ptr unsafe.Pointer) {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) LengthOf(obj interface{}) int {
+ return reflect.ValueOf(obj).Elem().Len()
+}
+
+func (type2 *safeSliceType) UnsafeLengthOf(ptr unsafe.Pointer) int {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeSliceType) Cap(obj interface{}) int {
+ return reflect.ValueOf(obj).Elem().Cap()
+}
+
+func (type2 *safeSliceType) UnsafeCap(ptr unsafe.Pointer) int {
+ panic("does not support unsafe operation")
+}
diff --git a/vendor/github.com/modern-go/reflect2/safe_struct.go b/vendor/github.com/modern-go/reflect2/safe_struct.go
new file mode 100644
index 00000000..e5fb9b31
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/safe_struct.go
@@ -0,0 +1,29 @@
+package reflect2
+
+type safeStructType struct {
+ safeType
+}
+
+func (type2 *safeStructType) FieldByName(name string) StructField {
+ field, found := type2.Type.FieldByName(name)
+ if !found {
+ panic("field " + name + " not found")
+ }
+ return &safeField{StructField: field}
+}
+
+func (type2 *safeStructType) Field(i int) StructField {
+ return &safeField{StructField: type2.Type.Field(i)}
+}
+
+func (type2 *safeStructType) FieldByIndex(index []int) StructField {
+ return &safeField{StructField: type2.Type.FieldByIndex(index)}
+}
+
+func (type2 *safeStructType) FieldByNameFunc(match func(string) bool) StructField {
+ field, found := type2.Type.FieldByNameFunc(match)
+ if !found {
+ panic("field match condition not found in " + type2.Type.String())
+ }
+ return &safeField{StructField: field}
+}
diff --git a/vendor/github.com/modern-go/reflect2/safe_type.go b/vendor/github.com/modern-go/reflect2/safe_type.go
new file mode 100644
index 00000000..ee4e7bb6
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/safe_type.go
@@ -0,0 +1,78 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type safeType struct {
+ reflect.Type
+ cfg *frozenConfig
+}
+
+func (type2 *safeType) New() interface{} {
+ return reflect.New(type2.Type).Interface()
+}
+
+func (type2 *safeType) UnsafeNew() unsafe.Pointer {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) Elem() Type {
+ return type2.cfg.Type2(type2.Type.Elem())
+}
+
+func (type2 *safeType) Type1() reflect.Type {
+ return type2.Type
+}
+
+func (type2 *safeType) PackEFace(ptr unsafe.Pointer) interface{} {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) Implements(thatType Type) bool {
+ return type2.Type.Implements(thatType.Type1())
+}
+
+func (type2 *safeType) RType() uintptr {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) Indirect(obj interface{}) interface{} {
+ return reflect.Indirect(reflect.ValueOf(obj)).Interface()
+}
+
+func (type2 *safeType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) LikePtr() bool {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) IsNullable() bool {
+ return IsNullable(type2.Kind())
+}
+
+func (type2 *safeType) IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ return reflect.ValueOf(obj).Elem().IsNil()
+}
+
+func (type2 *safeType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) Set(obj interface{}, val interface{}) {
+ reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(val).Elem())
+}
+
+func (type2 *safeType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer) {
+ panic("does not support unsafe operation")
+}
+
+func (type2 *safeType) AssignableTo(anotherType Type) bool {
+ return type2.Type1().AssignableTo(anotherType.Type1())
+}
diff --git a/vendor/github.com/modern-go/reflect2/test.sh b/vendor/github.com/modern-go/reflect2/test.sh
new file mode 100644
index 00000000..3d2b9768
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/test.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+echo "" > coverage.txt
+
+for d in $(go list github.com/modern-go/reflect2-tests/... | grep -v vendor); do
+ go test -coverprofile=profile.out -coverpkg=github.com/modern-go/reflect2 $d
+ if [ -f profile.out ]; then
+ cat profile.out >> coverage.txt
+ rm profile.out
+ fi
+done
diff --git a/vendor/github.com/modern-go/reflect2/type_map.go b/vendor/github.com/modern-go/reflect2/type_map.go
new file mode 100644
index 00000000..3acfb558
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/type_map.go
@@ -0,0 +1,113 @@
+package reflect2
+
+import (
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+ "unsafe"
+)
+
+// typelinks1 for 1.5 ~ 1.6
+//go:linkname typelinks1 reflect.typelinks
+func typelinks1() [][]unsafe.Pointer
+
+// typelinks2 for 1.7 ~
+//go:linkname typelinks2 reflect.typelinks
+func typelinks2() (sections []unsafe.Pointer, offset [][]int32)
+
+// initOnce guards initialization of types and packages
+var initOnce sync.Once
+
+var types map[string]reflect.Type
+var packages map[string]map[string]reflect.Type
+
+// discoverTypes initializes types and packages
+func discoverTypes() {
+ types = make(map[string]reflect.Type)
+ packages = make(map[string]map[string]reflect.Type)
+
+ ver := runtime.Version()
+ if ver == "go1.5" || strings.HasPrefix(ver, "go1.5.") {
+ loadGo15Types()
+ } else if ver == "go1.6" || strings.HasPrefix(ver, "go1.6.") {
+ loadGo15Types()
+ } else {
+ loadGo17Types()
+ }
+}
+
+func loadGo15Types() {
+ var obj interface{} = reflect.TypeOf(0)
+ typePtrss := typelinks1()
+ for _, typePtrs := range typePtrss {
+ for _, typePtr := range typePtrs {
+ (*emptyInterface)(unsafe.Pointer(&obj)).word = typePtr
+ typ := obj.(reflect.Type)
+ if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct {
+ loadedType := typ.Elem()
+ pkgTypes := packages[loadedType.PkgPath()]
+ if pkgTypes == nil {
+ pkgTypes = map[string]reflect.Type{}
+ packages[loadedType.PkgPath()] = pkgTypes
+ }
+ types[loadedType.String()] = loadedType
+ pkgTypes[loadedType.Name()] = loadedType
+ }
+ if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Ptr &&
+ typ.Elem().Elem().Kind() == reflect.Struct {
+ loadedType := typ.Elem().Elem()
+ pkgTypes := packages[loadedType.PkgPath()]
+ if pkgTypes == nil {
+ pkgTypes = map[string]reflect.Type{}
+ packages[loadedType.PkgPath()] = pkgTypes
+ }
+ types[loadedType.String()] = loadedType
+ pkgTypes[loadedType.Name()] = loadedType
+ }
+ }
+ }
+}
+
+func loadGo17Types() {
+ var obj interface{} = reflect.TypeOf(0)
+ sections, offset := typelinks2()
+ for i, offs := range offset {
+ rodata := sections[i]
+ for _, off := range offs {
+ (*emptyInterface)(unsafe.Pointer(&obj)).word = resolveTypeOff(unsafe.Pointer(rodata), off)
+ typ := obj.(reflect.Type)
+ if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct {
+ loadedType := typ.Elem()
+ pkgTypes := packages[loadedType.PkgPath()]
+ if pkgTypes == nil {
+ pkgTypes = map[string]reflect.Type{}
+ packages[loadedType.PkgPath()] = pkgTypes
+ }
+ types[loadedType.String()] = loadedType
+ pkgTypes[loadedType.Name()] = loadedType
+ }
+ }
+ }
+}
+
+type emptyInterface struct {
+ typ unsafe.Pointer
+ word unsafe.Pointer
+}
+
+// TypeByName return the type by its name, just like Class.forName in java
+func TypeByName(typeName string) Type {
+ initOnce.Do(discoverTypes)
+ return Type2(types[typeName])
+}
+
+// TypeByPackageName return the type by its package and name
+func TypeByPackageName(pkgPath string, name string) Type {
+ initOnce.Do(discoverTypes)
+ pkgTypes := packages[pkgPath]
+ if pkgTypes == nil {
+ return nil
+ }
+ return Type2(pkgTypes[name])
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_array.go b/vendor/github.com/modern-go/reflect2/unsafe_array.go
new file mode 100644
index 00000000..76cbdba6
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_array.go
@@ -0,0 +1,65 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type UnsafeArrayType struct {
+ unsafeType
+ elemRType unsafe.Pointer
+ pElemRType unsafe.Pointer
+ elemSize uintptr
+ likePtr bool
+}
+
+func newUnsafeArrayType(cfg *frozenConfig, type1 reflect.Type) *UnsafeArrayType {
+ return &UnsafeArrayType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ elemRType: unpackEFace(type1.Elem()).data,
+ pElemRType: unpackEFace(reflect.PtrTo(type1.Elem())).data,
+ elemSize: type1.Elem().Size(),
+ likePtr: likePtrType(type1),
+ }
+}
+
+func (type2 *UnsafeArrayType) LikePtr() bool {
+ return type2.likePtr
+}
+
+func (type2 *UnsafeArrayType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *UnsafeArrayType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ if type2.likePtr {
+ return packEFace(type2.rtype, *(*unsafe.Pointer)(ptr))
+ }
+ return packEFace(type2.rtype, ptr)
+}
+
+func (type2 *UnsafeArrayType) SetIndex(obj interface{}, index int, elem interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("ArrayType.SetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ elemEFace := unpackEFace(elem)
+ assertType("ArrayType.SetIndex argument 3", type2.pElemRType, elemEFace.rtype)
+ type2.UnsafeSetIndex(objEFace.data, index, elemEFace.data)
+}
+
+func (type2 *UnsafeArrayType) UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer) {
+ elemPtr := arrayAt(obj, index, type2.elemSize, "i < s.Len")
+ typedmemmove(type2.elemRType, elemPtr, elem)
+}
+
+func (type2 *UnsafeArrayType) GetIndex(obj interface{}, index int) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("ArrayType.GetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ elemPtr := type2.UnsafeGetIndex(objEFace.data, index)
+ return packEFace(type2.pElemRType, elemPtr)
+}
+
+func (type2 *UnsafeArrayType) UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer {
+ return arrayAt(obj, index, type2.elemSize, "i < s.Len")
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_eface.go b/vendor/github.com/modern-go/reflect2/unsafe_eface.go
new file mode 100644
index 00000000..805010f3
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_eface.go
@@ -0,0 +1,59 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type eface struct {
+ rtype unsafe.Pointer
+ data unsafe.Pointer
+}
+
+func unpackEFace(obj interface{}) *eface {
+ return (*eface)(unsafe.Pointer(&obj))
+}
+
+func packEFace(rtype unsafe.Pointer, data unsafe.Pointer) interface{} {
+ var i interface{}
+ e := (*eface)(unsafe.Pointer(&i))
+ e.rtype = rtype
+ e.data = data
+ return i
+}
+
+type UnsafeEFaceType struct {
+ unsafeType
+}
+
+func newUnsafeEFaceType(cfg *frozenConfig, type1 reflect.Type) *UnsafeEFaceType {
+ return &UnsafeEFaceType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ }
+}
+
+func (type2 *UnsafeEFaceType) IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ objEFace := unpackEFace(obj)
+ assertType("Type.IsNil argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIsNil(objEFace.data)
+}
+
+func (type2 *UnsafeEFaceType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ if ptr == nil {
+ return true
+ }
+ return unpackEFace(*(*interface{})(ptr)).data == nil
+}
+
+func (type2 *UnsafeEFaceType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *UnsafeEFaceType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ return *(*interface{})(ptr)
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_field.go b/vendor/github.com/modern-go/reflect2/unsafe_field.go
new file mode 100644
index 00000000..5eb53130
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_field.go
@@ -0,0 +1,74 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type UnsafeStructField struct {
+ reflect.StructField
+ structType *UnsafeStructType
+ rtype unsafe.Pointer
+ ptrRType unsafe.Pointer
+}
+
+func newUnsafeStructField(structType *UnsafeStructType, structField reflect.StructField) *UnsafeStructField {
+ return &UnsafeStructField{
+ StructField: structField,
+ rtype: unpackEFace(structField.Type).data,
+ ptrRType: unpackEFace(reflect.PtrTo(structField.Type)).data,
+ structType: structType,
+ }
+}
+
+func (field *UnsafeStructField) Offset() uintptr {
+ return field.StructField.Offset
+}
+
+func (field *UnsafeStructField) Name() string {
+ return field.StructField.Name
+}
+
+func (field *UnsafeStructField) PkgPath() string {
+ return field.StructField.PkgPath
+}
+
+func (field *UnsafeStructField) Type() Type {
+ return field.structType.cfg.Type2(field.StructField.Type)
+}
+
+func (field *UnsafeStructField) Tag() reflect.StructTag {
+ return field.StructField.Tag
+}
+
+func (field *UnsafeStructField) Index() []int {
+ return field.StructField.Index
+}
+
+func (field *UnsafeStructField) Anonymous() bool {
+ return field.StructField.Anonymous
+}
+
+func (field *UnsafeStructField) Set(obj interface{}, value interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("StructField.SetIndex argument 1", field.structType.ptrRType, objEFace.rtype)
+ valueEFace := unpackEFace(value)
+ assertType("StructField.SetIndex argument 2", field.ptrRType, valueEFace.rtype)
+ field.UnsafeSet(objEFace.data, valueEFace.data)
+}
+
+func (field *UnsafeStructField) UnsafeSet(obj unsafe.Pointer, value unsafe.Pointer) {
+ fieldPtr := add(obj, field.StructField.Offset, "same as non-reflect &v.field")
+ typedmemmove(field.rtype, fieldPtr, value)
+}
+
+func (field *UnsafeStructField) Get(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("StructField.GetIndex argument 1", field.structType.ptrRType, objEFace.rtype)
+ value := field.UnsafeGet(objEFace.data)
+ return packEFace(field.ptrRType, value)
+}
+
+func (field *UnsafeStructField) UnsafeGet(obj unsafe.Pointer) unsafe.Pointer {
+ return add(obj, field.StructField.Offset, "same as non-reflect &v.field")
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_iface.go b/vendor/github.com/modern-go/reflect2/unsafe_iface.go
new file mode 100644
index 00000000..b6019553
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_iface.go
@@ -0,0 +1,64 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type iface struct {
+ itab *itab
+ data unsafe.Pointer
+}
+
+type itab struct {
+ ignore unsafe.Pointer
+ rtype unsafe.Pointer
+}
+
+func IFaceToEFace(ptr unsafe.Pointer) interface{} {
+ iface := (*iface)(ptr)
+ if iface.itab == nil {
+ return nil
+ }
+ return packEFace(iface.itab.rtype, iface.data)
+}
+
+type UnsafeIFaceType struct {
+ unsafeType
+}
+
+func newUnsafeIFaceType(cfg *frozenConfig, type1 reflect.Type) *UnsafeIFaceType {
+ return &UnsafeIFaceType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ }
+}
+
+func (type2 *UnsafeIFaceType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *UnsafeIFaceType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ return IFaceToEFace(ptr)
+}
+
+func (type2 *UnsafeIFaceType) IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ objEFace := unpackEFace(obj)
+ assertType("Type.IsNil argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIsNil(objEFace.data)
+}
+
+func (type2 *UnsafeIFaceType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ if ptr == nil {
+ return true
+ }
+ iface := (*iface)(ptr)
+ if iface.itab == nil {
+ return true
+ }
+ return false
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_link.go b/vendor/github.com/modern-go/reflect2/unsafe_link.go
new file mode 100644
index 00000000..57229c8d
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_link.go
@@ -0,0 +1,70 @@
+package reflect2
+
+import "unsafe"
+
+//go:linkname unsafe_New reflect.unsafe_New
+func unsafe_New(rtype unsafe.Pointer) unsafe.Pointer
+
+//go:linkname typedmemmove reflect.typedmemmove
+func typedmemmove(rtype unsafe.Pointer, dst, src unsafe.Pointer)
+
+//go:linkname unsafe_NewArray reflect.unsafe_NewArray
+func unsafe_NewArray(rtype unsafe.Pointer, length int) unsafe.Pointer
+
+// typedslicecopy copies a slice of elemType values from src to dst,
+// returning the number of elements copied.
+//go:linkname typedslicecopy reflect.typedslicecopy
+//go:noescape
+func typedslicecopy(elemType unsafe.Pointer, dst, src sliceHeader) int
+
+//go:linkname mapassign reflect.mapassign
+//go:noescape
+func mapassign(rtype unsafe.Pointer, m unsafe.Pointer, key, val unsafe.Pointer)
+
+//go:linkname mapaccess reflect.mapaccess
+//go:noescape
+func mapaccess(rtype unsafe.Pointer, m unsafe.Pointer, key unsafe.Pointer) (val unsafe.Pointer)
+
+// m escapes into the return value, but the caller of mapiterinit
+// doesn't let the return value escape.
+//go:noescape
+//go:linkname mapiterinit reflect.mapiterinit
+func mapiterinit(rtype unsafe.Pointer, m unsafe.Pointer) *hiter
+
+//go:noescape
+//go:linkname mapiternext reflect.mapiternext
+func mapiternext(it *hiter)
+
+//go:linkname ifaceE2I reflect.ifaceE2I
+func ifaceE2I(rtype unsafe.Pointer, src interface{}, dst unsafe.Pointer)
+
+// A hash iteration structure.
+// If you modify hiter, also change cmd/internal/gc/reflect.go to indicate
+// the layout of this structure.
+type hiter struct {
+ key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/internal/gc/range.go).
+ value unsafe.Pointer // Must be in second position (see cmd/internal/gc/range.go).
+ // rest fields are ignored
+}
+
+// add returns p+x.
+//
+// The whySafe string is ignored, so that the function still inlines
+// as efficiently as p+x, but all call sites should use the string to
+// record why the addition is safe, which is to say why the addition
+// does not cause x to advance to the very end of p's allocation
+// and therefore point incorrectly at the next block in memory.
+func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
+ return unsafe.Pointer(uintptr(p) + x)
+}
+
+// arrayAt returns the i-th element of p,
+// an array whose elements are eltSize bytes wide.
+// The array pointed at by p must have at least i+1 elements:
+// it is invalid (but impossible to check here) to pass i >= len,
+// because then the result will point outside the array.
+// whySafe must explain why i < len. (Passing "i < len" is fine;
+// the benefit is to surface this assumption at the call site.)
+func arrayAt(p unsafe.Pointer, i int, eltSize uintptr, whySafe string) unsafe.Pointer {
+ return add(p, uintptr(i)*eltSize, "i < len")
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_map.go b/vendor/github.com/modern-go/reflect2/unsafe_map.go
new file mode 100644
index 00000000..f2e76e6b
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_map.go
@@ -0,0 +1,138 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type UnsafeMapType struct {
+ unsafeType
+ pKeyRType unsafe.Pointer
+ pElemRType unsafe.Pointer
+}
+
+func newUnsafeMapType(cfg *frozenConfig, type1 reflect.Type) MapType {
+ return &UnsafeMapType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ pKeyRType: unpackEFace(reflect.PtrTo(type1.Key())).data,
+ pElemRType: unpackEFace(reflect.PtrTo(type1.Elem())).data,
+ }
+}
+
+func (type2 *UnsafeMapType) IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ objEFace := unpackEFace(obj)
+ assertType("Type.IsNil argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIsNil(objEFace.data)
+}
+
+func (type2 *UnsafeMapType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ if ptr == nil {
+ return true
+ }
+ return *(*unsafe.Pointer)(ptr) == nil
+}
+
+func (type2 *UnsafeMapType) LikePtr() bool {
+ return true
+}
+
+func (type2 *UnsafeMapType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("MapType.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *UnsafeMapType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ return packEFace(type2.rtype, *(*unsafe.Pointer)(ptr))
+}
+
+func (type2 *UnsafeMapType) Key() Type {
+ return type2.cfg.Type2(type2.Type.Key())
+}
+
+func (type2 *UnsafeMapType) MakeMap(cap int) interface{} {
+ return packEFace(type2.ptrRType, type2.UnsafeMakeMap(cap))
+}
+
+func (type2 *UnsafeMapType) UnsafeMakeMap(cap int) unsafe.Pointer {
+ m := makeMapWithSize(type2.rtype, cap)
+ return unsafe.Pointer(&m)
+}
+
+func (type2 *UnsafeMapType) SetIndex(obj interface{}, key interface{}, elem interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("MapType.SetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ keyEFace := unpackEFace(key)
+ assertType("MapType.SetIndex argument 2", type2.pKeyRType, keyEFace.rtype)
+ elemEFace := unpackEFace(elem)
+ assertType("MapType.SetIndex argument 3", type2.pElemRType, elemEFace.rtype)
+ type2.UnsafeSetIndex(objEFace.data, keyEFace.data, elemEFace.data)
+}
+
+func (type2 *UnsafeMapType) UnsafeSetIndex(obj unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer) {
+ mapassign(type2.rtype, *(*unsafe.Pointer)(obj), key, elem)
+}
+
+func (type2 *UnsafeMapType) TryGetIndex(obj interface{}, key interface{}) (interface{}, bool) {
+ objEFace := unpackEFace(obj)
+ assertType("MapType.TryGetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ keyEFace := unpackEFace(key)
+ assertType("MapType.TryGetIndex argument 2", type2.pKeyRType, keyEFace.rtype)
+ elemPtr := type2.UnsafeGetIndex(objEFace.data, keyEFace.data)
+ if elemPtr == nil {
+ return nil, false
+ }
+ return packEFace(type2.pElemRType, elemPtr), true
+}
+
+func (type2 *UnsafeMapType) GetIndex(obj interface{}, key interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("MapType.GetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ keyEFace := unpackEFace(key)
+ assertType("MapType.GetIndex argument 2", type2.pKeyRType, keyEFace.rtype)
+ elemPtr := type2.UnsafeGetIndex(objEFace.data, keyEFace.data)
+ return packEFace(type2.pElemRType, elemPtr)
+}
+
+func (type2 *UnsafeMapType) UnsafeGetIndex(obj unsafe.Pointer, key unsafe.Pointer) unsafe.Pointer {
+ return mapaccess(type2.rtype, *(*unsafe.Pointer)(obj), key)
+}
+
+func (type2 *UnsafeMapType) Iterate(obj interface{}) MapIterator {
+ objEFace := unpackEFace(obj)
+ assertType("MapType.Iterate argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIterate(objEFace.data)
+}
+
+func (type2 *UnsafeMapType) UnsafeIterate(obj unsafe.Pointer) MapIterator {
+ return &UnsafeMapIterator{
+ hiter: mapiterinit(type2.rtype, *(*unsafe.Pointer)(obj)),
+ pKeyRType: type2.pKeyRType,
+ pElemRType: type2.pElemRType,
+ }
+}
+
+type UnsafeMapIterator struct {
+ *hiter
+ pKeyRType unsafe.Pointer
+ pElemRType unsafe.Pointer
+}
+
+func (iter *UnsafeMapIterator) HasNext() bool {
+ return iter.key != nil
+}
+
+func (iter *UnsafeMapIterator) Next() (interface{}, interface{}) {
+ key, elem := iter.UnsafeNext()
+ return packEFace(iter.pKeyRType, key), packEFace(iter.pElemRType, elem)
+}
+
+func (iter *UnsafeMapIterator) UnsafeNext() (unsafe.Pointer, unsafe.Pointer) {
+ key := iter.key
+ elem := iter.value
+ mapiternext(iter.hiter)
+ return key, elem
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_ptr.go b/vendor/github.com/modern-go/reflect2/unsafe_ptr.go
new file mode 100644
index 00000000..8e5ec9cf
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_ptr.go
@@ -0,0 +1,46 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type UnsafePtrType struct {
+ unsafeType
+}
+
+func newUnsafePtrType(cfg *frozenConfig, type1 reflect.Type) *UnsafePtrType {
+ return &UnsafePtrType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ }
+}
+
+func (type2 *UnsafePtrType) IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ objEFace := unpackEFace(obj)
+ assertType("Type.IsNil argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIsNil(objEFace.data)
+}
+
+func (type2 *UnsafePtrType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ if ptr == nil {
+ return true
+ }
+ return *(*unsafe.Pointer)(ptr) == nil
+}
+
+func (type2 *UnsafePtrType) LikePtr() bool {
+ return true
+}
+
+func (type2 *UnsafePtrType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *UnsafePtrType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ return packEFace(type2.rtype, *(*unsafe.Pointer)(ptr))
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_slice.go b/vendor/github.com/modern-go/reflect2/unsafe_slice.go
new file mode 100644
index 00000000..1c6d876c
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_slice.go
@@ -0,0 +1,177 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+// sliceHeader is a safe version of SliceHeader used within this package.
+type sliceHeader struct {
+ Data unsafe.Pointer
+ Len int
+ Cap int
+}
+
+type UnsafeSliceType struct {
+ unsafeType
+ elemRType unsafe.Pointer
+ pElemRType unsafe.Pointer
+ elemSize uintptr
+}
+
+func newUnsafeSliceType(cfg *frozenConfig, type1 reflect.Type) SliceType {
+ elemType := type1.Elem()
+ return &UnsafeSliceType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ pElemRType: unpackEFace(reflect.PtrTo(elemType)).data,
+ elemRType: unpackEFace(elemType).data,
+ elemSize: elemType.Size(),
+ }
+}
+
+func (type2 *UnsafeSliceType) Set(obj interface{}, val interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Set argument 1", type2.ptrRType, objEFace.rtype)
+ valEFace := unpackEFace(val)
+ assertType("Type.Set argument 2", type2.ptrRType, valEFace.rtype)
+ type2.UnsafeSet(objEFace.data, valEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer) {
+ *(*sliceHeader)(ptr) = *(*sliceHeader)(val)
+}
+
+func (type2 *UnsafeSliceType) IsNil(obj interface{}) bool {
+ if obj == nil {
+ return true
+ }
+ objEFace := unpackEFace(obj)
+ assertType("Type.IsNil argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIsNil(objEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ if ptr == nil {
+ return true
+ }
+ return (*sliceHeader)(ptr).Data == nil
+}
+
+func (type2 *UnsafeSliceType) SetNil(obj interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.SetNil argument 1", type2.ptrRType, objEFace.rtype)
+ type2.UnsafeSetNil(objEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeSetNil(ptr unsafe.Pointer) {
+ header := (*sliceHeader)(ptr)
+ header.Len = 0
+ header.Cap = 0
+ header.Data = nil
+}
+
+func (type2 *UnsafeSliceType) MakeSlice(length int, cap int) interface{} {
+ return packEFace(type2.ptrRType, type2.UnsafeMakeSlice(length, cap))
+}
+
+func (type2 *UnsafeSliceType) UnsafeMakeSlice(length int, cap int) unsafe.Pointer {
+ header := &sliceHeader{unsafe_NewArray(type2.elemRType, cap), length, cap}
+ return unsafe.Pointer(header)
+}
+
+func (type2 *UnsafeSliceType) LengthOf(obj interface{}) int {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.Len argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeLengthOf(objEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeLengthOf(obj unsafe.Pointer) int {
+ header := (*sliceHeader)(obj)
+ return header.Len
+}
+
+func (type2 *UnsafeSliceType) SetIndex(obj interface{}, index int, elem interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.SetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ elemEFace := unpackEFace(elem)
+ assertType("SliceType.SetIndex argument 3", type2.pElemRType, elemEFace.rtype)
+ type2.UnsafeSetIndex(objEFace.data, index, elemEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeSetIndex(obj unsafe.Pointer, index int, elem unsafe.Pointer) {
+ header := (*sliceHeader)(obj)
+ elemPtr := arrayAt(header.Data, index, type2.elemSize, "i < s.Len")
+ typedmemmove(type2.elemRType, elemPtr, elem)
+}
+
+func (type2 *UnsafeSliceType) GetIndex(obj interface{}, index int) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.GetIndex argument 1", type2.ptrRType, objEFace.rtype)
+ elemPtr := type2.UnsafeGetIndex(objEFace.data, index)
+ return packEFace(type2.pElemRType, elemPtr)
+}
+
+func (type2 *UnsafeSliceType) UnsafeGetIndex(obj unsafe.Pointer, index int) unsafe.Pointer {
+ header := (*sliceHeader)(obj)
+ return arrayAt(header.Data, index, type2.elemSize, "i < s.Len")
+}
+
+func (type2 *UnsafeSliceType) Append(obj interface{}, elem interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.Append argument 1", type2.ptrRType, objEFace.rtype)
+ elemEFace := unpackEFace(elem)
+ assertType("SliceType.Append argument 2", type2.pElemRType, elemEFace.rtype)
+ type2.UnsafeAppend(objEFace.data, elemEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeAppend(obj unsafe.Pointer, elem unsafe.Pointer) {
+ header := (*sliceHeader)(obj)
+ oldLen := header.Len
+ type2.UnsafeGrow(obj, oldLen+1)
+ type2.UnsafeSetIndex(obj, oldLen, elem)
+}
+
+func (type2 *UnsafeSliceType) Cap(obj interface{}) int {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.Cap argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeCap(objEFace.data)
+}
+
+func (type2 *UnsafeSliceType) UnsafeCap(ptr unsafe.Pointer) int {
+ return (*sliceHeader)(ptr).Cap
+}
+
+func (type2 *UnsafeSliceType) Grow(obj interface{}, newLength int) {
+ objEFace := unpackEFace(obj)
+ assertType("SliceType.Grow argument 1", type2.ptrRType, objEFace.rtype)
+ type2.UnsafeGrow(objEFace.data, newLength)
+}
+
+func (type2 *UnsafeSliceType) UnsafeGrow(obj unsafe.Pointer, newLength int) {
+ header := (*sliceHeader)(obj)
+ if newLength <= header.Cap {
+ header.Len = newLength
+ return
+ }
+ newCap := calcNewCap(header.Cap, newLength)
+ newHeader := (*sliceHeader)(type2.UnsafeMakeSlice(header.Len, newCap))
+ typedslicecopy(type2.elemRType, *newHeader, *header)
+ header.Data = newHeader.Data
+ header.Cap = newHeader.Cap
+ header.Len = newLength
+}
+
+func calcNewCap(cap int, expectedCap int) int {
+ if cap == 0 {
+ cap = expectedCap
+ } else {
+ for cap < expectedCap {
+ if cap < 1024 {
+ cap += cap
+ } else {
+ cap += cap / 4
+ }
+ }
+ }
+ return cap
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_struct.go b/vendor/github.com/modern-go/reflect2/unsafe_struct.go
new file mode 100644
index 00000000..804d9166
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_struct.go
@@ -0,0 +1,59 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type UnsafeStructType struct {
+ unsafeType
+ likePtr bool
+}
+
+func newUnsafeStructType(cfg *frozenConfig, type1 reflect.Type) *UnsafeStructType {
+ return &UnsafeStructType{
+ unsafeType: *newUnsafeType(cfg, type1),
+ likePtr: likePtrType(type1),
+ }
+}
+
+func (type2 *UnsafeStructType) LikePtr() bool {
+ return type2.likePtr
+}
+
+func (type2 *UnsafeStructType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *UnsafeStructType) UnsafeIndirect(ptr unsafe.Pointer) interface{} {
+ if type2.likePtr {
+ return packEFace(type2.rtype, *(*unsafe.Pointer)(ptr))
+ }
+ return packEFace(type2.rtype, ptr)
+}
+
+func (type2 *UnsafeStructType) FieldByName(name string) StructField {
+ structField, found := type2.Type.FieldByName(name)
+ if !found {
+ return nil
+ }
+ return newUnsafeStructField(type2, structField)
+}
+
+func (type2 *UnsafeStructType) Field(i int) StructField {
+ return newUnsafeStructField(type2, type2.Type.Field(i))
+}
+
+func (type2 *UnsafeStructType) FieldByIndex(index []int) StructField {
+ return newUnsafeStructField(type2, type2.Type.FieldByIndex(index))
+}
+
+func (type2 *UnsafeStructType) FieldByNameFunc(match func(string) bool) StructField {
+ structField, found := type2.Type.FieldByNameFunc(match)
+ if !found {
+ panic("field match condition not found in " + type2.Type.String())
+ }
+ return newUnsafeStructField(type2, structField)
+}
diff --git a/vendor/github.com/modern-go/reflect2/unsafe_type.go b/vendor/github.com/modern-go/reflect2/unsafe_type.go
new file mode 100644
index 00000000..13941716
--- /dev/null
+++ b/vendor/github.com/modern-go/reflect2/unsafe_type.go
@@ -0,0 +1,85 @@
+package reflect2
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+type unsafeType struct {
+ safeType
+ rtype unsafe.Pointer
+ ptrRType unsafe.Pointer
+}
+
+func newUnsafeType(cfg *frozenConfig, type1 reflect.Type) *unsafeType {
+ return &unsafeType{
+ safeType: safeType{
+ Type: type1,
+ cfg: cfg,
+ },
+ rtype: unpackEFace(type1).data,
+ ptrRType: unpackEFace(reflect.PtrTo(type1)).data,
+ }
+}
+
+func (type2 *unsafeType) Set(obj interface{}, val interface{}) {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Set argument 1", type2.ptrRType, objEFace.rtype)
+ valEFace := unpackEFace(val)
+ assertType("Type.Set argument 2", type2.ptrRType, valEFace.rtype)
+ type2.UnsafeSet(objEFace.data, valEFace.data)
+}
+
+func (type2 *unsafeType) UnsafeSet(ptr unsafe.Pointer, val unsafe.Pointer) {
+ typedmemmove(type2.rtype, ptr, val)
+}
+
+func (type2 *unsafeType) IsNil(obj interface{}) bool {
+ objEFace := unpackEFace(obj)
+ assertType("Type.IsNil argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIsNil(objEFace.data)
+}
+
+func (type2 *unsafeType) UnsafeIsNil(ptr unsafe.Pointer) bool {
+ return ptr == nil
+}
+
+func (type2 *unsafeType) UnsafeNew() unsafe.Pointer {
+ return unsafe_New(type2.rtype)
+}
+
+func (type2 *unsafeType) New() interface{} {
+ return packEFace(type2.ptrRType, type2.UnsafeNew())
+}
+
+func (type2 *unsafeType) PackEFace(ptr unsafe.Pointer) interface{} {
+ return packEFace(type2.ptrRType, ptr)
+}
+
+func (type2 *unsafeType) RType() uintptr {
+ return uintptr(type2.rtype)
+}
+
+func (type2 *unsafeType) Indirect(obj interface{}) interface{} {
+ objEFace := unpackEFace(obj)
+ assertType("Type.Indirect argument 1", type2.ptrRType, objEFace.rtype)
+ return type2.UnsafeIndirect(objEFace.data)
+}
+
+func (type2 *unsafeType) UnsafeIndirect(obj unsafe.Pointer) interface{} {
+ return packEFace(type2.rtype, obj)
+}
+
+func (type2 *unsafeType) LikePtr() bool {
+ return false
+}
+
+func assertType(where string, expectRType unsafe.Pointer, actualRType unsafe.Pointer) {
+ if expectRType != actualRType {
+ expectType := reflect.TypeOf(0)
+ (*iface)(unsafe.Pointer(&expectType)).data = expectRType
+ actualType := reflect.TypeOf(0)
+ (*iface)(unsafe.Pointer(&actualType)).data = actualRType
+ panic(where + ": expect " + expectType.String() + ", actual " + actualType.String())
+ }
+}
diff --git a/vendor/github.com/philhofer/fwd/reader.go b/vendor/github.com/philhofer/fwd/reader.go
index 75be62ab..72cc112b 100644
--- a/vendor/github.com/philhofer/fwd/reader.go
+++ b/vendor/github.com/philhofer/fwd/reader.go
@@ -34,7 +34,10 @@
//
package fwd
-import "io"
+import (
+ "io"
+ "os"
+)
const (
// DefaultReaderSize is the default size of the read buffer
@@ -50,11 +53,24 @@ func NewReader(r io.Reader) *Reader {
}
// NewReaderSize returns a new *Reader that
-// reads from 'r' and has a buffer size 'n'
+// reads from 'r' and has a buffer size 'n'.
func NewReaderSize(r io.Reader, n int) *Reader {
+ buf := make([]byte, 0, max(n, minReaderSize))
+ return NewReaderBuf(r, buf)
+}
+
+// NewReaderBuf returns a new *Reader that
+// reads from 'r' and uses 'buf' as a buffer.
+// 'buf' is not used when has smaller capacity than 16,
+// custom buffer is allocated instead.
+func NewReaderBuf(r io.Reader, buf []byte) *Reader {
+ if cap(buf) < minReaderSize {
+ buf = make([]byte, 0, minReaderSize)
+ }
+ buf = buf[:0]
rd := &Reader{
r: r,
- data: make([]byte, 0, max(minReaderSize, n)),
+ data: buf,
}
if s, ok := r.(io.Seeker); ok {
rd.rs = s
@@ -174,6 +190,19 @@ func (r *Reader) Peek(n int) ([]byte, error) {
return r.data[r.n : r.n+n], nil
}
+// discard(n) discards up to 'n' buffered bytes, and
+// and returns the number of bytes discarded
+func (r *Reader) discard(n int) int {
+ inbuf := r.buffered()
+ if inbuf <= n {
+ r.n = 0
+ r.data = r.data[:0]
+ return inbuf
+ }
+ r.n += n
+ return n
+}
+
// Skip moves the reader forward 'n' bytes.
// Returns the number of bytes skipped and any
// errors encountered. It is analogous to Seek(n, 1).
@@ -188,33 +217,25 @@ func (r *Reader) Peek(n int) ([]byte, error) {
// will not return `io.EOF` until the next call
// to Read.)
func (r *Reader) Skip(n int) (int, error) {
-
- // fast path
- if r.buffered() >= n {
- r.n += n
- return n, nil
+ if n < 0 {
+ return 0, os.ErrInvalid
}
- // use seeker implementation
- // if we can
- if r.rs != nil {
- return r.skipSeek(n)
- }
+ // discard some or all of the current buffer
+ skipped := r.discard(n)
- // loop on filling
- // and then erasing
- o := n
- for r.buffered() < n && r.state == nil {
+ // if we can Seek() through the remaining bytes, do that
+ if n > skipped && r.rs != nil {
+ nn, err := r.rs.Seek(int64(n-skipped), 1)
+ return int(nn) + skipped, err
+ }
+ // otherwise, keep filling the buffer
+ // and discarding it up to 'n'
+ for skipped < n && r.state == nil {
r.more()
- // we can skip forward
- // up to r.buffered() bytes
- step := min(r.buffered(), n)
- r.n += step
- n -= step
+ skipped += r.discard(n - skipped)
}
- // at this point, n should be
- // 0 if everything went smoothly
- return o - n, r.noEOF()
+ return skipped, r.noEOF()
}
// Next returns the next 'n' bytes in the stream.
@@ -249,20 +270,6 @@ func (r *Reader) Next(n int) ([]byte, error) {
return out, nil
}
-// skipSeek uses the io.Seeker to seek forward.
-// only call this function when n > r.buffered()
-func (r *Reader) skipSeek(n int) (int, error) {
- o := r.buffered()
- // first, clear buffer
- n -= o
- r.n = 0
- r.data = r.data[:0]
-
- // then seek forward remaning bytes
- i, err := r.rs.Seek(int64(n), 1)
- return int(i) + o, err
-}
-
// Read implements `io.Reader`
func (r *Reader) Read(b []byte) (int, error) {
// if we have data in the buffer, just
@@ -368,13 +375,6 @@ func (r *Reader) WriteTo(w io.Writer) (int64, error) {
return i, nil
}
-func min(a int, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
func max(a int, b int) int {
if a < b {
return b
diff --git a/vendor/github.com/philhofer/fwd/writer.go b/vendor/github.com/philhofer/fwd/writer.go
index 2dc392a9..4d6ea15b 100644
--- a/vendor/github.com/philhofer/fwd/writer.go
+++ b/vendor/github.com/philhofer/fwd/writer.go
@@ -29,16 +29,28 @@ func NewWriter(w io.Writer) *Writer {
}
}
-// NewWriterSize returns a new writer
-// that writes to 'w' and has a buffer
-// that is 'size' bytes.
-func NewWriterSize(w io.Writer, size int) *Writer {
- if wr, ok := w.(*Writer); ok && cap(wr.buf) >= size {
+// NewWriterSize returns a new writer that
+// writes to 'w' and has a buffer size 'n'.
+func NewWriterSize(w io.Writer, n int) *Writer {
+ if wr, ok := w.(*Writer); ok && cap(wr.buf) >= n {
return wr
}
+ buf := make([]byte, 0, max(n, minWriterSize))
+ return NewWriterBuf(w, buf)
+}
+
+// NewWriterBuf returns a new writer
+// that writes to 'w' and has 'buf' as a buffer.
+// 'buf' is not used when has smaller capacity than 18,
+// custom buffer is allocated instead.
+func NewWriterBuf(w io.Writer, buf []byte) *Writer {
+ if cap(buf) < minWriterSize {
+ buf = make([]byte, 0, minWriterSize)
+ }
+ buf = buf[:0]
return &Writer{
w: w,
- buf: make([]byte, 0, max(size, minWriterSize)),
+ buf: buf,
}
}
diff --git a/vendor/github.com/rivo/uniseg/LICENSE.txt b/vendor/github.com/rivo/uniseg/LICENSE.txt
new file mode 100644
index 00000000..5040f1ef
--- /dev/null
+++ b/vendor/github.com/rivo/uniseg/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Oliver Kuederle
+
+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/rivo/uniseg/README.md b/vendor/github.com/rivo/uniseg/README.md
new file mode 100644
index 00000000..f8da293e
--- /dev/null
+++ b/vendor/github.com/rivo/uniseg/README.md
@@ -0,0 +1,62 @@
+# Unicode Text Segmentation for Go
+
+[![Godoc Reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/rivo/uniseg)
+[![Go Report](https://img.shields.io/badge/go%20report-A%2B-brightgreen.svg)](https://goreportcard.com/report/github.com/rivo/uniseg)
+
+This Go package implements Unicode Text Segmentation according to [Unicode Standard Annex #29](http://unicode.org/reports/tr29/) (Unicode version 12.0.0).
+
+At this point, only the determination of grapheme cluster boundaries is implemented.
+
+## Background
+
+In Go, [strings are read-only slices of bytes](https://blog.golang.org/strings). They can be turned into Unicode code points using the `for` loop or by casting: `[]rune(str)`. However, multiple code points may be combined into one user-perceived character or what the Unicode specification calls "grapheme cluster". Here are some examples:
+
+|String|Bytes (UTF-8)|Code points (runes)|Grapheme clusters|
+|-|-|-|-|
+|Kaฬˆse|6 bytes: `4b 61 cc 88 73 65`|5 code points: `4b 61 308 73 65`|4 clusters: `[4b],[61 308],[73],[65]`|
+|๐Ÿณ๏ธโ€๐ŸŒˆ|14 bytes: `f0 9f 8f b3 ef b8 8f e2 80 8d f0 9f 8c 88`|4 code points: `1f3f3 fe0f 200d 1f308`|1 cluster: `[1f3f3 fe0f 200d 1f308]`|
+|๐Ÿ‡ฉ๐Ÿ‡ช|8 bytes: `f0 9f 87 a9 f0 9f 87 aa`|2 code points: `1f1e9 1f1ea`|1 cluster: `[1f1e9 1f1ea]`|
+
+This package provides a tool to iterate over these grapheme clusters. This may be used to determine the number of user-perceived characters, to split strings in their intended places, or to extract individual characters which form a unit.
+
+## Installation
+
+```bash
+go get github.com/rivo/uniseg
+```
+
+## Basic Example
+
+```go
+package uniseg
+
+import (
+ "fmt"
+
+ "github.com/rivo/uniseg"
+)
+
+func main() {
+ gr := uniseg.NewGraphemes("๐Ÿ‘๐Ÿผ!")
+ for gr.Next() {
+ fmt.Printf("%x ", gr.Runes())
+ }
+ // Output: [1f44d 1f3fc] [21]
+}
+```
+
+## Documentation
+
+Refer to https://godoc.org/github.com/rivo/uniseg for the package's documentation.
+
+## Dependencies
+
+This package does not depend on any packages outside the standard library.
+
+## Your Feedback
+
+Add your issue here on GitHub. Feel free to get in touch if you have any questions.
+
+## Version
+
+Version tags will be introduced once Golang modules are official. Consider this version 0.1.
diff --git a/vendor/github.com/rivo/uniseg/doc.go b/vendor/github.com/rivo/uniseg/doc.go
new file mode 100644
index 00000000..60c737d7
--- /dev/null
+++ b/vendor/github.com/rivo/uniseg/doc.go
@@ -0,0 +1,8 @@
+/*
+Package uniseg implements Unicode Text Segmentation according to Unicode
+Standard Annex #29 (http://unicode.org/reports/tr29/).
+
+At this point, only the determination of grapheme cluster boundaries is
+implemented.
+*/
+package uniseg
diff --git a/vendor/github.com/rivo/uniseg/go.mod b/vendor/github.com/rivo/uniseg/go.mod
new file mode 100644
index 00000000..a54280b2
--- /dev/null
+++ b/vendor/github.com/rivo/uniseg/go.mod
@@ -0,0 +1,3 @@
+module github.com/rivo/uniseg
+
+go 1.12
diff --git a/vendor/github.com/rivo/uniseg/grapheme.go b/vendor/github.com/rivo/uniseg/grapheme.go
new file mode 100644
index 00000000..207157f5
--- /dev/null
+++ b/vendor/github.com/rivo/uniseg/grapheme.go
@@ -0,0 +1,268 @@
+package uniseg
+
+import "unicode/utf8"
+
+// The states of the grapheme cluster parser.
+const (
+ grAny = iota
+ grCR
+ grControlLF
+ grL
+ grLVV
+ grLVTT
+ grPrepend
+ grExtendedPictographic
+ grExtendedPictographicZWJ
+ grRIOdd
+ grRIEven
+)
+
+// The grapheme cluster parser's breaking instructions.
+const (
+ grNoBoundary = iota
+ grBoundary
+)
+
+// The grapheme cluster parser's state transitions. Maps (state, property) to
+// (new state, breaking instruction, rule number). The breaking instruction
+// always refers to the boundary between the last and next code point.
+//
+// This map is queried as follows:
+//
+// 1. Find specific state + specific property. Stop if found.
+// 2. Find specific state + any property.
+// 3. Find any state + specific property.
+// 4. If only (2) or (3) (but not both) was found, stop.
+// 5. If both (2) and (3) were found, use state and breaking instruction from
+// the transition with the lower rule number, prefer (3) if rule numbers
+// are equal. Stop.
+// 6. Assume grAny and grBoundary.
+var grTransitions = map[[2]int][3]int{
+ // GB5
+ {grAny, prCR}: {grCR, grBoundary, 50},
+ {grAny, prLF}: {grControlLF, grBoundary, 50},
+ {grAny, prControl}: {grControlLF, grBoundary, 50},
+
+ // GB4
+ {grCR, prAny}: {grAny, grBoundary, 40},
+ {grControlLF, prAny}: {grAny, grBoundary, 40},
+
+ // GB3.
+ {grCR, prLF}: {grAny, grNoBoundary, 30},
+
+ // GB6.
+ {grAny, prL}: {grL, grBoundary, 9990},
+ {grL, prL}: {grL, grNoBoundary, 60},
+ {grL, prV}: {grLVV, grNoBoundary, 60},
+ {grL, prLV}: {grLVV, grNoBoundary, 60},
+ {grL, prLVT}: {grLVTT, grNoBoundary, 60},
+
+ // GB7.
+ {grAny, prLV}: {grLVV, grBoundary, 9990},
+ {grAny, prV}: {grLVV, grBoundary, 9990},
+ {grLVV, prV}: {grLVV, grNoBoundary, 70},
+ {grLVV, prT}: {grLVTT, grNoBoundary, 70},
+
+ // GB8.
+ {grAny, prLVT}: {grLVTT, grBoundary, 9990},
+ {grAny, prT}: {grLVTT, grBoundary, 9990},
+ {grLVTT, prT}: {grLVTT, grNoBoundary, 80},
+
+ // GB9.
+ {grAny, prExtend}: {grAny, grNoBoundary, 90},
+ {grAny, prZWJ}: {grAny, grNoBoundary, 90},
+
+ // GB9a.
+ {grAny, prSpacingMark}: {grAny, grNoBoundary, 91},
+
+ // GB9b.
+ {grAny, prPreprend}: {grPrepend, grBoundary, 9990},
+ {grPrepend, prAny}: {grAny, grNoBoundary, 92},
+
+ // GB11.
+ {grAny, prExtendedPictographic}: {grExtendedPictographic, grBoundary, 9990},
+ {grExtendedPictographic, prExtend}: {grExtendedPictographic, grNoBoundary, 110},
+ {grExtendedPictographic, prZWJ}: {grExtendedPictographicZWJ, grNoBoundary, 110},
+ {grExtendedPictographicZWJ, prExtendedPictographic}: {grExtendedPictographic, grNoBoundary, 110},
+
+ // GB12 / GB13.
+ {grAny, prRegionalIndicator}: {grRIOdd, grBoundary, 9990},
+ {grRIOdd, prRegionalIndicator}: {grRIEven, grNoBoundary, 120},
+ {grRIEven, prRegionalIndicator}: {grRIOdd, grBoundary, 120},
+}
+
+// Graphemes implements an iterator over Unicode extended grapheme clusters,
+// specified in the Unicode Standard Annex #29. Grapheme clusters correspond to
+// "user-perceived characters". These characters often consist of multiple
+// code points (e.g. the "woman kissing woman" emoji consists of 8 code points:
+// woman + ZWJ + heavy black heart (2 code points) + ZWJ + kiss mark + ZWJ +
+// woman) and the rules described in Annex #29 must be applied to group those
+// code points into clusters perceived by the user as one character.
+type Graphemes struct {
+ // The code points over which this class iterates.
+ codePoints []rune
+
+ // The (byte-based) indices of the code points into the original string plus
+ // len(original string). Thus, len(indices) = len(codePoints) + 1.
+ indices []int
+
+ // The current grapheme cluster to be returned. These are indices into
+ // codePoints/indices. If start == end, we either haven't started iterating
+ // yet (0) or the iteration has already completed (1).
+ start, end int
+
+ // The index of the next code point to be parsed.
+ pos int
+
+ // The current state of the code point parser.
+ state int
+}
+
+// NewGraphemes returns a new grapheme cluster iterator.
+func NewGraphemes(s string) *Graphemes {
+ l := utf8.RuneCountInString(s)
+ codePoints := make([]rune, l)
+ indices := make([]int, l+1)
+ i := 0
+ for pos, r := range s {
+ codePoints[i] = r
+ indices[i] = pos
+ i++
+ }
+ indices[l] = len(s)
+ g := &Graphemes{
+ codePoints: codePoints,
+ indices: indices,
+ }
+ g.Next() // Parse ahead.
+ return g
+}
+
+// Next advances the iterator by one grapheme cluster and returns false if no
+// clusters are left. This function must be called before the first cluster is
+// accessed.
+func (g *Graphemes) Next() bool {
+ g.start = g.end
+
+ // The state transition gives us a boundary instruction BEFORE the next code
+ // point so we always need to stay ahead by one code point.
+
+ // Parse the next code point.
+ for g.pos <= len(g.codePoints) {
+ // GB2.
+ if g.pos == len(g.codePoints) {
+ g.end = g.pos
+ g.pos++
+ break
+ }
+
+ // Determine the property of the next character.
+ nextProperty := property(g.codePoints[g.pos])
+ g.pos++
+
+ // Find the applicable transition.
+ var boundary bool
+ transition, ok := grTransitions[[2]int{g.state, nextProperty}]
+ if ok {
+ // We have a specific transition. We'll use it.
+ g.state = transition[0]
+ boundary = transition[1] == grBoundary
+ } else {
+ // No specific transition found. Try the less specific ones.
+ transAnyProp, okAnyProp := grTransitions[[2]int{g.state, prAny}]
+ transAnyState, okAnyState := grTransitions[[2]int{grAny, nextProperty}]
+ if okAnyProp && okAnyState {
+ // Both apply. We'll use a mix (see comments for grTransitions).
+ g.state = transAnyState[0]
+ boundary = transAnyState[1] == grBoundary
+ if transAnyProp[2] < transAnyState[2] {
+ g.state = transAnyProp[0]
+ boundary = transAnyProp[1] == grBoundary
+ }
+ } else if okAnyProp {
+ // We only have a specific state.
+ g.state = transAnyProp[0]
+ boundary = transAnyProp[1] == grBoundary
+ // This branch will probably never be reached because okAnyState will
+ // always be true given the current transition map. But we keep it here
+ // for future modifications to the transition map where this may not be
+ // true anymore.
+ } else if okAnyState {
+ // We only have a specific property.
+ g.state = transAnyState[0]
+ boundary = transAnyState[1] == grBoundary
+ } else {
+ // No known transition. GB999: Any x Any.
+ g.state = grAny
+ boundary = true
+ }
+ }
+
+ // If we found a cluster boundary, let's stop here. The current cluster will
+ // be the one that just ended.
+ if g.pos-1 == 0 /* GB1 */ || boundary {
+ g.end = g.pos - 1
+ break
+ }
+ }
+
+ return g.start != g.end
+}
+
+// Runes returns a slice of runes (code points) which corresponds to the current
+// grapheme cluster. If the iterator is already past the end or Next() has not
+// yet been called, nil is returned.
+func (g *Graphemes) Runes() []rune {
+ if g.start == g.end {
+ return nil
+ }
+ return g.codePoints[g.start:g.end]
+}
+
+// Str returns a substring of the original string which corresponds to the
+// current grapheme cluster. If the iterator is already past the end or Next()
+// has not yet been called, an empty string is returned.
+func (g *Graphemes) Str() string {
+ if g.start == g.end {
+ return ""
+ }
+ return string(g.codePoints[g.start:g.end])
+}
+
+// Bytes returns a byte slice which corresponds to the current grapheme cluster.
+// If the iterator is already past the end or Next() has not yet been called,
+// nil is returned.
+func (g *Graphemes) Bytes() []byte {
+ if g.start == g.end {
+ return nil
+ }
+ return []byte(string(g.codePoints[g.start:g.end]))
+}
+
+// Positions returns the interval of the current grapheme cluster as byte
+// positions into the original string. The first returned value "from" indexes
+// the first byte and the second returned value "to" indexes the first byte that
+// is not included anymore, i.e. str[from:to] is the current grapheme cluster of
+// the original string "str". If Next() has not yet been called, both values are
+// 0. If the iterator is already past the end, both values are 1.
+func (g *Graphemes) Positions() (int, int) {
+ return g.indices[g.start], g.indices[g.end]
+}
+
+// Reset puts the iterator into its initial state such that the next call to
+// Next() sets it to the first grapheme cluster again.
+func (g *Graphemes) Reset() {
+ g.start, g.end, g.pos, g.state = 0, 0, 0, grAny
+ g.Next() // Parse ahead again.
+}
+
+// GraphemeClusterCount returns the number of user-perceived characters
+// (grapheme clusters) for the given string. To calculate this number, it
+// iterates through the string using the Graphemes iterator.
+func GraphemeClusterCount(s string) (n int) {
+ g := NewGraphemes(s)
+ for g.Next() {
+ n++
+ }
+ return
+}
diff --git a/vendor/github.com/rivo/uniseg/properties.go b/vendor/github.com/rivo/uniseg/properties.go
new file mode 100644
index 00000000..a75ab588
--- /dev/null
+++ b/vendor/github.com/rivo/uniseg/properties.go
@@ -0,0 +1,1658 @@
+package uniseg
+
+// The unicode properties. Only the ones needed in the context of this package
+// are included.
+const (
+ prAny = iota
+ prPreprend
+ prCR
+ prLF
+ prControl
+ prExtend
+ prRegionalIndicator
+ prSpacingMark
+ prL
+ prV
+ prT
+ prLV
+ prLVT
+ prZWJ
+ prExtendedPictographic
+)
+
+// Maps code point ranges to their properties. In the context of this package,
+// any code point that is not contained may map to "prAny". The code point
+// ranges in this slice are numerically sorted.
+//
+// These ranges were taken from
+// http://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt
+// as well as
+// https://unicode.org/Public/emoji/latest/emoji-data.txt
+// ("Extended_Pictographic" only) on March 11, 2019. See
+// https://www.unicode.org/license.html for the Unicode license agreement.
+var codePoints = [][3]int{
+ {0x0000, 0x0009, prControl}, // Cc [10] <control-0000>..<control-0009>
+ {0x000A, 0x000A, prLF}, // Cc <control-000A>
+ {0x000B, 0x000C, prControl}, // Cc [2] <control-000B>..<control-000C>
+ {0x000D, 0x000D, prCR}, // Cc <control-000D>
+ {0x000E, 0x001F, prControl}, // Cc [18] <control-000E>..<control-001F>
+ {0x007F, 0x009F, prControl}, // Cc [33] <control-007F>..<control-009F>
+ {0x00A9, 0x00A9, prExtendedPictographic}, // 1.1 [1] (ยฉ๏ธ) copyright
+ {0x00AD, 0x00AD, prControl}, // Cf SOFT HYPHEN
+ {0x00AE, 0x00AE, prExtendedPictographic}, // 1.1 [1] (ยฎ๏ธ) registered
+ {0x0300, 0x036F, prExtend}, // Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X
+ {0x0483, 0x0487, prExtend}, // Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE
+ {0x0488, 0x0489, prExtend}, // Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN
+ {0x0591, 0x05BD, prExtend}, // Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG
+ {0x05BF, 0x05BF, prExtend}, // Mn HEBREW POINT RAFE
+ {0x05C1, 0x05C2, prExtend}, // Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT
+ {0x05C4, 0x05C5, prExtend}, // Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT
+ {0x05C7, 0x05C7, prExtend}, // Mn HEBREW POINT QAMATS QATAN
+ {0x0600, 0x0605, prPreprend}, // Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE
+ {0x0610, 0x061A, prExtend}, // Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA
+ {0x061C, 0x061C, prControl}, // Cf ARABIC LETTER MARK
+ {0x064B, 0x065F, prExtend}, // Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW
+ {0x0670, 0x0670, prExtend}, // Mn ARABIC LETTER SUPERSCRIPT ALEF
+ {0x06D6, 0x06DC, prExtend}, // Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN
+ {0x06DD, 0x06DD, prPreprend}, // Cf ARABIC END OF AYAH
+ {0x06DF, 0x06E4, prExtend}, // Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA
+ {0x06E7, 0x06E8, prExtend}, // Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON
+ {0x06EA, 0x06ED, prExtend}, // Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM
+ {0x070F, 0x070F, prPreprend}, // Cf SYRIAC ABBREVIATION MARK
+ {0x0711, 0x0711, prExtend}, // Mn SYRIAC LETTER SUPERSCRIPT ALAPH
+ {0x0730, 0x074A, prExtend}, // Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH
+ {0x07A6, 0x07B0, prExtend}, // Mn [11] THAANA ABAFILI..THAANA SUKUN
+ {0x07EB, 0x07F3, prExtend}, // Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE
+ {0x07FD, 0x07FD, prExtend}, // Mn NKO DANTAYALAN
+ {0x0816, 0x0819, prExtend}, // Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH
+ {0x081B, 0x0823, prExtend}, // Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A
+ {0x0825, 0x0827, prExtend}, // Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U
+ {0x0829, 0x082D, prExtend}, // Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA
+ {0x0859, 0x085B, prExtend}, // Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK
+ {0x08D3, 0x08E1, prExtend}, // Mn [15] ARABIC SMALL LOW WAW..ARABIC SMALL HIGH SIGN SAFHA
+ {0x08E2, 0x08E2, prPreprend}, // Cf ARABIC DISPUTED END OF AYAH
+ {0x08E3, 0x0902, prExtend}, // Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA
+ {0x0903, 0x0903, prSpacingMark}, // Mc DEVANAGARI SIGN VISARGA
+ {0x093A, 0x093A, prExtend}, // Mn DEVANAGARI VOWEL SIGN OE
+ {0x093B, 0x093B, prSpacingMark}, // Mc DEVANAGARI VOWEL SIGN OOE
+ {0x093C, 0x093C, prExtend}, // Mn DEVANAGARI SIGN NUKTA
+ {0x093E, 0x0940, prSpacingMark}, // Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II
+ {0x0941, 0x0948, prExtend}, // Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI
+ {0x0949, 0x094C, prSpacingMark}, // Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU
+ {0x094D, 0x094D, prExtend}, // Mn DEVANAGARI SIGN VIRAMA
+ {0x094E, 0x094F, prSpacingMark}, // Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW
+ {0x0951, 0x0957, prExtend}, // Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE
+ {0x0962, 0x0963, prExtend}, // Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL
+ {0x0981, 0x0981, prExtend}, // Mn BENGALI SIGN CANDRABINDU
+ {0x0982, 0x0983, prSpacingMark}, // Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA
+ {0x09BC, 0x09BC, prExtend}, // Mn BENGALI SIGN NUKTA
+ {0x09BE, 0x09BE, prExtend}, // Mc BENGALI VOWEL SIGN AA
+ {0x09BF, 0x09C0, prSpacingMark}, // Mc [2] BENGALI VOWEL SIGN I..BENGALI VOWEL SIGN II
+ {0x09C1, 0x09C4, prExtend}, // Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR
+ {0x09C7, 0x09C8, prSpacingMark}, // Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI
+ {0x09CB, 0x09CC, prSpacingMark}, // Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU
+ {0x09CD, 0x09CD, prExtend}, // Mn BENGALI SIGN VIRAMA
+ {0x09D7, 0x09D7, prExtend}, // Mc BENGALI AU LENGTH MARK
+ {0x09E2, 0x09E3, prExtend}, // Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL
+ {0x09FE, 0x09FE, prExtend}, // Mn BENGALI SANDHI MARK
+ {0x0A01, 0x0A02, prExtend}, // Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI
+ {0x0A03, 0x0A03, prSpacingMark}, // Mc GURMUKHI SIGN VISARGA
+ {0x0A3C, 0x0A3C, prExtend}, // Mn GURMUKHI SIGN NUKTA
+ {0x0A3E, 0x0A40, prSpacingMark}, // Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II
+ {0x0A41, 0x0A42, prExtend}, // Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU
+ {0x0A47, 0x0A48, prExtend}, // Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI
+ {0x0A4B, 0x0A4D, prExtend}, // Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA
+ {0x0A51, 0x0A51, prExtend}, // Mn GURMUKHI SIGN UDAAT
+ {0x0A70, 0x0A71, prExtend}, // Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK
+ {0x0A75, 0x0A75, prExtend}, // Mn GURMUKHI SIGN YAKASH
+ {0x0A81, 0x0A82, prExtend}, // Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA
+ {0x0A83, 0x0A83, prSpacingMark}, // Mc GUJARATI SIGN VISARGA
+ {0x0ABC, 0x0ABC, prExtend}, // Mn GUJARATI SIGN NUKTA
+ {0x0ABE, 0x0AC0, prSpacingMark}, // Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II
+ {0x0AC1, 0x0AC5, prExtend}, // Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E
+ {0x0AC7, 0x0AC8, prExtend}, // Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI
+ {0x0AC9, 0x0AC9, prSpacingMark}, // Mc GUJARATI VOWEL SIGN CANDRA O
+ {0x0ACB, 0x0ACC, prSpacingMark}, // Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU
+ {0x0ACD, 0x0ACD, prExtend}, // Mn GUJARATI SIGN VIRAMA
+ {0x0AE2, 0x0AE3, prExtend}, // Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL
+ {0x0AFA, 0x0AFF, prExtend}, // Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE
+ {0x0B01, 0x0B01, prExtend}, // Mn ORIYA SIGN CANDRABINDU
+ {0x0B02, 0x0B03, prSpacingMark}, // Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA
+ {0x0B3C, 0x0B3C, prExtend}, // Mn ORIYA SIGN NUKTA
+ {0x0B3E, 0x0B3E, prExtend}, // Mc ORIYA VOWEL SIGN AA
+ {0x0B3F, 0x0B3F, prExtend}, // Mn ORIYA VOWEL SIGN I
+ {0x0B40, 0x0B40, prSpacingMark}, // Mc ORIYA VOWEL SIGN II
+ {0x0B41, 0x0B44, prExtend}, // Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR
+ {0x0B47, 0x0B48, prSpacingMark}, // Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI
+ {0x0B4B, 0x0B4C, prSpacingMark}, // Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU
+ {0x0B4D, 0x0B4D, prExtend}, // Mn ORIYA SIGN VIRAMA
+ {0x0B56, 0x0B56, prExtend}, // Mn ORIYA AI LENGTH MARK
+ {0x0B57, 0x0B57, prExtend}, // Mc ORIYA AU LENGTH MARK
+ {0x0B62, 0x0B63, prExtend}, // Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL
+ {0x0B82, 0x0B82, prExtend}, // Mn TAMIL SIGN ANUSVARA
+ {0x0BBE, 0x0BBE, prExtend}, // Mc TAMIL VOWEL SIGN AA
+ {0x0BBF, 0x0BBF, prSpacingMark}, // Mc TAMIL VOWEL SIGN I
+ {0x0BC0, 0x0BC0, prExtend}, // Mn TAMIL VOWEL SIGN II
+ {0x0BC1, 0x0BC2, prSpacingMark}, // Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU
+ {0x0BC6, 0x0BC8, prSpacingMark}, // Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI
+ {0x0BCA, 0x0BCC, prSpacingMark}, // Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU
+ {0x0BCD, 0x0BCD, prExtend}, // Mn TAMIL SIGN VIRAMA
+ {0x0BD7, 0x0BD7, prExtend}, // Mc TAMIL AU LENGTH MARK
+ {0x0C00, 0x0C00, prExtend}, // Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE
+ {0x0C01, 0x0C03, prSpacingMark}, // Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA
+ {0x0C04, 0x0C04, prExtend}, // Mn TELUGU SIGN COMBINING ANUSVARA ABOVE
+ {0x0C3E, 0x0C40, prExtend}, // Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II
+ {0x0C41, 0x0C44, prSpacingMark}, // Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR
+ {0x0C46, 0x0C48, prExtend}, // Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI
+ {0x0C4A, 0x0C4D, prExtend}, // Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA
+ {0x0C55, 0x0C56, prExtend}, // Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK
+ {0x0C62, 0x0C63, prExtend}, // Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL
+ {0x0C81, 0x0C81, prExtend}, // Mn KANNADA SIGN CANDRABINDU
+ {0x0C82, 0x0C83, prSpacingMark}, // Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA
+ {0x0CBC, 0x0CBC, prExtend}, // Mn KANNADA SIGN NUKTA
+ {0x0CBE, 0x0CBE, prSpacingMark}, // Mc KANNADA VOWEL SIGN AA
+ {0x0CBF, 0x0CBF, prExtend}, // Mn KANNADA VOWEL SIGN I
+ {0x0CC0, 0x0CC1, prSpacingMark}, // Mc [2] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN U
+ {0x0CC2, 0x0CC2, prExtend}, // Mc KANNADA VOWEL SIGN UU
+ {0x0CC3, 0x0CC4, prSpacingMark}, // Mc [2] KANNADA VOWEL SIGN VOCALIC R..KANNADA VOWEL SIGN VOCALIC RR
+ {0x0CC6, 0x0CC6, prExtend}, // Mn KANNADA VOWEL SIGN E
+ {0x0CC7, 0x0CC8, prSpacingMark}, // Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI
+ {0x0CCA, 0x0CCB, prSpacingMark}, // Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO
+ {0x0CCC, 0x0CCD, prExtend}, // Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA
+ {0x0CD5, 0x0CD6, prExtend}, // Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK
+ {0x0CE2, 0x0CE3, prExtend}, // Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL
+ {0x0D00, 0x0D01, prExtend}, // Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU
+ {0x0D02, 0x0D03, prSpacingMark}, // Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA
+ {0x0D3B, 0x0D3C, prExtend}, // Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA
+ {0x0D3E, 0x0D3E, prExtend}, // Mc MALAYALAM VOWEL SIGN AA
+ {0x0D3F, 0x0D40, prSpacingMark}, // Mc [2] MALAYALAM VOWEL SIGN I..MALAYALAM VOWEL SIGN II
+ {0x0D41, 0x0D44, prExtend}, // Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR
+ {0x0D46, 0x0D48, prSpacingMark}, // Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI
+ {0x0D4A, 0x0D4C, prSpacingMark}, // Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU
+ {0x0D4D, 0x0D4D, prExtend}, // Mn MALAYALAM SIGN VIRAMA
+ {0x0D4E, 0x0D4E, prPreprend}, // Lo MALAYALAM LETTER DOT REPH
+ {0x0D57, 0x0D57, prExtend}, // Mc MALAYALAM AU LENGTH MARK
+ {0x0D62, 0x0D63, prExtend}, // Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL
+ {0x0D82, 0x0D83, prSpacingMark}, // Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA
+ {0x0DCA, 0x0DCA, prExtend}, // Mn SINHALA SIGN AL-LAKUNA
+ {0x0DCF, 0x0DCF, prExtend}, // Mc SINHALA VOWEL SIGN AELA-PILLA
+ {0x0DD0, 0x0DD1, prSpacingMark}, // Mc [2] SINHALA VOWEL SIGN KETTI AEDA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA
+ {0x0DD2, 0x0DD4, prExtend}, // Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA
+ {0x0DD6, 0x0DD6, prExtend}, // Mn SINHALA VOWEL SIGN DIGA PAA-PILLA
+ {0x0DD8, 0x0DDE, prSpacingMark}, // Mc [7] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA
+ {0x0DDF, 0x0DDF, prExtend}, // Mc SINHALA VOWEL SIGN GAYANUKITTA
+ {0x0DF2, 0x0DF3, prSpacingMark}, // Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA
+ {0x0E31, 0x0E31, prExtend}, // Mn THAI CHARACTER MAI HAN-AKAT
+ {0x0E33, 0x0E33, prSpacingMark}, // Lo THAI CHARACTER SARA AM
+ {0x0E34, 0x0E3A, prExtend}, // Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU
+ {0x0E47, 0x0E4E, prExtend}, // Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN
+ {0x0EB1, 0x0EB1, prExtend}, // Mn LAO VOWEL SIGN MAI KAN
+ {0x0EB3, 0x0EB3, prSpacingMark}, // Lo LAO VOWEL SIGN AM
+ {0x0EB4, 0x0EBC, prExtend}, // Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO
+ {0x0EC8, 0x0ECD, prExtend}, // Mn [6] LAO TONE MAI EK..LAO NIGGAHITA
+ {0x0F18, 0x0F19, prExtend}, // Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS
+ {0x0F35, 0x0F35, prExtend}, // Mn TIBETAN MARK NGAS BZUNG NYI ZLA
+ {0x0F37, 0x0F37, prExtend}, // Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS
+ {0x0F39, 0x0F39, prExtend}, // Mn TIBETAN MARK TSA -PHRU
+ {0x0F3E, 0x0F3F, prSpacingMark}, // Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES
+ {0x0F71, 0x0F7E, prExtend}, // Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO
+ {0x0F7F, 0x0F7F, prSpacingMark}, // Mc TIBETAN SIGN RNAM BCAD
+ {0x0F80, 0x0F84, prExtend}, // Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA
+ {0x0F86, 0x0F87, prExtend}, // Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS
+ {0x0F8D, 0x0F97, prExtend}, // Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA
+ {0x0F99, 0x0FBC, prExtend}, // Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA
+ {0x0FC6, 0x0FC6, prExtend}, // Mn TIBETAN SYMBOL PADMA GDAN
+ {0x102D, 0x1030, prExtend}, // Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU
+ {0x1031, 0x1031, prSpacingMark}, // Mc MYANMAR VOWEL SIGN E
+ {0x1032, 0x1037, prExtend}, // Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW
+ {0x1039, 0x103A, prExtend}, // Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT
+ {0x103B, 0x103C, prSpacingMark}, // Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA
+ {0x103D, 0x103E, prExtend}, // Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA
+ {0x1056, 0x1057, prSpacingMark}, // Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR
+ {0x1058, 0x1059, prExtend}, // Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL
+ {0x105E, 0x1060, prExtend}, // Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA
+ {0x1071, 0x1074, prExtend}, // Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE
+ {0x1082, 0x1082, prExtend}, // Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+ {0x1084, 0x1084, prSpacingMark}, // Mc MYANMAR VOWEL SIGN SHAN E
+ {0x1085, 0x1086, prExtend}, // Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y
+ {0x108D, 0x108D, prExtend}, // Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE
+ {0x109D, 0x109D, prExtend}, // Mn MYANMAR VOWEL SIGN AITON AI
+ {0x1100, 0x115F, prL}, // Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER
+ {0x1160, 0x11A7, prV}, // Lo [72] HANGUL JUNGSEONG FILLER..HANGUL JUNGSEONG O-YAE
+ {0x11A8, 0x11FF, prT}, // Lo [88] HANGUL JONGSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN
+ {0x135D, 0x135F, prExtend}, // Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK
+ {0x1712, 0x1714, prExtend}, // Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA
+ {0x1732, 0x1734, prExtend}, // Mn [3] HANUNOO VOWEL SIGN I..HANUNOO SIGN PAMUDPOD
+ {0x1752, 0x1753, prExtend}, // Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U
+ {0x1772, 0x1773, prExtend}, // Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U
+ {0x17B4, 0x17B5, prExtend}, // Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
+ {0x17B6, 0x17B6, prSpacingMark}, // Mc KHMER VOWEL SIGN AA
+ {0x17B7, 0x17BD, prExtend}, // Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA
+ {0x17BE, 0x17C5, prSpacingMark}, // Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU
+ {0x17C6, 0x17C6, prExtend}, // Mn KHMER SIGN NIKAHIT
+ {0x17C7, 0x17C8, prSpacingMark}, // Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU
+ {0x17C9, 0x17D3, prExtend}, // Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT
+ {0x17DD, 0x17DD, prExtend}, // Mn KHMER SIGN ATTHACAN
+ {0x180B, 0x180D, prExtend}, // Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
+ {0x180E, 0x180E, prControl}, // Cf MONGOLIAN VOWEL SEPARATOR
+ {0x1885, 0x1886, prExtend}, // Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
+ {0x18A9, 0x18A9, prExtend}, // Mn MONGOLIAN LETTER ALI GALI DAGALGA
+ {0x1920, 0x1922, prExtend}, // Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U
+ {0x1923, 0x1926, prSpacingMark}, // Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU
+ {0x1927, 0x1928, prExtend}, // Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O
+ {0x1929, 0x192B, prSpacingMark}, // Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA
+ {0x1930, 0x1931, prSpacingMark}, // Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA
+ {0x1932, 0x1932, prExtend}, // Mn LIMBU SMALL LETTER ANUSVARA
+ {0x1933, 0x1938, prSpacingMark}, // Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA
+ {0x1939, 0x193B, prExtend}, // Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I
+ {0x1A17, 0x1A18, prExtend}, // Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U
+ {0x1A19, 0x1A1A, prSpacingMark}, // Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O
+ {0x1A1B, 0x1A1B, prExtend}, // Mn BUGINESE VOWEL SIGN AE
+ {0x1A55, 0x1A55, prSpacingMark}, // Mc TAI THAM CONSONANT SIGN MEDIAL RA
+ {0x1A56, 0x1A56, prExtend}, // Mn TAI THAM CONSONANT SIGN MEDIAL LA
+ {0x1A57, 0x1A57, prSpacingMark}, // Mc TAI THAM CONSONANT SIGN LA TANG LAI
+ {0x1A58, 0x1A5E, prExtend}, // Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA
+ {0x1A60, 0x1A60, prExtend}, // Mn TAI THAM SIGN SAKOT
+ {0x1A62, 0x1A62, prExtend}, // Mn TAI THAM VOWEL SIGN MAI SAT
+ {0x1A65, 0x1A6C, prExtend}, // Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW
+ {0x1A6D, 0x1A72, prSpacingMark}, // Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI
+ {0x1A73, 0x1A7C, prExtend}, // Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN
+ {0x1A7F, 0x1A7F, prExtend}, // Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT
+ {0x1AB0, 0x1ABD, prExtend}, // Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW
+ {0x1ABE, 0x1ABE, prExtend}, // Me COMBINING PARENTHESES OVERLAY
+ {0x1B00, 0x1B03, prExtend}, // Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG
+ {0x1B04, 0x1B04, prSpacingMark}, // Mc BALINESE SIGN BISAH
+ {0x1B34, 0x1B34, prExtend}, // Mn BALINESE SIGN REREKAN
+ {0x1B35, 0x1B35, prExtend}, // Mc BALINESE VOWEL SIGN TEDUNG
+ {0x1B36, 0x1B3A, prExtend}, // Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA
+ {0x1B3B, 0x1B3B, prSpacingMark}, // Mc BALINESE VOWEL SIGN RA REPA TEDUNG
+ {0x1B3C, 0x1B3C, prExtend}, // Mn BALINESE VOWEL SIGN LA LENGA
+ {0x1B3D, 0x1B41, prSpacingMark}, // Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG
+ {0x1B42, 0x1B42, prExtend}, // Mn BALINESE VOWEL SIGN PEPET
+ {0x1B43, 0x1B44, prSpacingMark}, // Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG
+ {0x1B6B, 0x1B73, prExtend}, // Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG
+ {0x1B80, 0x1B81, prExtend}, // Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR
+ {0x1B82, 0x1B82, prSpacingMark}, // Mc SUNDANESE SIGN PANGWISAD
+ {0x1BA1, 0x1BA1, prSpacingMark}, // Mc SUNDANESE CONSONANT SIGN PAMINGKAL
+ {0x1BA2, 0x1BA5, prExtend}, // Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU
+ {0x1BA6, 0x1BA7, prSpacingMark}, // Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG
+ {0x1BA8, 0x1BA9, prExtend}, // Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG
+ {0x1BAA, 0x1BAA, prSpacingMark}, // Mc SUNDANESE SIGN PAMAAEH
+ {0x1BAB, 0x1BAD, prExtend}, // Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA
+ {0x1BE6, 0x1BE6, prExtend}, // Mn BATAK SIGN TOMPI
+ {0x1BE7, 0x1BE7, prSpacingMark}, // Mc BATAK VOWEL SIGN E
+ {0x1BE8, 0x1BE9, prExtend}, // Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE
+ {0x1BEA, 0x1BEC, prSpacingMark}, // Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O
+ {0x1BED, 0x1BED, prExtend}, // Mn BATAK VOWEL SIGN KARO O
+ {0x1BEE, 0x1BEE, prSpacingMark}, // Mc BATAK VOWEL SIGN U
+ {0x1BEF, 0x1BF1, prExtend}, // Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H
+ {0x1BF2, 0x1BF3, prSpacingMark}, // Mc [2] BATAK PANGOLAT..BATAK PANONGONAN
+ {0x1C24, 0x1C2B, prSpacingMark}, // Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU
+ {0x1C2C, 0x1C33, prExtend}, // Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T
+ {0x1C34, 0x1C35, prSpacingMark}, // Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG
+ {0x1C36, 0x1C37, prExtend}, // Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA
+ {0x1CD0, 0x1CD2, prExtend}, // Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA
+ {0x1CD4, 0x1CE0, prExtend}, // Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA
+ {0x1CE1, 0x1CE1, prSpacingMark}, // Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA
+ {0x1CE2, 0x1CE8, prExtend}, // Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL
+ {0x1CED, 0x1CED, prExtend}, // Mn VEDIC SIGN TIRYAK
+ {0x1CF4, 0x1CF4, prExtend}, // Mn VEDIC TONE CANDRA ABOVE
+ {0x1CF7, 0x1CF7, prSpacingMark}, // Mc VEDIC SIGN ATIKRAMA
+ {0x1CF8, 0x1CF9, prExtend}, // Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE
+ {0x1DC0, 0x1DF9, prExtend}, // Mn [58] COMBINING DOTTED GRAVE ACCENT..COMBINING WIDE INVERTED BRIDGE BELOW
+ {0x1DFB, 0x1DFF, prExtend}, // Mn [5] COMBINING DELETION MARK..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
+ {0x200B, 0x200B, prControl}, // Cf ZERO WIDTH SPACE
+ {0x200C, 0x200C, prExtend}, // Cf ZERO WIDTH NON-JOINER
+ {0x200D, 0x200D, prZWJ}, // Cf ZERO WIDTH JOINER
+ {0x200E, 0x200F, prControl}, // Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK
+ {0x2028, 0x2028, prControl}, // Zl LINE SEPARATOR
+ {0x2029, 0x2029, prControl}, // Zp PARAGRAPH SEPARATOR
+ {0x202A, 0x202E, prControl}, // Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+ {0x203C, 0x203C, prExtendedPictographic}, // 1.1 [1] (โ€ผ๏ธ) double exclamation mark
+ {0x2049, 0x2049, prExtendedPictographic}, // 3.0 [1] (โ‰๏ธ) exclamation question mark
+ {0x2060, 0x2064, prControl}, // Cf [5] WORD JOINER..INVISIBLE PLUS
+ {0x2065, 0x2065, prControl}, // Cn <reserved-2065>
+ {0x2066, 0x206F, prControl}, // Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
+ {0x20D0, 0x20DC, prExtend}, // Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE
+ {0x20DD, 0x20E0, prExtend}, // Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH
+ {0x20E1, 0x20E1, prExtend}, // Mn COMBINING LEFT RIGHT ARROW ABOVE
+ {0x20E2, 0x20E4, prExtend}, // Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE
+ {0x20E5, 0x20F0, prExtend}, // Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE
+ {0x2122, 0x2122, prExtendedPictographic}, // 1.1 [1] (โ„ข๏ธ) trade mark
+ {0x2139, 0x2139, prExtendedPictographic}, // 3.0 [1] (โ„น๏ธ) information
+ {0x2194, 0x2199, prExtendedPictographic}, // 1.1 [6] (โ†”๏ธ..โ†™๏ธ) left-right arrow..down-left arrow
+ {0x21A9, 0x21AA, prExtendedPictographic}, // 1.1 [2] (โ†ฉ๏ธ..โ†ช๏ธ) right arrow curving left..left arrow curving right
+ {0x231A, 0x231B, prExtendedPictographic}, // 1.1 [2] (โŒš..โŒ›) watch..hourglass done
+ {0x2328, 0x2328, prExtendedPictographic}, // 1.1 [1] (โŒจ๏ธ) keyboard
+ {0x2388, 0x2388, prExtendedPictographic}, // 3.0 [1] (โŽˆ) HELM SYMBOL
+ {0x23CF, 0x23CF, prExtendedPictographic}, // 4.0 [1] (โ๏ธ) eject button
+ {0x23E9, 0x23F3, prExtendedPictographic}, // 6.0 [11] (โฉ..โณ) fast-forward button..hourglass not done
+ {0x23F8, 0x23FA, prExtendedPictographic}, // 7.0 [3] (โธ๏ธ..โบ๏ธ) pause button..record button
+ {0x24C2, 0x24C2, prExtendedPictographic}, // 1.1 [1] (โ“‚๏ธ) circled M
+ {0x25AA, 0x25AB, prExtendedPictographic}, // 1.1 [2] (โ–ช๏ธ..โ–ซ๏ธ) black small square..white small square
+ {0x25B6, 0x25B6, prExtendedPictographic}, // 1.1 [1] (โ–ถ๏ธ) play button
+ {0x25C0, 0x25C0, prExtendedPictographic}, // 1.1 [1] (โ—€๏ธ) reverse button
+ {0x25FB, 0x25FE, prExtendedPictographic}, // 3.2 [4] (โ—ป๏ธ..โ—พ) white medium square..black medium-small square
+ {0x2600, 0x2605, prExtendedPictographic}, // 1.1 [6] (โ˜€๏ธ..โ˜…) sun..BLACK STAR
+ {0x2607, 0x2612, prExtendedPictographic}, // 1.1 [12] (โ˜‡..โ˜’) LIGHTNING..BALLOT BOX WITH X
+ {0x2614, 0x2615, prExtendedPictographic}, // 4.0 [2] (โ˜”..โ˜•) umbrella with rain drops..hot beverage
+ {0x2616, 0x2617, prExtendedPictographic}, // 3.2 [2] (โ˜–..โ˜—) WHITE SHOGI PIECE..BLACK SHOGI PIECE
+ {0x2618, 0x2618, prExtendedPictographic}, // 4.1 [1] (โ˜˜๏ธ) shamrock
+ {0x2619, 0x2619, prExtendedPictographic}, // 3.0 [1] (โ˜™) REVERSED ROTATED FLORAL HEART BULLET
+ {0x261A, 0x266F, prExtendedPictographic}, // 1.1 [86] (โ˜š..โ™ฏ) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN
+ {0x2670, 0x2671, prExtendedPictographic}, // 3.0 [2] (โ™ฐ..โ™ฑ) WEST SYRIAC CROSS..EAST SYRIAC CROSS
+ {0x2672, 0x267D, prExtendedPictographic}, // 3.2 [12] (โ™ฒ..โ™ฝ) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL
+ {0x267E, 0x267F, prExtendedPictographic}, // 4.1 [2] (โ™พ๏ธ..โ™ฟ) infinity..wheelchair symbol
+ {0x2680, 0x2685, prExtendedPictographic}, // 3.2 [6] (โš€..โš…) DIE FACE-1..DIE FACE-6
+ {0x2690, 0x2691, prExtendedPictographic}, // 4.0 [2] (โš..โš‘) WHITE FLAG..BLACK FLAG
+ {0x2692, 0x269C, prExtendedPictographic}, // 4.1 [11] (โš’๏ธ..โšœ๏ธ) hammer and pick..fleur-de-lis
+ {0x269D, 0x269D, prExtendedPictographic}, // 5.1 [1] (โš) OUTLINED WHITE STAR
+ {0x269E, 0x269F, prExtendedPictographic}, // 5.2 [2] (โšž..โšŸ) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT
+ {0x26A0, 0x26A1, prExtendedPictographic}, // 4.0 [2] (โš ๏ธ..โšก) warning..high voltage
+ {0x26A2, 0x26B1, prExtendedPictographic}, // 4.1 [16] (โšข..โšฑ๏ธ) DOUBLED FEMALE SIGN..funeral urn
+ {0x26B2, 0x26B2, prExtendedPictographic}, // 5.0 [1] (โšฒ) NEUTER
+ {0x26B3, 0x26BC, prExtendedPictographic}, // 5.1 [10] (โšณ..โšผ) CERES..SESQUIQUADRATE
+ {0x26BD, 0x26BF, prExtendedPictographic}, // 5.2 [3] (โšฝ..โšฟ) soccer ball..SQUARED KEY
+ {0x26C0, 0x26C3, prExtendedPictographic}, // 5.1 [4] (โ›€..โ›ƒ) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING
+ {0x26C4, 0x26CD, prExtendedPictographic}, // 5.2 [10] (โ›„..โ›) snowman without snow..DISABLED CAR
+ {0x26CE, 0x26CE, prExtendedPictographic}, // 6.0 [1] (โ›Ž) Ophiuchus
+ {0x26CF, 0x26E1, prExtendedPictographic}, // 5.2 [19] (โ›๏ธ..โ›ก) pick..RESTRICTED LEFT ENTRY-2
+ {0x26E2, 0x26E2, prExtendedPictographic}, // 6.0 [1] (โ›ข) ASTRONOMICAL SYMBOL FOR URANUS
+ {0x26E3, 0x26E3, prExtendedPictographic}, // 5.2 [1] (โ›ฃ) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE
+ {0x26E4, 0x26E7, prExtendedPictographic}, // 6.0 [4] (โ›ค..โ›ง) PENTAGRAM..INVERTED PENTAGRAM
+ {0x26E8, 0x26FF, prExtendedPictographic}, // 5.2 [24] (โ›จ..โ›ฟ) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE
+ {0x2700, 0x2700, prExtendedPictographic}, // 7.0 [1] (โœ€) BLACK SAFETY SCISSORS
+ {0x2701, 0x2704, prExtendedPictographic}, // 1.1 [4] (โœ..โœ„) UPPER BLADE SCISSORS..WHITE SCISSORS
+ {0x2705, 0x2705, prExtendedPictographic}, // 6.0 [1] (โœ…) check mark button
+ {0x2708, 0x2709, prExtendedPictographic}, // 1.1 [2] (โœˆ๏ธ..โœ‰๏ธ) airplane..envelope
+ {0x270A, 0x270B, prExtendedPictographic}, // 6.0 [2] (โœŠ..โœ‹) raised fist..raised hand
+ {0x270C, 0x2712, prExtendedPictographic}, // 1.1 [7] (โœŒ๏ธ..โœ’๏ธ) victory hand..black nib
+ {0x2714, 0x2714, prExtendedPictographic}, // 1.1 [1] (โœ”๏ธ) check mark
+ {0x2716, 0x2716, prExtendedPictographic}, // 1.1 [1] (โœ–๏ธ) multiplication sign
+ {0x271D, 0x271D, prExtendedPictographic}, // 1.1 [1] (โœ๏ธ) latin cross
+ {0x2721, 0x2721, prExtendedPictographic}, // 1.1 [1] (โœก๏ธ) star of David
+ {0x2728, 0x2728, prExtendedPictographic}, // 6.0 [1] (โœจ) sparkles
+ {0x2733, 0x2734, prExtendedPictographic}, // 1.1 [2] (โœณ๏ธ..โœด๏ธ) eight-spoked asterisk..eight-pointed star
+ {0x2744, 0x2744, prExtendedPictographic}, // 1.1 [1] (โ„๏ธ) snowflake
+ {0x2747, 0x2747, prExtendedPictographic}, // 1.1 [1] (โ‡๏ธ) sparkle
+ {0x274C, 0x274C, prExtendedPictographic}, // 6.0 [1] (โŒ) cross mark
+ {0x274E, 0x274E, prExtendedPictographic}, // 6.0 [1] (โŽ) cross mark button
+ {0x2753, 0x2755, prExtendedPictographic}, // 6.0 [3] (โ“..โ•) question mark..white exclamation mark
+ {0x2757, 0x2757, prExtendedPictographic}, // 5.2 [1] (โ—) exclamation mark
+ {0x2763, 0x2767, prExtendedPictographic}, // 1.1 [5] (โฃ๏ธ..โง) heart exclamation..ROTATED FLORAL HEART BULLET
+ {0x2795, 0x2797, prExtendedPictographic}, // 6.0 [3] (โž•..โž—) plus sign..division sign
+ {0x27A1, 0x27A1, prExtendedPictographic}, // 1.1 [1] (โžก๏ธ) right arrow
+ {0x27B0, 0x27B0, prExtendedPictographic}, // 6.0 [1] (โžฐ) curly loop
+ {0x27BF, 0x27BF, prExtendedPictographic}, // 6.0 [1] (โžฟ) double curly loop
+ {0x2934, 0x2935, prExtendedPictographic}, // 3.2 [2] (โคด๏ธ..โคต๏ธ) right arrow curving up..right arrow curving down
+ {0x2B05, 0x2B07, prExtendedPictographic}, // 4.0 [3] (โฌ…๏ธ..โฌ‡๏ธ) left arrow..down arrow
+ {0x2B1B, 0x2B1C, prExtendedPictographic}, // 5.1 [2] (โฌ›..โฌœ) black large square..white large square
+ {0x2B50, 0x2B50, prExtendedPictographic}, // 5.1 [1] (โญ) star
+ {0x2B55, 0x2B55, prExtendedPictographic}, // 5.2 [1] (โญ•) hollow red circle
+ {0x2CEF, 0x2CF1, prExtend}, // Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS
+ {0x2D7F, 0x2D7F, prExtend}, // Mn TIFINAGH CONSONANT JOINER
+ {0x2DE0, 0x2DFF, prExtend}, // Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
+ {0x302A, 0x302D, prExtend}, // Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK
+ {0x302E, 0x302F, prExtend}, // Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK
+ {0x3030, 0x3030, prExtendedPictographic}, // 1.1 [1] (ใ€ฐ๏ธ) wavy dash
+ {0x303D, 0x303D, prExtendedPictographic}, // 3.2 [1] (ใ€ฝ๏ธ) part alternation mark
+ {0x3099, 0x309A, prExtend}, // Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+ {0x3297, 0x3297, prExtendedPictographic}, // 1.1 [1] (ใŠ—๏ธ) Japanese โ€œcongratulationsโ€ button
+ {0x3299, 0x3299, prExtendedPictographic}, // 1.1 [1] (ใŠ™๏ธ) Japanese โ€œsecretโ€ button
+ {0xA66F, 0xA66F, prExtend}, // Mn COMBINING CYRILLIC VZMET
+ {0xA670, 0xA672, prExtend}, // Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN
+ {0xA674, 0xA67D, prExtend}, // Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK
+ {0xA69E, 0xA69F, prExtend}, // Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E
+ {0xA6F0, 0xA6F1, prExtend}, // Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS
+ {0xA802, 0xA802, prExtend}, // Mn SYLOTI NAGRI SIGN DVISVARA
+ {0xA806, 0xA806, prExtend}, // Mn SYLOTI NAGRI SIGN HASANTA
+ {0xA80B, 0xA80B, prExtend}, // Mn SYLOTI NAGRI SIGN ANUSVARA
+ {0xA823, 0xA824, prSpacingMark}, // Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I
+ {0xA825, 0xA826, prExtend}, // Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E
+ {0xA827, 0xA827, prSpacingMark}, // Mc SYLOTI NAGRI VOWEL SIGN OO
+ {0xA880, 0xA881, prSpacingMark}, // Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA
+ {0xA8B4, 0xA8C3, prSpacingMark}, // Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU
+ {0xA8C4, 0xA8C5, prExtend}, // Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU
+ {0xA8E0, 0xA8F1, prExtend}, // Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA
+ {0xA8FF, 0xA8FF, prExtend}, // Mn DEVANAGARI VOWEL SIGN AY
+ {0xA926, 0xA92D, prExtend}, // Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU
+ {0xA947, 0xA951, prExtend}, // Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R
+ {0xA952, 0xA953, prSpacingMark}, // Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA
+ {0xA960, 0xA97C, prL}, // Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH
+ {0xA980, 0xA982, prExtend}, // Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR
+ {0xA983, 0xA983, prSpacingMark}, // Mc JAVANESE SIGN WIGNYAN
+ {0xA9B3, 0xA9B3, prExtend}, // Mn JAVANESE SIGN CECAK TELU
+ {0xA9B4, 0xA9B5, prSpacingMark}, // Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG
+ {0xA9B6, 0xA9B9, prExtend}, // Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT
+ {0xA9BA, 0xA9BB, prSpacingMark}, // Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE
+ {0xA9BC, 0xA9BD, prExtend}, // Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET
+ {0xA9BE, 0xA9C0, prSpacingMark}, // Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON
+ {0xA9E5, 0xA9E5, prExtend}, // Mn MYANMAR SIGN SHAN SAW
+ {0xAA29, 0xAA2E, prExtend}, // Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE
+ {0xAA2F, 0xAA30, prSpacingMark}, // Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI
+ {0xAA31, 0xAA32, prExtend}, // Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE
+ {0xAA33, 0xAA34, prSpacingMark}, // Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA
+ {0xAA35, 0xAA36, prExtend}, // Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA
+ {0xAA43, 0xAA43, prExtend}, // Mn CHAM CONSONANT SIGN FINAL NG
+ {0xAA4C, 0xAA4C, prExtend}, // Mn CHAM CONSONANT SIGN FINAL M
+ {0xAA4D, 0xAA4D, prSpacingMark}, // Mc CHAM CONSONANT SIGN FINAL H
+ {0xAA7C, 0xAA7C, prExtend}, // Mn MYANMAR SIGN TAI LAING TONE-2
+ {0xAAB0, 0xAAB0, prExtend}, // Mn TAI VIET MAI KANG
+ {0xAAB2, 0xAAB4, prExtend}, // Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U
+ {0xAAB7, 0xAAB8, prExtend}, // Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA
+ {0xAABE, 0xAABF, prExtend}, // Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK
+ {0xAAC1, 0xAAC1, prExtend}, // Mn TAI VIET TONE MAI THO
+ {0xAAEB, 0xAAEB, prSpacingMark}, // Mc MEETEI MAYEK VOWEL SIGN II
+ {0xAAEC, 0xAAED, prExtend}, // Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI
+ {0xAAEE, 0xAAEF, prSpacingMark}, // Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU
+ {0xAAF5, 0xAAF5, prSpacingMark}, // Mc MEETEI MAYEK VOWEL SIGN VISARGA
+ {0xAAF6, 0xAAF6, prExtend}, // Mn MEETEI MAYEK VIRAMA
+ {0xABE3, 0xABE4, prSpacingMark}, // Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP
+ {0xABE5, 0xABE5, prExtend}, // Mn MEETEI MAYEK VOWEL SIGN ANAP
+ {0xABE6, 0xABE7, prSpacingMark}, // Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP
+ {0xABE8, 0xABE8, prExtend}, // Mn MEETEI MAYEK VOWEL SIGN UNAP
+ {0xABE9, 0xABEA, prSpacingMark}, // Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG
+ {0xABEC, 0xABEC, prSpacingMark}, // Mc MEETEI MAYEK LUM IYEK
+ {0xABED, 0xABED, prExtend}, // Mn MEETEI MAYEK APUN IYEK
+ {0xAC00, 0xAC00, prLV}, // Lo HANGUL SYLLABLE GA
+ {0xAC01, 0xAC1B, prLVT}, // Lo [27] HANGUL SYLLABLE GAG..HANGUL SYLLABLE GAH
+ {0xAC1C, 0xAC1C, prLV}, // Lo HANGUL SYLLABLE GAE
+ {0xAC1D, 0xAC37, prLVT}, // Lo [27] HANGUL SYLLABLE GAEG..HANGUL SYLLABLE GAEH
+ {0xAC38, 0xAC38, prLV}, // Lo HANGUL SYLLABLE GYA
+ {0xAC39, 0xAC53, prLVT}, // Lo [27] HANGUL SYLLABLE GYAG..HANGUL SYLLABLE GYAH
+ {0xAC54, 0xAC54, prLV}, // Lo HANGUL SYLLABLE GYAE
+ {0xAC55, 0xAC6F, prLVT}, // Lo [27] HANGUL SYLLABLE GYAEG..HANGUL SYLLABLE GYAEH
+ {0xAC70, 0xAC70, prLV}, // Lo HANGUL SYLLABLE GEO
+ {0xAC71, 0xAC8B, prLVT}, // Lo [27] HANGUL SYLLABLE GEOG..HANGUL SYLLABLE GEOH
+ {0xAC8C, 0xAC8C, prLV}, // Lo HANGUL SYLLABLE GE
+ {0xAC8D, 0xACA7, prLVT}, // Lo [27] HANGUL SYLLABLE GEG..HANGUL SYLLABLE GEH
+ {0xACA8, 0xACA8, prLV}, // Lo HANGUL SYLLABLE GYEO
+ {0xACA9, 0xACC3, prLVT}, // Lo [27] HANGUL SYLLABLE GYEOG..HANGUL SYLLABLE GYEOH
+ {0xACC4, 0xACC4, prLV}, // Lo HANGUL SYLLABLE GYE
+ {0xACC5, 0xACDF, prLVT}, // Lo [27] HANGUL SYLLABLE GYEG..HANGUL SYLLABLE GYEH
+ {0xACE0, 0xACE0, prLV}, // Lo HANGUL SYLLABLE GO
+ {0xACE1, 0xACFB, prLVT}, // Lo [27] HANGUL SYLLABLE GOG..HANGUL SYLLABLE GOH
+ {0xACFC, 0xACFC, prLV}, // Lo HANGUL SYLLABLE GWA
+ {0xACFD, 0xAD17, prLVT}, // Lo [27] HANGUL SYLLABLE GWAG..HANGUL SYLLABLE GWAH
+ {0xAD18, 0xAD18, prLV}, // Lo HANGUL SYLLABLE GWAE
+ {0xAD19, 0xAD33, prLVT}, // Lo [27] HANGUL SYLLABLE GWAEG..HANGUL SYLLABLE GWAEH
+ {0xAD34, 0xAD34, prLV}, // Lo HANGUL SYLLABLE GOE
+ {0xAD35, 0xAD4F, prLVT}, // Lo [27] HANGUL SYLLABLE GOEG..HANGUL SYLLABLE GOEH
+ {0xAD50, 0xAD50, prLV}, // Lo HANGUL SYLLABLE GYO
+ {0xAD51, 0xAD6B, prLVT}, // Lo [27] HANGUL SYLLABLE GYOG..HANGUL SYLLABLE GYOH
+ {0xAD6C, 0xAD6C, prLV}, // Lo HANGUL SYLLABLE GU
+ {0xAD6D, 0xAD87, prLVT}, // Lo [27] HANGUL SYLLABLE GUG..HANGUL SYLLABLE GUH
+ {0xAD88, 0xAD88, prLV}, // Lo HANGUL SYLLABLE GWEO
+ {0xAD89, 0xADA3, prLVT}, // Lo [27] HANGUL SYLLABLE GWEOG..HANGUL SYLLABLE GWEOH
+ {0xADA4, 0xADA4, prLV}, // Lo HANGUL SYLLABLE GWE
+ {0xADA5, 0xADBF, prLVT}, // Lo [27] HANGUL SYLLABLE GWEG..HANGUL SYLLABLE GWEH
+ {0xADC0, 0xADC0, prLV}, // Lo HANGUL SYLLABLE GWI
+ {0xADC1, 0xADDB, prLVT}, // Lo [27] HANGUL SYLLABLE GWIG..HANGUL SYLLABLE GWIH
+ {0xADDC, 0xADDC, prLV}, // Lo HANGUL SYLLABLE GYU
+ {0xADDD, 0xADF7, prLVT}, // Lo [27] HANGUL SYLLABLE GYUG..HANGUL SYLLABLE GYUH
+ {0xADF8, 0xADF8, prLV}, // Lo HANGUL SYLLABLE GEU
+ {0xADF9, 0xAE13, prLVT}, // Lo [27] HANGUL SYLLABLE GEUG..HANGUL SYLLABLE GEUH
+ {0xAE14, 0xAE14, prLV}, // Lo HANGUL SYLLABLE GYI
+ {0xAE15, 0xAE2F, prLVT}, // Lo [27] HANGUL SYLLABLE GYIG..HANGUL SYLLABLE GYIH
+ {0xAE30, 0xAE30, prLV}, // Lo HANGUL SYLLABLE GI
+ {0xAE31, 0xAE4B, prLVT}, // Lo [27] HANGUL SYLLABLE GIG..HANGUL SYLLABLE GIH
+ {0xAE4C, 0xAE4C, prLV}, // Lo HANGUL SYLLABLE GGA
+ {0xAE4D, 0xAE67, prLVT}, // Lo [27] HANGUL SYLLABLE GGAG..HANGUL SYLLABLE GGAH
+ {0xAE68, 0xAE68, prLV}, // Lo HANGUL SYLLABLE GGAE
+ {0xAE69, 0xAE83, prLVT}, // Lo [27] HANGUL SYLLABLE GGAEG..HANGUL SYLLABLE GGAEH
+ {0xAE84, 0xAE84, prLV}, // Lo HANGUL SYLLABLE GGYA
+ {0xAE85, 0xAE9F, prLVT}, // Lo [27] HANGUL SYLLABLE GGYAG..HANGUL SYLLABLE GGYAH
+ {0xAEA0, 0xAEA0, prLV}, // Lo HANGUL SYLLABLE GGYAE
+ {0xAEA1, 0xAEBB, prLVT}, // Lo [27] HANGUL SYLLABLE GGYAEG..HANGUL SYLLABLE GGYAEH
+ {0xAEBC, 0xAEBC, prLV}, // Lo HANGUL SYLLABLE GGEO
+ {0xAEBD, 0xAED7, prLVT}, // Lo [27] HANGUL SYLLABLE GGEOG..HANGUL SYLLABLE GGEOH
+ {0xAED8, 0xAED8, prLV}, // Lo HANGUL SYLLABLE GGE
+ {0xAED9, 0xAEF3, prLVT}, // Lo [27] HANGUL SYLLABLE GGEG..HANGUL SYLLABLE GGEH
+ {0xAEF4, 0xAEF4, prLV}, // Lo HANGUL SYLLABLE GGYEO
+ {0xAEF5, 0xAF0F, prLVT}, // Lo [27] HANGUL SYLLABLE GGYEOG..HANGUL SYLLABLE GGYEOH
+ {0xAF10, 0xAF10, prLV}, // Lo HANGUL SYLLABLE GGYE
+ {0xAF11, 0xAF2B, prLVT}, // Lo [27] HANGUL SYLLABLE GGYEG..HANGUL SYLLABLE GGYEH
+ {0xAF2C, 0xAF2C, prLV}, // Lo HANGUL SYLLABLE GGO
+ {0xAF2D, 0xAF47, prLVT}, // Lo [27] HANGUL SYLLABLE GGOG..HANGUL SYLLABLE GGOH
+ {0xAF48, 0xAF48, prLV}, // Lo HANGUL SYLLABLE GGWA
+ {0xAF49, 0xAF63, prLVT}, // Lo [27] HANGUL SYLLABLE GGWAG..HANGUL SYLLABLE GGWAH
+ {0xAF64, 0xAF64, prLV}, // Lo HANGUL SYLLABLE GGWAE
+ {0xAF65, 0xAF7F, prLVT}, // Lo [27] HANGUL SYLLABLE GGWAEG..HANGUL SYLLABLE GGWAEH
+ {0xAF80, 0xAF80, prLV}, // Lo HANGUL SYLLABLE GGOE
+ {0xAF81, 0xAF9B, prLVT}, // Lo [27] HANGUL SYLLABLE GGOEG..HANGUL SYLLABLE GGOEH
+ {0xAF9C, 0xAF9C, prLV}, // Lo HANGUL SYLLABLE GGYO
+ {0xAF9D, 0xAFB7, prLVT}, // Lo [27] HANGUL SYLLABLE GGYOG..HANGUL SYLLABLE GGYOH
+ {0xAFB8, 0xAFB8, prLV}, // Lo HANGUL SYLLABLE GGU
+ {0xAFB9, 0xAFD3, prLVT}, // Lo [27] HANGUL SYLLABLE GGUG..HANGUL SYLLABLE GGUH
+ {0xAFD4, 0xAFD4, prLV}, // Lo HANGUL SYLLABLE GGWEO
+ {0xAFD5, 0xAFEF, prLVT}, // Lo [27] HANGUL SYLLABLE GGWEOG..HANGUL SYLLABLE GGWEOH
+ {0xAFF0, 0xAFF0, prLV}, // Lo HANGUL SYLLABLE GGWE
+ {0xAFF1, 0xB00B, prLVT}, // Lo [27] HANGUL SYLLABLE GGWEG..HANGUL SYLLABLE GGWEH
+ {0xB00C, 0xB00C, prLV}, // Lo HANGUL SYLLABLE GGWI
+ {0xB00D, 0xB027, prLVT}, // Lo [27] HANGUL SYLLABLE GGWIG..HANGUL SYLLABLE GGWIH
+ {0xB028, 0xB028, prLV}, // Lo HANGUL SYLLABLE GGYU
+ {0xB029, 0xB043, prLVT}, // Lo [27] HANGUL SYLLABLE GGYUG..HANGUL SYLLABLE GGYUH
+ {0xB044, 0xB044, prLV}, // Lo HANGUL SYLLABLE GGEU
+ {0xB045, 0xB05F, prLVT}, // Lo [27] HANGUL SYLLABLE GGEUG..HANGUL SYLLABLE GGEUH
+ {0xB060, 0xB060, prLV}, // Lo HANGUL SYLLABLE GGYI
+ {0xB061, 0xB07B, prLVT}, // Lo [27] HANGUL SYLLABLE GGYIG..HANGUL SYLLABLE GGYIH
+ {0xB07C, 0xB07C, prLV}, // Lo HANGUL SYLLABLE GGI
+ {0xB07D, 0xB097, prLVT}, // Lo [27] HANGUL SYLLABLE GGIG..HANGUL SYLLABLE GGIH
+ {0xB098, 0xB098, prLV}, // Lo HANGUL SYLLABLE NA
+ {0xB099, 0xB0B3, prLVT}, // Lo [27] HANGUL SYLLABLE NAG..HANGUL SYLLABLE NAH
+ {0xB0B4, 0xB0B4, prLV}, // Lo HANGUL SYLLABLE NAE
+ {0xB0B5, 0xB0CF, prLVT}, // Lo [27] HANGUL SYLLABLE NAEG..HANGUL SYLLABLE NAEH
+ {0xB0D0, 0xB0D0, prLV}, // Lo HANGUL SYLLABLE NYA
+ {0xB0D1, 0xB0EB, prLVT}, // Lo [27] HANGUL SYLLABLE NYAG..HANGUL SYLLABLE NYAH
+ {0xB0EC, 0xB0EC, prLV}, // Lo HANGUL SYLLABLE NYAE
+ {0xB0ED, 0xB107, prLVT}, // Lo [27] HANGUL SYLLABLE NYAEG..HANGUL SYLLABLE NYAEH
+ {0xB108, 0xB108, prLV}, // Lo HANGUL SYLLABLE NEO
+ {0xB109, 0xB123, prLVT}, // Lo [27] HANGUL SYLLABLE NEOG..HANGUL SYLLABLE NEOH
+ {0xB124, 0xB124, prLV}, // Lo HANGUL SYLLABLE NE
+ {0xB125, 0xB13F, prLVT}, // Lo [27] HANGUL SYLLABLE NEG..HANGUL SYLLABLE NEH
+ {0xB140, 0xB140, prLV}, // Lo HANGUL SYLLABLE NYEO
+ {0xB141, 0xB15B, prLVT}, // Lo [27] HANGUL SYLLABLE NYEOG..HANGUL SYLLABLE NYEOH
+ {0xB15C, 0xB15C, prLV}, // Lo HANGUL SYLLABLE NYE
+ {0xB15D, 0xB177, prLVT}, // Lo [27] HANGUL SYLLABLE NYEG..HANGUL SYLLABLE NYEH
+ {0xB178, 0xB178, prLV}, // Lo HANGUL SYLLABLE NO
+ {0xB179, 0xB193, prLVT}, // Lo [27] HANGUL SYLLABLE NOG..HANGUL SYLLABLE NOH
+ {0xB194, 0xB194, prLV}, // Lo HANGUL SYLLABLE NWA
+ {0xB195, 0xB1AF, prLVT}, // Lo [27] HANGUL SYLLABLE NWAG..HANGUL SYLLABLE NWAH
+ {0xB1B0, 0xB1B0, prLV}, // Lo HANGUL SYLLABLE NWAE
+ {0xB1B1, 0xB1CB, prLVT}, // Lo [27] HANGUL SYLLABLE NWAEG..HANGUL SYLLABLE NWAEH
+ {0xB1CC, 0xB1CC, prLV}, // Lo HANGUL SYLLABLE NOE
+ {0xB1CD, 0xB1E7, prLVT}, // Lo [27] HANGUL SYLLABLE NOEG..HANGUL SYLLABLE NOEH
+ {0xB1E8, 0xB1E8, prLV}, // Lo HANGUL SYLLABLE NYO
+ {0xB1E9, 0xB203, prLVT}, // Lo [27] HANGUL SYLLABLE NYOG..HANGUL SYLLABLE NYOH
+ {0xB204, 0xB204, prLV}, // Lo HANGUL SYLLABLE NU
+ {0xB205, 0xB21F, prLVT}, // Lo [27] HANGUL SYLLABLE NUG..HANGUL SYLLABLE NUH
+ {0xB220, 0xB220, prLV}, // Lo HANGUL SYLLABLE NWEO
+ {0xB221, 0xB23B, prLVT}, // Lo [27] HANGUL SYLLABLE NWEOG..HANGUL SYLLABLE NWEOH
+ {0xB23C, 0xB23C, prLV}, // Lo HANGUL SYLLABLE NWE
+ {0xB23D, 0xB257, prLVT}, // Lo [27] HANGUL SYLLABLE NWEG..HANGUL SYLLABLE NWEH
+ {0xB258, 0xB258, prLV}, // Lo HANGUL SYLLABLE NWI
+ {0xB259, 0xB273, prLVT}, // Lo [27] HANGUL SYLLABLE NWIG..HANGUL SYLLABLE NWIH
+ {0xB274, 0xB274, prLV}, // Lo HANGUL SYLLABLE NYU
+ {0xB275, 0xB28F, prLVT}, // Lo [27] HANGUL SYLLABLE NYUG..HANGUL SYLLABLE NYUH
+ {0xB290, 0xB290, prLV}, // Lo HANGUL SYLLABLE NEU
+ {0xB291, 0xB2AB, prLVT}, // Lo [27] HANGUL SYLLABLE NEUG..HANGUL SYLLABLE NEUH
+ {0xB2AC, 0xB2AC, prLV}, // Lo HANGUL SYLLABLE NYI
+ {0xB2AD, 0xB2C7, prLVT}, // Lo [27] HANGUL SYLLABLE NYIG..HANGUL SYLLABLE NYIH
+ {0xB2C8, 0xB2C8, prLV}, // Lo HANGUL SYLLABLE NI
+ {0xB2C9, 0xB2E3, prLVT}, // Lo [27] HANGUL SYLLABLE NIG..HANGUL SYLLABLE NIH
+ {0xB2E4, 0xB2E4, prLV}, // Lo HANGUL SYLLABLE DA
+ {0xB2E5, 0xB2FF, prLVT}, // Lo [27] HANGUL SYLLABLE DAG..HANGUL SYLLABLE DAH
+ {0xB300, 0xB300, prLV}, // Lo HANGUL SYLLABLE DAE
+ {0xB301, 0xB31B, prLVT}, // Lo [27] HANGUL SYLLABLE DAEG..HANGUL SYLLABLE DAEH
+ {0xB31C, 0xB31C, prLV}, // Lo HANGUL SYLLABLE DYA
+ {0xB31D, 0xB337, prLVT}, // Lo [27] HANGUL SYLLABLE DYAG..HANGUL SYLLABLE DYAH
+ {0xB338, 0xB338, prLV}, // Lo HANGUL SYLLABLE DYAE
+ {0xB339, 0xB353, prLVT}, // Lo [27] HANGUL SYLLABLE DYAEG..HANGUL SYLLABLE DYAEH
+ {0xB354, 0xB354, prLV}, // Lo HANGUL SYLLABLE DEO
+ {0xB355, 0xB36F, prLVT}, // Lo [27] HANGUL SYLLABLE DEOG..HANGUL SYLLABLE DEOH
+ {0xB370, 0xB370, prLV}, // Lo HANGUL SYLLABLE DE
+ {0xB371, 0xB38B, prLVT}, // Lo [27] HANGUL SYLLABLE DEG..HANGUL SYLLABLE DEH
+ {0xB38C, 0xB38C, prLV}, // Lo HANGUL SYLLABLE DYEO
+ {0xB38D, 0xB3A7, prLVT}, // Lo [27] HANGUL SYLLABLE DYEOG..HANGUL SYLLABLE DYEOH
+ {0xB3A8, 0xB3A8, prLV}, // Lo HANGUL SYLLABLE DYE
+ {0xB3A9, 0xB3C3, prLVT}, // Lo [27] HANGUL SYLLABLE DYEG..HANGUL SYLLABLE DYEH
+ {0xB3C4, 0xB3C4, prLV}, // Lo HANGUL SYLLABLE DO
+ {0xB3C5, 0xB3DF, prLVT}, // Lo [27] HANGUL SYLLABLE DOG..HANGUL SYLLABLE DOH
+ {0xB3E0, 0xB3E0, prLV}, // Lo HANGUL SYLLABLE DWA
+ {0xB3E1, 0xB3FB, prLVT}, // Lo [27] HANGUL SYLLABLE DWAG..HANGUL SYLLABLE DWAH
+ {0xB3FC, 0xB3FC, prLV}, // Lo HANGUL SYLLABLE DWAE
+ {0xB3FD, 0xB417, prLVT}, // Lo [27] HANGUL SYLLABLE DWAEG..HANGUL SYLLABLE DWAEH
+ {0xB418, 0xB418, prLV}, // Lo HANGUL SYLLABLE DOE
+ {0xB419, 0xB433, prLVT}, // Lo [27] HANGUL SYLLABLE DOEG..HANGUL SYLLABLE DOEH
+ {0xB434, 0xB434, prLV}, // Lo HANGUL SYLLABLE DYO
+ {0xB435, 0xB44F, prLVT}, // Lo [27] HANGUL SYLLABLE DYOG..HANGUL SYLLABLE DYOH
+ {0xB450, 0xB450, prLV}, // Lo HANGUL SYLLABLE DU
+ {0xB451, 0xB46B, prLVT}, // Lo [27] HANGUL SYLLABLE DUG..HANGUL SYLLABLE DUH
+ {0xB46C, 0xB46C, prLV}, // Lo HANGUL SYLLABLE DWEO
+ {0xB46D, 0xB487, prLVT}, // Lo [27] HANGUL SYLLABLE DWEOG..HANGUL SYLLABLE DWEOH
+ {0xB488, 0xB488, prLV}, // Lo HANGUL SYLLABLE DWE
+ {0xB489, 0xB4A3, prLVT}, // Lo [27] HANGUL SYLLABLE DWEG..HANGUL SYLLABLE DWEH
+ {0xB4A4, 0xB4A4, prLV}, // Lo HANGUL SYLLABLE DWI
+ {0xB4A5, 0xB4BF, prLVT}, // Lo [27] HANGUL SYLLABLE DWIG..HANGUL SYLLABLE DWIH
+ {0xB4C0, 0xB4C0, prLV}, // Lo HANGUL SYLLABLE DYU
+ {0xB4C1, 0xB4DB, prLVT}, // Lo [27] HANGUL SYLLABLE DYUG..HANGUL SYLLABLE DYUH
+ {0xB4DC, 0xB4DC, prLV}, // Lo HANGUL SYLLABLE DEU
+ {0xB4DD, 0xB4F7, prLVT}, // Lo [27] HANGUL SYLLABLE DEUG..HANGUL SYLLABLE DEUH
+ {0xB4F8, 0xB4F8, prLV}, // Lo HANGUL SYLLABLE DYI
+ {0xB4F9, 0xB513, prLVT}, // Lo [27] HANGUL SYLLABLE DYIG..HANGUL SYLLABLE DYIH
+ {0xB514, 0xB514, prLV}, // Lo HANGUL SYLLABLE DI
+ {0xB515, 0xB52F, prLVT}, // Lo [27] HANGUL SYLLABLE DIG..HANGUL SYLLABLE DIH
+ {0xB530, 0xB530, prLV}, // Lo HANGUL SYLLABLE DDA
+ {0xB531, 0xB54B, prLVT}, // Lo [27] HANGUL SYLLABLE DDAG..HANGUL SYLLABLE DDAH
+ {0xB54C, 0xB54C, prLV}, // Lo HANGUL SYLLABLE DDAE
+ {0xB54D, 0xB567, prLVT}, // Lo [27] HANGUL SYLLABLE DDAEG..HANGUL SYLLABLE DDAEH
+ {0xB568, 0xB568, prLV}, // Lo HANGUL SYLLABLE DDYA
+ {0xB569, 0xB583, prLVT}, // Lo [27] HANGUL SYLLABLE DDYAG..HANGUL SYLLABLE DDYAH
+ {0xB584, 0xB584, prLV}, // Lo HANGUL SYLLABLE DDYAE
+ {0xB585, 0xB59F, prLVT}, // Lo [27] HANGUL SYLLABLE DDYAEG..HANGUL SYLLABLE DDYAEH
+ {0xB5A0, 0xB5A0, prLV}, // Lo HANGUL SYLLABLE DDEO
+ {0xB5A1, 0xB5BB, prLVT}, // Lo [27] HANGUL SYLLABLE DDEOG..HANGUL SYLLABLE DDEOH
+ {0xB5BC, 0xB5BC, prLV}, // Lo HANGUL SYLLABLE DDE
+ {0xB5BD, 0xB5D7, prLVT}, // Lo [27] HANGUL SYLLABLE DDEG..HANGUL SYLLABLE DDEH
+ {0xB5D8, 0xB5D8, prLV}, // Lo HANGUL SYLLABLE DDYEO
+ {0xB5D9, 0xB5F3, prLVT}, // Lo [27] HANGUL SYLLABLE DDYEOG..HANGUL SYLLABLE DDYEOH
+ {0xB5F4, 0xB5F4, prLV}, // Lo HANGUL SYLLABLE DDYE
+ {0xB5F5, 0xB60F, prLVT}, // Lo [27] HANGUL SYLLABLE DDYEG..HANGUL SYLLABLE DDYEH
+ {0xB610, 0xB610, prLV}, // Lo HANGUL SYLLABLE DDO
+ {0xB611, 0xB62B, prLVT}, // Lo [27] HANGUL SYLLABLE DDOG..HANGUL SYLLABLE DDOH
+ {0xB62C, 0xB62C, prLV}, // Lo HANGUL SYLLABLE DDWA
+ {0xB62D, 0xB647, prLVT}, // Lo [27] HANGUL SYLLABLE DDWAG..HANGUL SYLLABLE DDWAH
+ {0xB648, 0xB648, prLV}, // Lo HANGUL SYLLABLE DDWAE
+ {0xB649, 0xB663, prLVT}, // Lo [27] HANGUL SYLLABLE DDWAEG..HANGUL SYLLABLE DDWAEH
+ {0xB664, 0xB664, prLV}, // Lo HANGUL SYLLABLE DDOE
+ {0xB665, 0xB67F, prLVT}, // Lo [27] HANGUL SYLLABLE DDOEG..HANGUL SYLLABLE DDOEH
+ {0xB680, 0xB680, prLV}, // Lo HANGUL SYLLABLE DDYO
+ {0xB681, 0xB69B, prLVT}, // Lo [27] HANGUL SYLLABLE DDYOG..HANGUL SYLLABLE DDYOH
+ {0xB69C, 0xB69C, prLV}, // Lo HANGUL SYLLABLE DDU
+ {0xB69D, 0xB6B7, prLVT}, // Lo [27] HANGUL SYLLABLE DDUG..HANGUL SYLLABLE DDUH
+ {0xB6B8, 0xB6B8, prLV}, // Lo HANGUL SYLLABLE DDWEO
+ {0xB6B9, 0xB6D3, prLVT}, // Lo [27] HANGUL SYLLABLE DDWEOG..HANGUL SYLLABLE DDWEOH
+ {0xB6D4, 0xB6D4, prLV}, // Lo HANGUL SYLLABLE DDWE
+ {0xB6D5, 0xB6EF, prLVT}, // Lo [27] HANGUL SYLLABLE DDWEG..HANGUL SYLLABLE DDWEH
+ {0xB6F0, 0xB6F0, prLV}, // Lo HANGUL SYLLABLE DDWI
+ {0xB6F1, 0xB70B, prLVT}, // Lo [27] HANGUL SYLLABLE DDWIG..HANGUL SYLLABLE DDWIH
+ {0xB70C, 0xB70C, prLV}, // Lo HANGUL SYLLABLE DDYU
+ {0xB70D, 0xB727, prLVT}, // Lo [27] HANGUL SYLLABLE DDYUG..HANGUL SYLLABLE DDYUH
+ {0xB728, 0xB728, prLV}, // Lo HANGUL SYLLABLE DDEU
+ {0xB729, 0xB743, prLVT}, // Lo [27] HANGUL SYLLABLE DDEUG..HANGUL SYLLABLE DDEUH
+ {0xB744, 0xB744, prLV}, // Lo HANGUL SYLLABLE DDYI
+ {0xB745, 0xB75F, prLVT}, // Lo [27] HANGUL SYLLABLE DDYIG..HANGUL SYLLABLE DDYIH
+ {0xB760, 0xB760, prLV}, // Lo HANGUL SYLLABLE DDI
+ {0xB761, 0xB77B, prLVT}, // Lo [27] HANGUL SYLLABLE DDIG..HANGUL SYLLABLE DDIH
+ {0xB77C, 0xB77C, prLV}, // Lo HANGUL SYLLABLE RA
+ {0xB77D, 0xB797, prLVT}, // Lo [27] HANGUL SYLLABLE RAG..HANGUL SYLLABLE RAH
+ {0xB798, 0xB798, prLV}, // Lo HANGUL SYLLABLE RAE
+ {0xB799, 0xB7B3, prLVT}, // Lo [27] HANGUL SYLLABLE RAEG..HANGUL SYLLABLE RAEH
+ {0xB7B4, 0xB7B4, prLV}, // Lo HANGUL SYLLABLE RYA
+ {0xB7B5, 0xB7CF, prLVT}, // Lo [27] HANGUL SYLLABLE RYAG..HANGUL SYLLABLE RYAH
+ {0xB7D0, 0xB7D0, prLV}, // Lo HANGUL SYLLABLE RYAE
+ {0xB7D1, 0xB7EB, prLVT}, // Lo [27] HANGUL SYLLABLE RYAEG..HANGUL SYLLABLE RYAEH
+ {0xB7EC, 0xB7EC, prLV}, // Lo HANGUL SYLLABLE REO
+ {0xB7ED, 0xB807, prLVT}, // Lo [27] HANGUL SYLLABLE REOG..HANGUL SYLLABLE REOH
+ {0xB808, 0xB808, prLV}, // Lo HANGUL SYLLABLE RE
+ {0xB809, 0xB823, prLVT}, // Lo [27] HANGUL SYLLABLE REG..HANGUL SYLLABLE REH
+ {0xB824, 0xB824, prLV}, // Lo HANGUL SYLLABLE RYEO
+ {0xB825, 0xB83F, prLVT}, // Lo [27] HANGUL SYLLABLE RYEOG..HANGUL SYLLABLE RYEOH
+ {0xB840, 0xB840, prLV}, // Lo HANGUL SYLLABLE RYE
+ {0xB841, 0xB85B, prLVT}, // Lo [27] HANGUL SYLLABLE RYEG..HANGUL SYLLABLE RYEH
+ {0xB85C, 0xB85C, prLV}, // Lo HANGUL SYLLABLE RO
+ {0xB85D, 0xB877, prLVT}, // Lo [27] HANGUL SYLLABLE ROG..HANGUL SYLLABLE ROH
+ {0xB878, 0xB878, prLV}, // Lo HANGUL SYLLABLE RWA
+ {0xB879, 0xB893, prLVT}, // Lo [27] HANGUL SYLLABLE RWAG..HANGUL SYLLABLE RWAH
+ {0xB894, 0xB894, prLV}, // Lo HANGUL SYLLABLE RWAE
+ {0xB895, 0xB8AF, prLVT}, // Lo [27] HANGUL SYLLABLE RWAEG..HANGUL SYLLABLE RWAEH
+ {0xB8B0, 0xB8B0, prLV}, // Lo HANGUL SYLLABLE ROE
+ {0xB8B1, 0xB8CB, prLVT}, // Lo [27] HANGUL SYLLABLE ROEG..HANGUL SYLLABLE ROEH
+ {0xB8CC, 0xB8CC, prLV}, // Lo HANGUL SYLLABLE RYO
+ {0xB8CD, 0xB8E7, prLVT}, // Lo [27] HANGUL SYLLABLE RYOG..HANGUL SYLLABLE RYOH
+ {0xB8E8, 0xB8E8, prLV}, // Lo HANGUL SYLLABLE RU
+ {0xB8E9, 0xB903, prLVT}, // Lo [27] HANGUL SYLLABLE RUG..HANGUL SYLLABLE RUH
+ {0xB904, 0xB904, prLV}, // Lo HANGUL SYLLABLE RWEO
+ {0xB905, 0xB91F, prLVT}, // Lo [27] HANGUL SYLLABLE RWEOG..HANGUL SYLLABLE RWEOH
+ {0xB920, 0xB920, prLV}, // Lo HANGUL SYLLABLE RWE
+ {0xB921, 0xB93B, prLVT}, // Lo [27] HANGUL SYLLABLE RWEG..HANGUL SYLLABLE RWEH
+ {0xB93C, 0xB93C, prLV}, // Lo HANGUL SYLLABLE RWI
+ {0xB93D, 0xB957, prLVT}, // Lo [27] HANGUL SYLLABLE RWIG..HANGUL SYLLABLE RWIH
+ {0xB958, 0xB958, prLV}, // Lo HANGUL SYLLABLE RYU
+ {0xB959, 0xB973, prLVT}, // Lo [27] HANGUL SYLLABLE RYUG..HANGUL SYLLABLE RYUH
+ {0xB974, 0xB974, prLV}, // Lo HANGUL SYLLABLE REU
+ {0xB975, 0xB98F, prLVT}, // Lo [27] HANGUL SYLLABLE REUG..HANGUL SYLLABLE REUH
+ {0xB990, 0xB990, prLV}, // Lo HANGUL SYLLABLE RYI
+ {0xB991, 0xB9AB, prLVT}, // Lo [27] HANGUL SYLLABLE RYIG..HANGUL SYLLABLE RYIH
+ {0xB9AC, 0xB9AC, prLV}, // Lo HANGUL SYLLABLE RI
+ {0xB9AD, 0xB9C7, prLVT}, // Lo [27] HANGUL SYLLABLE RIG..HANGUL SYLLABLE RIH
+ {0xB9C8, 0xB9C8, prLV}, // Lo HANGUL SYLLABLE MA
+ {0xB9C9, 0xB9E3, prLVT}, // Lo [27] HANGUL SYLLABLE MAG..HANGUL SYLLABLE MAH
+ {0xB9E4, 0xB9E4, prLV}, // Lo HANGUL SYLLABLE MAE
+ {0xB9E5, 0xB9FF, prLVT}, // Lo [27] HANGUL SYLLABLE MAEG..HANGUL SYLLABLE MAEH
+ {0xBA00, 0xBA00, prLV}, // Lo HANGUL SYLLABLE MYA
+ {0xBA01, 0xBA1B, prLVT}, // Lo [27] HANGUL SYLLABLE MYAG..HANGUL SYLLABLE MYAH
+ {0xBA1C, 0xBA1C, prLV}, // Lo HANGUL SYLLABLE MYAE
+ {0xBA1D, 0xBA37, prLVT}, // Lo [27] HANGUL SYLLABLE MYAEG..HANGUL SYLLABLE MYAEH
+ {0xBA38, 0xBA38, prLV}, // Lo HANGUL SYLLABLE MEO
+ {0xBA39, 0xBA53, prLVT}, // Lo [27] HANGUL SYLLABLE MEOG..HANGUL SYLLABLE MEOH
+ {0xBA54, 0xBA54, prLV}, // Lo HANGUL SYLLABLE ME
+ {0xBA55, 0xBA6F, prLVT}, // Lo [27] HANGUL SYLLABLE MEG..HANGUL SYLLABLE MEH
+ {0xBA70, 0xBA70, prLV}, // Lo HANGUL SYLLABLE MYEO
+ {0xBA71, 0xBA8B, prLVT}, // Lo [27] HANGUL SYLLABLE MYEOG..HANGUL SYLLABLE MYEOH
+ {0xBA8C, 0xBA8C, prLV}, // Lo HANGUL SYLLABLE MYE
+ {0xBA8D, 0xBAA7, prLVT}, // Lo [27] HANGUL SYLLABLE MYEG..HANGUL SYLLABLE MYEH
+ {0xBAA8, 0xBAA8, prLV}, // Lo HANGUL SYLLABLE MO
+ {0xBAA9, 0xBAC3, prLVT}, // Lo [27] HANGUL SYLLABLE MOG..HANGUL SYLLABLE MOH
+ {0xBAC4, 0xBAC4, prLV}, // Lo HANGUL SYLLABLE MWA
+ {0xBAC5, 0xBADF, prLVT}, // Lo [27] HANGUL SYLLABLE MWAG..HANGUL SYLLABLE MWAH
+ {0xBAE0, 0xBAE0, prLV}, // Lo HANGUL SYLLABLE MWAE
+ {0xBAE1, 0xBAFB, prLVT}, // Lo [27] HANGUL SYLLABLE MWAEG..HANGUL SYLLABLE MWAEH
+ {0xBAFC, 0xBAFC, prLV}, // Lo HANGUL SYLLABLE MOE
+ {0xBAFD, 0xBB17, prLVT}, // Lo [27] HANGUL SYLLABLE MOEG..HANGUL SYLLABLE MOEH
+ {0xBB18, 0xBB18, prLV}, // Lo HANGUL SYLLABLE MYO
+ {0xBB19, 0xBB33, prLVT}, // Lo [27] HANGUL SYLLABLE MYOG..HANGUL SYLLABLE MYOH
+ {0xBB34, 0xBB34, prLV}, // Lo HANGUL SYLLABLE MU
+ {0xBB35, 0xBB4F, prLVT}, // Lo [27] HANGUL SYLLABLE MUG..HANGUL SYLLABLE MUH
+ {0xBB50, 0xBB50, prLV}, // Lo HANGUL SYLLABLE MWEO
+ {0xBB51, 0xBB6B, prLVT}, // Lo [27] HANGUL SYLLABLE MWEOG..HANGUL SYLLABLE MWEOH
+ {0xBB6C, 0xBB6C, prLV}, // Lo HANGUL SYLLABLE MWE
+ {0xBB6D, 0xBB87, prLVT}, // Lo [27] HANGUL SYLLABLE MWEG..HANGUL SYLLABLE MWEH
+ {0xBB88, 0xBB88, prLV}, // Lo HANGUL SYLLABLE MWI
+ {0xBB89, 0xBBA3, prLVT}, // Lo [27] HANGUL SYLLABLE MWIG..HANGUL SYLLABLE MWIH
+ {0xBBA4, 0xBBA4, prLV}, // Lo HANGUL SYLLABLE MYU
+ {0xBBA5, 0xBBBF, prLVT}, // Lo [27] HANGUL SYLLABLE MYUG..HANGUL SYLLABLE MYUH
+ {0xBBC0, 0xBBC0, prLV}, // Lo HANGUL SYLLABLE MEU
+ {0xBBC1, 0xBBDB, prLVT}, // Lo [27] HANGUL SYLLABLE MEUG..HANGUL SYLLABLE MEUH
+ {0xBBDC, 0xBBDC, prLV}, // Lo HANGUL SYLLABLE MYI
+ {0xBBDD, 0xBBF7, prLVT}, // Lo [27] HANGUL SYLLABLE MYIG..HANGUL SYLLABLE MYIH
+ {0xBBF8, 0xBBF8, prLV}, // Lo HANGUL SYLLABLE MI
+ {0xBBF9, 0xBC13, prLVT}, // Lo [27] HANGUL SYLLABLE MIG..HANGUL SYLLABLE MIH
+ {0xBC14, 0xBC14, prLV}, // Lo HANGUL SYLLABLE BA
+ {0xBC15, 0xBC2F, prLVT}, // Lo [27] HANGUL SYLLABLE BAG..HANGUL SYLLABLE BAH
+ {0xBC30, 0xBC30, prLV}, // Lo HANGUL SYLLABLE BAE
+ {0xBC31, 0xBC4B, prLVT}, // Lo [27] HANGUL SYLLABLE BAEG..HANGUL SYLLABLE BAEH
+ {0xBC4C, 0xBC4C, prLV}, // Lo HANGUL SYLLABLE BYA
+ {0xBC4D, 0xBC67, prLVT}, // Lo [27] HANGUL SYLLABLE BYAG..HANGUL SYLLABLE BYAH
+ {0xBC68, 0xBC68, prLV}, // Lo HANGUL SYLLABLE BYAE
+ {0xBC69, 0xBC83, prLVT}, // Lo [27] HANGUL SYLLABLE BYAEG..HANGUL SYLLABLE BYAEH
+ {0xBC84, 0xBC84, prLV}, // Lo HANGUL SYLLABLE BEO
+ {0xBC85, 0xBC9F, prLVT}, // Lo [27] HANGUL SYLLABLE BEOG..HANGUL SYLLABLE BEOH
+ {0xBCA0, 0xBCA0, prLV}, // Lo HANGUL SYLLABLE BE
+ {0xBCA1, 0xBCBB, prLVT}, // Lo [27] HANGUL SYLLABLE BEG..HANGUL SYLLABLE BEH
+ {0xBCBC, 0xBCBC, prLV}, // Lo HANGUL SYLLABLE BYEO
+ {0xBCBD, 0xBCD7, prLVT}, // Lo [27] HANGUL SYLLABLE BYEOG..HANGUL SYLLABLE BYEOH
+ {0xBCD8, 0xBCD8, prLV}, // Lo HANGUL SYLLABLE BYE
+ {0xBCD9, 0xBCF3, prLVT}, // Lo [27] HANGUL SYLLABLE BYEG..HANGUL SYLLABLE BYEH
+ {0xBCF4, 0xBCF4, prLV}, // Lo HANGUL SYLLABLE BO
+ {0xBCF5, 0xBD0F, prLVT}, // Lo [27] HANGUL SYLLABLE BOG..HANGUL SYLLABLE BOH
+ {0xBD10, 0xBD10, prLV}, // Lo HANGUL SYLLABLE BWA
+ {0xBD11, 0xBD2B, prLVT}, // Lo [27] HANGUL SYLLABLE BWAG..HANGUL SYLLABLE BWAH
+ {0xBD2C, 0xBD2C, prLV}, // Lo HANGUL SYLLABLE BWAE
+ {0xBD2D, 0xBD47, prLVT}, // Lo [27] HANGUL SYLLABLE BWAEG..HANGUL SYLLABLE BWAEH
+ {0xBD48, 0xBD48, prLV}, // Lo HANGUL SYLLABLE BOE
+ {0xBD49, 0xBD63, prLVT}, // Lo [27] HANGUL SYLLABLE BOEG..HANGUL SYLLABLE BOEH
+ {0xBD64, 0xBD64, prLV}, // Lo HANGUL SYLLABLE BYO
+ {0xBD65, 0xBD7F, prLVT}, // Lo [27] HANGUL SYLLABLE BYOG..HANGUL SYLLABLE BYOH
+ {0xBD80, 0xBD80, prLV}, // Lo HANGUL SYLLABLE BU
+ {0xBD81, 0xBD9B, prLVT}, // Lo [27] HANGUL SYLLABLE BUG..HANGUL SYLLABLE BUH
+ {0xBD9C, 0xBD9C, prLV}, // Lo HANGUL SYLLABLE BWEO
+ {0xBD9D, 0xBDB7, prLVT}, // Lo [27] HANGUL SYLLABLE BWEOG..HANGUL SYLLABLE BWEOH
+ {0xBDB8, 0xBDB8, prLV}, // Lo HANGUL SYLLABLE BWE
+ {0xBDB9, 0xBDD3, prLVT}, // Lo [27] HANGUL SYLLABLE BWEG..HANGUL SYLLABLE BWEH
+ {0xBDD4, 0xBDD4, prLV}, // Lo HANGUL SYLLABLE BWI
+ {0xBDD5, 0xBDEF, prLVT}, // Lo [27] HANGUL SYLLABLE BWIG..HANGUL SYLLABLE BWIH
+ {0xBDF0, 0xBDF0, prLV}, // Lo HANGUL SYLLABLE BYU
+ {0xBDF1, 0xBE0B, prLVT}, // Lo [27] HANGUL SYLLABLE BYUG..HANGUL SYLLABLE BYUH
+ {0xBE0C, 0xBE0C, prLV}, // Lo HANGUL SYLLABLE BEU
+ {0xBE0D, 0xBE27, prLVT}, // Lo [27] HANGUL SYLLABLE BEUG..HANGUL SYLLABLE BEUH
+ {0xBE28, 0xBE28, prLV}, // Lo HANGUL SYLLABLE BYI
+ {0xBE29, 0xBE43, prLVT}, // Lo [27] HANGUL SYLLABLE BYIG..HANGUL SYLLABLE BYIH
+ {0xBE44, 0xBE44, prLV}, // Lo HANGUL SYLLABLE BI
+ {0xBE45, 0xBE5F, prLVT}, // Lo [27] HANGUL SYLLABLE BIG..HANGUL SYLLABLE BIH
+ {0xBE60, 0xBE60, prLV}, // Lo HANGUL SYLLABLE BBA
+ {0xBE61, 0xBE7B, prLVT}, // Lo [27] HANGUL SYLLABLE BBAG..HANGUL SYLLABLE BBAH
+ {0xBE7C, 0xBE7C, prLV}, // Lo HANGUL SYLLABLE BBAE
+ {0xBE7D, 0xBE97, prLVT}, // Lo [27] HANGUL SYLLABLE BBAEG..HANGUL SYLLABLE BBAEH
+ {0xBE98, 0xBE98, prLV}, // Lo HANGUL SYLLABLE BBYA
+ {0xBE99, 0xBEB3, prLVT}, // Lo [27] HANGUL SYLLABLE BBYAG..HANGUL SYLLABLE BBYAH
+ {0xBEB4, 0xBEB4, prLV}, // Lo HANGUL SYLLABLE BBYAE
+ {0xBEB5, 0xBECF, prLVT}, // Lo [27] HANGUL SYLLABLE BBYAEG..HANGUL SYLLABLE BBYAEH
+ {0xBED0, 0xBED0, prLV}, // Lo HANGUL SYLLABLE BBEO
+ {0xBED1, 0xBEEB, prLVT}, // Lo [27] HANGUL SYLLABLE BBEOG..HANGUL SYLLABLE BBEOH
+ {0xBEEC, 0xBEEC, prLV}, // Lo HANGUL SYLLABLE BBE
+ {0xBEED, 0xBF07, prLVT}, // Lo [27] HANGUL SYLLABLE BBEG..HANGUL SYLLABLE BBEH
+ {0xBF08, 0xBF08, prLV}, // Lo HANGUL SYLLABLE BBYEO
+ {0xBF09, 0xBF23, prLVT}, // Lo [27] HANGUL SYLLABLE BBYEOG..HANGUL SYLLABLE BBYEOH
+ {0xBF24, 0xBF24, prLV}, // Lo HANGUL SYLLABLE BBYE
+ {0xBF25, 0xBF3F, prLVT}, // Lo [27] HANGUL SYLLABLE BBYEG..HANGUL SYLLABLE BBYEH
+ {0xBF40, 0xBF40, prLV}, // Lo HANGUL SYLLABLE BBO
+ {0xBF41, 0xBF5B, prLVT}, // Lo [27] HANGUL SYLLABLE BBOG..HANGUL SYLLABLE BBOH
+ {0xBF5C, 0xBF5C, prLV}, // Lo HANGUL SYLLABLE BBWA
+ {0xBF5D, 0xBF77, prLVT}, // Lo [27] HANGUL SYLLABLE BBWAG..HANGUL SYLLABLE BBWAH
+ {0xBF78, 0xBF78, prLV}, // Lo HANGUL SYLLABLE BBWAE
+ {0xBF79, 0xBF93, prLVT}, // Lo [27] HANGUL SYLLABLE BBWAEG..HANGUL SYLLABLE BBWAEH
+ {0xBF94, 0xBF94, prLV}, // Lo HANGUL SYLLABLE BBOE
+ {0xBF95, 0xBFAF, prLVT}, // Lo [27] HANGUL SYLLABLE BBOEG..HANGUL SYLLABLE BBOEH
+ {0xBFB0, 0xBFB0, prLV}, // Lo HANGUL SYLLABLE BBYO
+ {0xBFB1, 0xBFCB, prLVT}, // Lo [27] HANGUL SYLLABLE BBYOG..HANGUL SYLLABLE BBYOH
+ {0xBFCC, 0xBFCC, prLV}, // Lo HANGUL SYLLABLE BBU
+ {0xBFCD, 0xBFE7, prLVT}, // Lo [27] HANGUL SYLLABLE BBUG..HANGUL SYLLABLE BBUH
+ {0xBFE8, 0xBFE8, prLV}, // Lo HANGUL SYLLABLE BBWEO
+ {0xBFE9, 0xC003, prLVT}, // Lo [27] HANGUL SYLLABLE BBWEOG..HANGUL SYLLABLE BBWEOH
+ {0xC004, 0xC004, prLV}, // Lo HANGUL SYLLABLE BBWE
+ {0xC005, 0xC01F, prLVT}, // Lo [27] HANGUL SYLLABLE BBWEG..HANGUL SYLLABLE BBWEH
+ {0xC020, 0xC020, prLV}, // Lo HANGUL SYLLABLE BBWI
+ {0xC021, 0xC03B, prLVT}, // Lo [27] HANGUL SYLLABLE BBWIG..HANGUL SYLLABLE BBWIH
+ {0xC03C, 0xC03C, prLV}, // Lo HANGUL SYLLABLE BBYU
+ {0xC03D, 0xC057, prLVT}, // Lo [27] HANGUL SYLLABLE BBYUG..HANGUL SYLLABLE BBYUH
+ {0xC058, 0xC058, prLV}, // Lo HANGUL SYLLABLE BBEU
+ {0xC059, 0xC073, prLVT}, // Lo [27] HANGUL SYLLABLE BBEUG..HANGUL SYLLABLE BBEUH
+ {0xC074, 0xC074, prLV}, // Lo HANGUL SYLLABLE BBYI
+ {0xC075, 0xC08F, prLVT}, // Lo [27] HANGUL SYLLABLE BBYIG..HANGUL SYLLABLE BBYIH
+ {0xC090, 0xC090, prLV}, // Lo HANGUL SYLLABLE BBI
+ {0xC091, 0xC0AB, prLVT}, // Lo [27] HANGUL SYLLABLE BBIG..HANGUL SYLLABLE BBIH
+ {0xC0AC, 0xC0AC, prLV}, // Lo HANGUL SYLLABLE SA
+ {0xC0AD, 0xC0C7, prLVT}, // Lo [27] HANGUL SYLLABLE SAG..HANGUL SYLLABLE SAH
+ {0xC0C8, 0xC0C8, prLV}, // Lo HANGUL SYLLABLE SAE
+ {0xC0C9, 0xC0E3, prLVT}, // Lo [27] HANGUL SYLLABLE SAEG..HANGUL SYLLABLE SAEH
+ {0xC0E4, 0xC0E4, prLV}, // Lo HANGUL SYLLABLE SYA
+ {0xC0E5, 0xC0FF, prLVT}, // Lo [27] HANGUL SYLLABLE SYAG..HANGUL SYLLABLE SYAH
+ {0xC100, 0xC100, prLV}, // Lo HANGUL SYLLABLE SYAE
+ {0xC101, 0xC11B, prLVT}, // Lo [27] HANGUL SYLLABLE SYAEG..HANGUL SYLLABLE SYAEH
+ {0xC11C, 0xC11C, prLV}, // Lo HANGUL SYLLABLE SEO
+ {0xC11D, 0xC137, prLVT}, // Lo [27] HANGUL SYLLABLE SEOG..HANGUL SYLLABLE SEOH
+ {0xC138, 0xC138, prLV}, // Lo HANGUL SYLLABLE SE
+ {0xC139, 0xC153, prLVT}, // Lo [27] HANGUL SYLLABLE SEG..HANGUL SYLLABLE SEH
+ {0xC154, 0xC154, prLV}, // Lo HANGUL SYLLABLE SYEO
+ {0xC155, 0xC16F, prLVT}, // Lo [27] HANGUL SYLLABLE SYEOG..HANGUL SYLLABLE SYEOH
+ {0xC170, 0xC170, prLV}, // Lo HANGUL SYLLABLE SYE
+ {0xC171, 0xC18B, prLVT}, // Lo [27] HANGUL SYLLABLE SYEG..HANGUL SYLLABLE SYEH
+ {0xC18C, 0xC18C, prLV}, // Lo HANGUL SYLLABLE SO
+ {0xC18D, 0xC1A7, prLVT}, // Lo [27] HANGUL SYLLABLE SOG..HANGUL SYLLABLE SOH
+ {0xC1A8, 0xC1A8, prLV}, // Lo HANGUL SYLLABLE SWA
+ {0xC1A9, 0xC1C3, prLVT}, // Lo [27] HANGUL SYLLABLE SWAG..HANGUL SYLLABLE SWAH
+ {0xC1C4, 0xC1C4, prLV}, // Lo HANGUL SYLLABLE SWAE
+ {0xC1C5, 0xC1DF, prLVT}, // Lo [27] HANGUL SYLLABLE SWAEG..HANGUL SYLLABLE SWAEH
+ {0xC1E0, 0xC1E0, prLV}, // Lo HANGUL SYLLABLE SOE
+ {0xC1E1, 0xC1FB, prLVT}, // Lo [27] HANGUL SYLLABLE SOEG..HANGUL SYLLABLE SOEH
+ {0xC1FC, 0xC1FC, prLV}, // Lo HANGUL SYLLABLE SYO
+ {0xC1FD, 0xC217, prLVT}, // Lo [27] HANGUL SYLLABLE SYOG..HANGUL SYLLABLE SYOH
+ {0xC218, 0xC218, prLV}, // Lo HANGUL SYLLABLE SU
+ {0xC219, 0xC233, prLVT}, // Lo [27] HANGUL SYLLABLE SUG..HANGUL SYLLABLE SUH
+ {0xC234, 0xC234, prLV}, // Lo HANGUL SYLLABLE SWEO
+ {0xC235, 0xC24F, prLVT}, // Lo [27] HANGUL SYLLABLE SWEOG..HANGUL SYLLABLE SWEOH
+ {0xC250, 0xC250, prLV}, // Lo HANGUL SYLLABLE SWE
+ {0xC251, 0xC26B, prLVT}, // Lo [27] HANGUL SYLLABLE SWEG..HANGUL SYLLABLE SWEH
+ {0xC26C, 0xC26C, prLV}, // Lo HANGUL SYLLABLE SWI
+ {0xC26D, 0xC287, prLVT}, // Lo [27] HANGUL SYLLABLE SWIG..HANGUL SYLLABLE SWIH
+ {0xC288, 0xC288, prLV}, // Lo HANGUL SYLLABLE SYU
+ {0xC289, 0xC2A3, prLVT}, // Lo [27] HANGUL SYLLABLE SYUG..HANGUL SYLLABLE SYUH
+ {0xC2A4, 0xC2A4, prLV}, // Lo HANGUL SYLLABLE SEU
+ {0xC2A5, 0xC2BF, prLVT}, // Lo [27] HANGUL SYLLABLE SEUG..HANGUL SYLLABLE SEUH
+ {0xC2C0, 0xC2C0, prLV}, // Lo HANGUL SYLLABLE SYI
+ {0xC2C1, 0xC2DB, prLVT}, // Lo [27] HANGUL SYLLABLE SYIG..HANGUL SYLLABLE SYIH
+ {0xC2DC, 0xC2DC, prLV}, // Lo HANGUL SYLLABLE SI
+ {0xC2DD, 0xC2F7, prLVT}, // Lo [27] HANGUL SYLLABLE SIG..HANGUL SYLLABLE SIH
+ {0xC2F8, 0xC2F8, prLV}, // Lo HANGUL SYLLABLE SSA
+ {0xC2F9, 0xC313, prLVT}, // Lo [27] HANGUL SYLLABLE SSAG..HANGUL SYLLABLE SSAH
+ {0xC314, 0xC314, prLV}, // Lo HANGUL SYLLABLE SSAE
+ {0xC315, 0xC32F, prLVT}, // Lo [27] HANGUL SYLLABLE SSAEG..HANGUL SYLLABLE SSAEH
+ {0xC330, 0xC330, prLV}, // Lo HANGUL SYLLABLE SSYA
+ {0xC331, 0xC34B, prLVT}, // Lo [27] HANGUL SYLLABLE SSYAG..HANGUL SYLLABLE SSYAH
+ {0xC34C, 0xC34C, prLV}, // Lo HANGUL SYLLABLE SSYAE
+ {0xC34D, 0xC367, prLVT}, // Lo [27] HANGUL SYLLABLE SSYAEG..HANGUL SYLLABLE SSYAEH
+ {0xC368, 0xC368, prLV}, // Lo HANGUL SYLLABLE SSEO
+ {0xC369, 0xC383, prLVT}, // Lo [27] HANGUL SYLLABLE SSEOG..HANGUL SYLLABLE SSEOH
+ {0xC384, 0xC384, prLV}, // Lo HANGUL SYLLABLE SSE
+ {0xC385, 0xC39F, prLVT}, // Lo [27] HANGUL SYLLABLE SSEG..HANGUL SYLLABLE SSEH
+ {0xC3A0, 0xC3A0, prLV}, // Lo HANGUL SYLLABLE SSYEO
+ {0xC3A1, 0xC3BB, prLVT}, // Lo [27] HANGUL SYLLABLE SSYEOG..HANGUL SYLLABLE SSYEOH
+ {0xC3BC, 0xC3BC, prLV}, // Lo HANGUL SYLLABLE SSYE
+ {0xC3BD, 0xC3D7, prLVT}, // Lo [27] HANGUL SYLLABLE SSYEG..HANGUL SYLLABLE SSYEH
+ {0xC3D8, 0xC3D8, prLV}, // Lo HANGUL SYLLABLE SSO
+ {0xC3D9, 0xC3F3, prLVT}, // Lo [27] HANGUL SYLLABLE SSOG..HANGUL SYLLABLE SSOH
+ {0xC3F4, 0xC3F4, prLV}, // Lo HANGUL SYLLABLE SSWA
+ {0xC3F5, 0xC40F, prLVT}, // Lo [27] HANGUL SYLLABLE SSWAG..HANGUL SYLLABLE SSWAH
+ {0xC410, 0xC410, prLV}, // Lo HANGUL SYLLABLE SSWAE
+ {0xC411, 0xC42B, prLVT}, // Lo [27] HANGUL SYLLABLE SSWAEG..HANGUL SYLLABLE SSWAEH
+ {0xC42C, 0xC42C, prLV}, // Lo HANGUL SYLLABLE SSOE
+ {0xC42D, 0xC447, prLVT}, // Lo [27] HANGUL SYLLABLE SSOEG..HANGUL SYLLABLE SSOEH
+ {0xC448, 0xC448, prLV}, // Lo HANGUL SYLLABLE SSYO
+ {0xC449, 0xC463, prLVT}, // Lo [27] HANGUL SYLLABLE SSYOG..HANGUL SYLLABLE SSYOH
+ {0xC464, 0xC464, prLV}, // Lo HANGUL SYLLABLE SSU
+ {0xC465, 0xC47F, prLVT}, // Lo [27] HANGUL SYLLABLE SSUG..HANGUL SYLLABLE SSUH
+ {0xC480, 0xC480, prLV}, // Lo HANGUL SYLLABLE SSWEO
+ {0xC481, 0xC49B, prLVT}, // Lo [27] HANGUL SYLLABLE SSWEOG..HANGUL SYLLABLE SSWEOH
+ {0xC49C, 0xC49C, prLV}, // Lo HANGUL SYLLABLE SSWE
+ {0xC49D, 0xC4B7, prLVT}, // Lo [27] HANGUL SYLLABLE SSWEG..HANGUL SYLLABLE SSWEH
+ {0xC4B8, 0xC4B8, prLV}, // Lo HANGUL SYLLABLE SSWI
+ {0xC4B9, 0xC4D3, prLVT}, // Lo [27] HANGUL SYLLABLE SSWIG..HANGUL SYLLABLE SSWIH
+ {0xC4D4, 0xC4D4, prLV}, // Lo HANGUL SYLLABLE SSYU
+ {0xC4D5, 0xC4EF, prLVT}, // Lo [27] HANGUL SYLLABLE SSYUG..HANGUL SYLLABLE SSYUH
+ {0xC4F0, 0xC4F0, prLV}, // Lo HANGUL SYLLABLE SSEU
+ {0xC4F1, 0xC50B, prLVT}, // Lo [27] HANGUL SYLLABLE SSEUG..HANGUL SYLLABLE SSEUH
+ {0xC50C, 0xC50C, prLV}, // Lo HANGUL SYLLABLE SSYI
+ {0xC50D, 0xC527, prLVT}, // Lo [27] HANGUL SYLLABLE SSYIG..HANGUL SYLLABLE SSYIH
+ {0xC528, 0xC528, prLV}, // Lo HANGUL SYLLABLE SSI
+ {0xC529, 0xC543, prLVT}, // Lo [27] HANGUL SYLLABLE SSIG..HANGUL SYLLABLE SSIH
+ {0xC544, 0xC544, prLV}, // Lo HANGUL SYLLABLE A
+ {0xC545, 0xC55F, prLVT}, // Lo [27] HANGUL SYLLABLE AG..HANGUL SYLLABLE AH
+ {0xC560, 0xC560, prLV}, // Lo HANGUL SYLLABLE AE
+ {0xC561, 0xC57B, prLVT}, // Lo [27] HANGUL SYLLABLE AEG..HANGUL SYLLABLE AEH
+ {0xC57C, 0xC57C, prLV}, // Lo HANGUL SYLLABLE YA
+ {0xC57D, 0xC597, prLVT}, // Lo [27] HANGUL SYLLABLE YAG..HANGUL SYLLABLE YAH
+ {0xC598, 0xC598, prLV}, // Lo HANGUL SYLLABLE YAE
+ {0xC599, 0xC5B3, prLVT}, // Lo [27] HANGUL SYLLABLE YAEG..HANGUL SYLLABLE YAEH
+ {0xC5B4, 0xC5B4, prLV}, // Lo HANGUL SYLLABLE EO
+ {0xC5B5, 0xC5CF, prLVT}, // Lo [27] HANGUL SYLLABLE EOG..HANGUL SYLLABLE EOH
+ {0xC5D0, 0xC5D0, prLV}, // Lo HANGUL SYLLABLE E
+ {0xC5D1, 0xC5EB, prLVT}, // Lo [27] HANGUL SYLLABLE EG..HANGUL SYLLABLE EH
+ {0xC5EC, 0xC5EC, prLV}, // Lo HANGUL SYLLABLE YEO
+ {0xC5ED, 0xC607, prLVT}, // Lo [27] HANGUL SYLLABLE YEOG..HANGUL SYLLABLE YEOH
+ {0xC608, 0xC608, prLV}, // Lo HANGUL SYLLABLE YE
+ {0xC609, 0xC623, prLVT}, // Lo [27] HANGUL SYLLABLE YEG..HANGUL SYLLABLE YEH
+ {0xC624, 0xC624, prLV}, // Lo HANGUL SYLLABLE O
+ {0xC625, 0xC63F, prLVT}, // Lo [27] HANGUL SYLLABLE OG..HANGUL SYLLABLE OH
+ {0xC640, 0xC640, prLV}, // Lo HANGUL SYLLABLE WA
+ {0xC641, 0xC65B, prLVT}, // Lo [27] HANGUL SYLLABLE WAG..HANGUL SYLLABLE WAH
+ {0xC65C, 0xC65C, prLV}, // Lo HANGUL SYLLABLE WAE
+ {0xC65D, 0xC677, prLVT}, // Lo [27] HANGUL SYLLABLE WAEG..HANGUL SYLLABLE WAEH
+ {0xC678, 0xC678, prLV}, // Lo HANGUL SYLLABLE OE
+ {0xC679, 0xC693, prLVT}, // Lo [27] HANGUL SYLLABLE OEG..HANGUL SYLLABLE OEH
+ {0xC694, 0xC694, prLV}, // Lo HANGUL SYLLABLE YO
+ {0xC695, 0xC6AF, prLVT}, // Lo [27] HANGUL SYLLABLE YOG..HANGUL SYLLABLE YOH
+ {0xC6B0, 0xC6B0, prLV}, // Lo HANGUL SYLLABLE U
+ {0xC6B1, 0xC6CB, prLVT}, // Lo [27] HANGUL SYLLABLE UG..HANGUL SYLLABLE UH
+ {0xC6CC, 0xC6CC, prLV}, // Lo HANGUL SYLLABLE WEO
+ {0xC6CD, 0xC6E7, prLVT}, // Lo [27] HANGUL SYLLABLE WEOG..HANGUL SYLLABLE WEOH
+ {0xC6E8, 0xC6E8, prLV}, // Lo HANGUL SYLLABLE WE
+ {0xC6E9, 0xC703, prLVT}, // Lo [27] HANGUL SYLLABLE WEG..HANGUL SYLLABLE WEH
+ {0xC704, 0xC704, prLV}, // Lo HANGUL SYLLABLE WI
+ {0xC705, 0xC71F, prLVT}, // Lo [27] HANGUL SYLLABLE WIG..HANGUL SYLLABLE WIH
+ {0xC720, 0xC720, prLV}, // Lo HANGUL SYLLABLE YU
+ {0xC721, 0xC73B, prLVT}, // Lo [27] HANGUL SYLLABLE YUG..HANGUL SYLLABLE YUH
+ {0xC73C, 0xC73C, prLV}, // Lo HANGUL SYLLABLE EU
+ {0xC73D, 0xC757, prLVT}, // Lo [27] HANGUL SYLLABLE EUG..HANGUL SYLLABLE EUH
+ {0xC758, 0xC758, prLV}, // Lo HANGUL SYLLABLE YI
+ {0xC759, 0xC773, prLVT}, // Lo [27] HANGUL SYLLABLE YIG..HANGUL SYLLABLE YIH
+ {0xC774, 0xC774, prLV}, // Lo HANGUL SYLLABLE I
+ {0xC775, 0xC78F, prLVT}, // Lo [27] HANGUL SYLLABLE IG..HANGUL SYLLABLE IH
+ {0xC790, 0xC790, prLV}, // Lo HANGUL SYLLABLE JA
+ {0xC791, 0xC7AB, prLVT}, // Lo [27] HANGUL SYLLABLE JAG..HANGUL SYLLABLE JAH
+ {0xC7AC, 0xC7AC, prLV}, // Lo HANGUL SYLLABLE JAE
+ {0xC7AD, 0xC7C7, prLVT}, // Lo [27] HANGUL SYLLABLE JAEG..HANGUL SYLLABLE JAEH
+ {0xC7C8, 0xC7C8, prLV}, // Lo HANGUL SYLLABLE JYA
+ {0xC7C9, 0xC7E3, prLVT}, // Lo [27] HANGUL SYLLABLE JYAG..HANGUL SYLLABLE JYAH
+ {0xC7E4, 0xC7E4, prLV}, // Lo HANGUL SYLLABLE JYAE
+ {0xC7E5, 0xC7FF, prLVT}, // Lo [27] HANGUL SYLLABLE JYAEG..HANGUL SYLLABLE JYAEH
+ {0xC800, 0xC800, prLV}, // Lo HANGUL SYLLABLE JEO
+ {0xC801, 0xC81B, prLVT}, // Lo [27] HANGUL SYLLABLE JEOG..HANGUL SYLLABLE JEOH
+ {0xC81C, 0xC81C, prLV}, // Lo HANGUL SYLLABLE JE
+ {0xC81D, 0xC837, prLVT}, // Lo [27] HANGUL SYLLABLE JEG..HANGUL SYLLABLE JEH
+ {0xC838, 0xC838, prLV}, // Lo HANGUL SYLLABLE JYEO
+ {0xC839, 0xC853, prLVT}, // Lo [27] HANGUL SYLLABLE JYEOG..HANGUL SYLLABLE JYEOH
+ {0xC854, 0xC854, prLV}, // Lo HANGUL SYLLABLE JYE
+ {0xC855, 0xC86F, prLVT}, // Lo [27] HANGUL SYLLABLE JYEG..HANGUL SYLLABLE JYEH
+ {0xC870, 0xC870, prLV}, // Lo HANGUL SYLLABLE JO
+ {0xC871, 0xC88B, prLVT}, // Lo [27] HANGUL SYLLABLE JOG..HANGUL SYLLABLE JOH
+ {0xC88C, 0xC88C, prLV}, // Lo HANGUL SYLLABLE JWA
+ {0xC88D, 0xC8A7, prLVT}, // Lo [27] HANGUL SYLLABLE JWAG..HANGUL SYLLABLE JWAH
+ {0xC8A8, 0xC8A8, prLV}, // Lo HANGUL SYLLABLE JWAE
+ {0xC8A9, 0xC8C3, prLVT}, // Lo [27] HANGUL SYLLABLE JWAEG..HANGUL SYLLABLE JWAEH
+ {0xC8C4, 0xC8C4, prLV}, // Lo HANGUL SYLLABLE JOE
+ {0xC8C5, 0xC8DF, prLVT}, // Lo [27] HANGUL SYLLABLE JOEG..HANGUL SYLLABLE JOEH
+ {0xC8E0, 0xC8E0, prLV}, // Lo HANGUL SYLLABLE JYO
+ {0xC8E1, 0xC8FB, prLVT}, // Lo [27] HANGUL SYLLABLE JYOG..HANGUL SYLLABLE JYOH
+ {0xC8FC, 0xC8FC, prLV}, // Lo HANGUL SYLLABLE JU
+ {0xC8FD, 0xC917, prLVT}, // Lo [27] HANGUL SYLLABLE JUG..HANGUL SYLLABLE JUH
+ {0xC918, 0xC918, prLV}, // Lo HANGUL SYLLABLE JWEO
+ {0xC919, 0xC933, prLVT}, // Lo [27] HANGUL SYLLABLE JWEOG..HANGUL SYLLABLE JWEOH
+ {0xC934, 0xC934, prLV}, // Lo HANGUL SYLLABLE JWE
+ {0xC935, 0xC94F, prLVT}, // Lo [27] HANGUL SYLLABLE JWEG..HANGUL SYLLABLE JWEH
+ {0xC950, 0xC950, prLV}, // Lo HANGUL SYLLABLE JWI
+ {0xC951, 0xC96B, prLVT}, // Lo [27] HANGUL SYLLABLE JWIG..HANGUL SYLLABLE JWIH
+ {0xC96C, 0xC96C, prLV}, // Lo HANGUL SYLLABLE JYU
+ {0xC96D, 0xC987, prLVT}, // Lo [27] HANGUL SYLLABLE JYUG..HANGUL SYLLABLE JYUH
+ {0xC988, 0xC988, prLV}, // Lo HANGUL SYLLABLE JEU
+ {0xC989, 0xC9A3, prLVT}, // Lo [27] HANGUL SYLLABLE JEUG..HANGUL SYLLABLE JEUH
+ {0xC9A4, 0xC9A4, prLV}, // Lo HANGUL SYLLABLE JYI
+ {0xC9A5, 0xC9BF, prLVT}, // Lo [27] HANGUL SYLLABLE JYIG..HANGUL SYLLABLE JYIH
+ {0xC9C0, 0xC9C0, prLV}, // Lo HANGUL SYLLABLE JI
+ {0xC9C1, 0xC9DB, prLVT}, // Lo [27] HANGUL SYLLABLE JIG..HANGUL SYLLABLE JIH
+ {0xC9DC, 0xC9DC, prLV}, // Lo HANGUL SYLLABLE JJA
+ {0xC9DD, 0xC9F7, prLVT}, // Lo [27] HANGUL SYLLABLE JJAG..HANGUL SYLLABLE JJAH
+ {0xC9F8, 0xC9F8, prLV}, // Lo HANGUL SYLLABLE JJAE
+ {0xC9F9, 0xCA13, prLVT}, // Lo [27] HANGUL SYLLABLE JJAEG..HANGUL SYLLABLE JJAEH
+ {0xCA14, 0xCA14, prLV}, // Lo HANGUL SYLLABLE JJYA
+ {0xCA15, 0xCA2F, prLVT}, // Lo [27] HANGUL SYLLABLE JJYAG..HANGUL SYLLABLE JJYAH
+ {0xCA30, 0xCA30, prLV}, // Lo HANGUL SYLLABLE JJYAE
+ {0xCA31, 0xCA4B, prLVT}, // Lo [27] HANGUL SYLLABLE JJYAEG..HANGUL SYLLABLE JJYAEH
+ {0xCA4C, 0xCA4C, prLV}, // Lo HANGUL SYLLABLE JJEO
+ {0xCA4D, 0xCA67, prLVT}, // Lo [27] HANGUL SYLLABLE JJEOG..HANGUL SYLLABLE JJEOH
+ {0xCA68, 0xCA68, prLV}, // Lo HANGUL SYLLABLE JJE
+ {0xCA69, 0xCA83, prLVT}, // Lo [27] HANGUL SYLLABLE JJEG..HANGUL SYLLABLE JJEH
+ {0xCA84, 0xCA84, prLV}, // Lo HANGUL SYLLABLE JJYEO
+ {0xCA85, 0xCA9F, prLVT}, // Lo [27] HANGUL SYLLABLE JJYEOG..HANGUL SYLLABLE JJYEOH
+ {0xCAA0, 0xCAA0, prLV}, // Lo HANGUL SYLLABLE JJYE
+ {0xCAA1, 0xCABB, prLVT}, // Lo [27] HANGUL SYLLABLE JJYEG..HANGUL SYLLABLE JJYEH
+ {0xCABC, 0xCABC, prLV}, // Lo HANGUL SYLLABLE JJO
+ {0xCABD, 0xCAD7, prLVT}, // Lo [27] HANGUL SYLLABLE JJOG..HANGUL SYLLABLE JJOH
+ {0xCAD8, 0xCAD8, prLV}, // Lo HANGUL SYLLABLE JJWA
+ {0xCAD9, 0xCAF3, prLVT}, // Lo [27] HANGUL SYLLABLE JJWAG..HANGUL SYLLABLE JJWAH
+ {0xCAF4, 0xCAF4, prLV}, // Lo HANGUL SYLLABLE JJWAE
+ {0xCAF5, 0xCB0F, prLVT}, // Lo [27] HANGUL SYLLABLE JJWAEG..HANGUL SYLLABLE JJWAEH
+ {0xCB10, 0xCB10, prLV}, // Lo HANGUL SYLLABLE JJOE
+ {0xCB11, 0xCB2B, prLVT}, // Lo [27] HANGUL SYLLABLE JJOEG..HANGUL SYLLABLE JJOEH
+ {0xCB2C, 0xCB2C, prLV}, // Lo HANGUL SYLLABLE JJYO
+ {0xCB2D, 0xCB47, prLVT}, // Lo [27] HANGUL SYLLABLE JJYOG..HANGUL SYLLABLE JJYOH
+ {0xCB48, 0xCB48, prLV}, // Lo HANGUL SYLLABLE JJU
+ {0xCB49, 0xCB63, prLVT}, // Lo [27] HANGUL SYLLABLE JJUG..HANGUL SYLLABLE JJUH
+ {0xCB64, 0xCB64, prLV}, // Lo HANGUL SYLLABLE JJWEO
+ {0xCB65, 0xCB7F, prLVT}, // Lo [27] HANGUL SYLLABLE JJWEOG..HANGUL SYLLABLE JJWEOH
+ {0xCB80, 0xCB80, prLV}, // Lo HANGUL SYLLABLE JJWE
+ {0xCB81, 0xCB9B, prLVT}, // Lo [27] HANGUL SYLLABLE JJWEG..HANGUL SYLLABLE JJWEH
+ {0xCB9C, 0xCB9C, prLV}, // Lo HANGUL SYLLABLE JJWI
+ {0xCB9D, 0xCBB7, prLVT}, // Lo [27] HANGUL SYLLABLE JJWIG..HANGUL SYLLABLE JJWIH
+ {0xCBB8, 0xCBB8, prLV}, // Lo HANGUL SYLLABLE JJYU
+ {0xCBB9, 0xCBD3, prLVT}, // Lo [27] HANGUL SYLLABLE JJYUG..HANGUL SYLLABLE JJYUH
+ {0xCBD4, 0xCBD4, prLV}, // Lo HANGUL SYLLABLE JJEU
+ {0xCBD5, 0xCBEF, prLVT}, // Lo [27] HANGUL SYLLABLE JJEUG..HANGUL SYLLABLE JJEUH
+ {0xCBF0, 0xCBF0, prLV}, // Lo HANGUL SYLLABLE JJYI
+ {0xCBF1, 0xCC0B, prLVT}, // Lo [27] HANGUL SYLLABLE JJYIG..HANGUL SYLLABLE JJYIH
+ {0xCC0C, 0xCC0C, prLV}, // Lo HANGUL SYLLABLE JJI
+ {0xCC0D, 0xCC27, prLVT}, // Lo [27] HANGUL SYLLABLE JJIG..HANGUL SYLLABLE JJIH
+ {0xCC28, 0xCC28, prLV}, // Lo HANGUL SYLLABLE CA
+ {0xCC29, 0xCC43, prLVT}, // Lo [27] HANGUL SYLLABLE CAG..HANGUL SYLLABLE CAH
+ {0xCC44, 0xCC44, prLV}, // Lo HANGUL SYLLABLE CAE
+ {0xCC45, 0xCC5F, prLVT}, // Lo [27] HANGUL SYLLABLE CAEG..HANGUL SYLLABLE CAEH
+ {0xCC60, 0xCC60, prLV}, // Lo HANGUL SYLLABLE CYA
+ {0xCC61, 0xCC7B, prLVT}, // Lo [27] HANGUL SYLLABLE CYAG..HANGUL SYLLABLE CYAH
+ {0xCC7C, 0xCC7C, prLV}, // Lo HANGUL SYLLABLE CYAE
+ {0xCC7D, 0xCC97, prLVT}, // Lo [27] HANGUL SYLLABLE CYAEG..HANGUL SYLLABLE CYAEH
+ {0xCC98, 0xCC98, prLV}, // Lo HANGUL SYLLABLE CEO
+ {0xCC99, 0xCCB3, prLVT}, // Lo [27] HANGUL SYLLABLE CEOG..HANGUL SYLLABLE CEOH
+ {0xCCB4, 0xCCB4, prLV}, // Lo HANGUL SYLLABLE CE
+ {0xCCB5, 0xCCCF, prLVT}, // Lo [27] HANGUL SYLLABLE CEG..HANGUL SYLLABLE CEH
+ {0xCCD0, 0xCCD0, prLV}, // Lo HANGUL SYLLABLE CYEO
+ {0xCCD1, 0xCCEB, prLVT}, // Lo [27] HANGUL SYLLABLE CYEOG..HANGUL SYLLABLE CYEOH
+ {0xCCEC, 0xCCEC, prLV}, // Lo HANGUL SYLLABLE CYE
+ {0xCCED, 0xCD07, prLVT}, // Lo [27] HANGUL SYLLABLE CYEG..HANGUL SYLLABLE CYEH
+ {0xCD08, 0xCD08, prLV}, // Lo HANGUL SYLLABLE CO
+ {0xCD09, 0xCD23, prLVT}, // Lo [27] HANGUL SYLLABLE COG..HANGUL SYLLABLE COH
+ {0xCD24, 0xCD24, prLV}, // Lo HANGUL SYLLABLE CWA
+ {0xCD25, 0xCD3F, prLVT}, // Lo [27] HANGUL SYLLABLE CWAG..HANGUL SYLLABLE CWAH
+ {0xCD40, 0xCD40, prLV}, // Lo HANGUL SYLLABLE CWAE
+ {0xCD41, 0xCD5B, prLVT}, // Lo [27] HANGUL SYLLABLE CWAEG..HANGUL SYLLABLE CWAEH
+ {0xCD5C, 0xCD5C, prLV}, // Lo HANGUL SYLLABLE COE
+ {0xCD5D, 0xCD77, prLVT}, // Lo [27] HANGUL SYLLABLE COEG..HANGUL SYLLABLE COEH
+ {0xCD78, 0xCD78, prLV}, // Lo HANGUL SYLLABLE CYO
+ {0xCD79, 0xCD93, prLVT}, // Lo [27] HANGUL SYLLABLE CYOG..HANGUL SYLLABLE CYOH
+ {0xCD94, 0xCD94, prLV}, // Lo HANGUL SYLLABLE CU
+ {0xCD95, 0xCDAF, prLVT}, // Lo [27] HANGUL SYLLABLE CUG..HANGUL SYLLABLE CUH
+ {0xCDB0, 0xCDB0, prLV}, // Lo HANGUL SYLLABLE CWEO
+ {0xCDB1, 0xCDCB, prLVT}, // Lo [27] HANGUL SYLLABLE CWEOG..HANGUL SYLLABLE CWEOH
+ {0xCDCC, 0xCDCC, prLV}, // Lo HANGUL SYLLABLE CWE
+ {0xCDCD, 0xCDE7, prLVT}, // Lo [27] HANGUL SYLLABLE CWEG..HANGUL SYLLABLE CWEH
+ {0xCDE8, 0xCDE8, prLV}, // Lo HANGUL SYLLABLE CWI
+ {0xCDE9, 0xCE03, prLVT}, // Lo [27] HANGUL SYLLABLE CWIG..HANGUL SYLLABLE CWIH
+ {0xCE04, 0xCE04, prLV}, // Lo HANGUL SYLLABLE CYU
+ {0xCE05, 0xCE1F, prLVT}, // Lo [27] HANGUL SYLLABLE CYUG..HANGUL SYLLABLE CYUH
+ {0xCE20, 0xCE20, prLV}, // Lo HANGUL SYLLABLE CEU
+ {0xCE21, 0xCE3B, prLVT}, // Lo [27] HANGUL SYLLABLE CEUG..HANGUL SYLLABLE CEUH
+ {0xCE3C, 0xCE3C, prLV}, // Lo HANGUL SYLLABLE CYI
+ {0xCE3D, 0xCE57, prLVT}, // Lo [27] HANGUL SYLLABLE CYIG..HANGUL SYLLABLE CYIH
+ {0xCE58, 0xCE58, prLV}, // Lo HANGUL SYLLABLE CI
+ {0xCE59, 0xCE73, prLVT}, // Lo [27] HANGUL SYLLABLE CIG..HANGUL SYLLABLE CIH
+ {0xCE74, 0xCE74, prLV}, // Lo HANGUL SYLLABLE KA
+ {0xCE75, 0xCE8F, prLVT}, // Lo [27] HANGUL SYLLABLE KAG..HANGUL SYLLABLE KAH
+ {0xCE90, 0xCE90, prLV}, // Lo HANGUL SYLLABLE KAE
+ {0xCE91, 0xCEAB, prLVT}, // Lo [27] HANGUL SYLLABLE KAEG..HANGUL SYLLABLE KAEH
+ {0xCEAC, 0xCEAC, prLV}, // Lo HANGUL SYLLABLE KYA
+ {0xCEAD, 0xCEC7, prLVT}, // Lo [27] HANGUL SYLLABLE KYAG..HANGUL SYLLABLE KYAH
+ {0xCEC8, 0xCEC8, prLV}, // Lo HANGUL SYLLABLE KYAE
+ {0xCEC9, 0xCEE3, prLVT}, // Lo [27] HANGUL SYLLABLE KYAEG..HANGUL SYLLABLE KYAEH
+ {0xCEE4, 0xCEE4, prLV}, // Lo HANGUL SYLLABLE KEO
+ {0xCEE5, 0xCEFF, prLVT}, // Lo [27] HANGUL SYLLABLE KEOG..HANGUL SYLLABLE KEOH
+ {0xCF00, 0xCF00, prLV}, // Lo HANGUL SYLLABLE KE
+ {0xCF01, 0xCF1B, prLVT}, // Lo [27] HANGUL SYLLABLE KEG..HANGUL SYLLABLE KEH
+ {0xCF1C, 0xCF1C, prLV}, // Lo HANGUL SYLLABLE KYEO
+ {0xCF1D, 0xCF37, prLVT}, // Lo [27] HANGUL SYLLABLE KYEOG..HANGUL SYLLABLE KYEOH
+ {0xCF38, 0xCF38, prLV}, // Lo HANGUL SYLLABLE KYE
+ {0xCF39, 0xCF53, prLVT}, // Lo [27] HANGUL SYLLABLE KYEG..HANGUL SYLLABLE KYEH
+ {0xCF54, 0xCF54, prLV}, // Lo HANGUL SYLLABLE KO
+ {0xCF55, 0xCF6F, prLVT}, // Lo [27] HANGUL SYLLABLE KOG..HANGUL SYLLABLE KOH
+ {0xCF70, 0xCF70, prLV}, // Lo HANGUL SYLLABLE KWA
+ {0xCF71, 0xCF8B, prLVT}, // Lo [27] HANGUL SYLLABLE KWAG..HANGUL SYLLABLE KWAH
+ {0xCF8C, 0xCF8C, prLV}, // Lo HANGUL SYLLABLE KWAE
+ {0xCF8D, 0xCFA7, prLVT}, // Lo [27] HANGUL SYLLABLE KWAEG..HANGUL SYLLABLE KWAEH
+ {0xCFA8, 0xCFA8, prLV}, // Lo HANGUL SYLLABLE KOE
+ {0xCFA9, 0xCFC3, prLVT}, // Lo [27] HANGUL SYLLABLE KOEG..HANGUL SYLLABLE KOEH
+ {0xCFC4, 0xCFC4, prLV}, // Lo HANGUL SYLLABLE KYO
+ {0xCFC5, 0xCFDF, prLVT}, // Lo [27] HANGUL SYLLABLE KYOG..HANGUL SYLLABLE KYOH
+ {0xCFE0, 0xCFE0, prLV}, // Lo HANGUL SYLLABLE KU
+ {0xCFE1, 0xCFFB, prLVT}, // Lo [27] HANGUL SYLLABLE KUG..HANGUL SYLLABLE KUH
+ {0xCFFC, 0xCFFC, prLV}, // Lo HANGUL SYLLABLE KWEO
+ {0xCFFD, 0xD017, prLVT}, // Lo [27] HANGUL SYLLABLE KWEOG..HANGUL SYLLABLE KWEOH
+ {0xD018, 0xD018, prLV}, // Lo HANGUL SYLLABLE KWE
+ {0xD019, 0xD033, prLVT}, // Lo [27] HANGUL SYLLABLE KWEG..HANGUL SYLLABLE KWEH
+ {0xD034, 0xD034, prLV}, // Lo HANGUL SYLLABLE KWI
+ {0xD035, 0xD04F, prLVT}, // Lo [27] HANGUL SYLLABLE KWIG..HANGUL SYLLABLE KWIH
+ {0xD050, 0xD050, prLV}, // Lo HANGUL SYLLABLE KYU
+ {0xD051, 0xD06B, prLVT}, // Lo [27] HANGUL SYLLABLE KYUG..HANGUL SYLLABLE KYUH
+ {0xD06C, 0xD06C, prLV}, // Lo HANGUL SYLLABLE KEU
+ {0xD06D, 0xD087, prLVT}, // Lo [27] HANGUL SYLLABLE KEUG..HANGUL SYLLABLE KEUH
+ {0xD088, 0xD088, prLV}, // Lo HANGUL SYLLABLE KYI
+ {0xD089, 0xD0A3, prLVT}, // Lo [27] HANGUL SYLLABLE KYIG..HANGUL SYLLABLE KYIH
+ {0xD0A4, 0xD0A4, prLV}, // Lo HANGUL SYLLABLE KI
+ {0xD0A5, 0xD0BF, prLVT}, // Lo [27] HANGUL SYLLABLE KIG..HANGUL SYLLABLE KIH
+ {0xD0C0, 0xD0C0, prLV}, // Lo HANGUL SYLLABLE TA
+ {0xD0C1, 0xD0DB, prLVT}, // Lo [27] HANGUL SYLLABLE TAG..HANGUL SYLLABLE TAH
+ {0xD0DC, 0xD0DC, prLV}, // Lo HANGUL SYLLABLE TAE
+ {0xD0DD, 0xD0F7, prLVT}, // Lo [27] HANGUL SYLLABLE TAEG..HANGUL SYLLABLE TAEH
+ {0xD0F8, 0xD0F8, prLV}, // Lo HANGUL SYLLABLE TYA
+ {0xD0F9, 0xD113, prLVT}, // Lo [27] HANGUL SYLLABLE TYAG..HANGUL SYLLABLE TYAH
+ {0xD114, 0xD114, prLV}, // Lo HANGUL SYLLABLE TYAE
+ {0xD115, 0xD12F, prLVT}, // Lo [27] HANGUL SYLLABLE TYAEG..HANGUL SYLLABLE TYAEH
+ {0xD130, 0xD130, prLV}, // Lo HANGUL SYLLABLE TEO
+ {0xD131, 0xD14B, prLVT}, // Lo [27] HANGUL SYLLABLE TEOG..HANGUL SYLLABLE TEOH
+ {0xD14C, 0xD14C, prLV}, // Lo HANGUL SYLLABLE TE
+ {0xD14D, 0xD167, prLVT}, // Lo [27] HANGUL SYLLABLE TEG..HANGUL SYLLABLE TEH
+ {0xD168, 0xD168, prLV}, // Lo HANGUL SYLLABLE TYEO
+ {0xD169, 0xD183, prLVT}, // Lo [27] HANGUL SYLLABLE TYEOG..HANGUL SYLLABLE TYEOH
+ {0xD184, 0xD184, prLV}, // Lo HANGUL SYLLABLE TYE
+ {0xD185, 0xD19F, prLVT}, // Lo [27] HANGUL SYLLABLE TYEG..HANGUL SYLLABLE TYEH
+ {0xD1A0, 0xD1A0, prLV}, // Lo HANGUL SYLLABLE TO
+ {0xD1A1, 0xD1BB, prLVT}, // Lo [27] HANGUL SYLLABLE TOG..HANGUL SYLLABLE TOH
+ {0xD1BC, 0xD1BC, prLV}, // Lo HANGUL SYLLABLE TWA
+ {0xD1BD, 0xD1D7, prLVT}, // Lo [27] HANGUL SYLLABLE TWAG..HANGUL SYLLABLE TWAH
+ {0xD1D8, 0xD1D8, prLV}, // Lo HANGUL SYLLABLE TWAE
+ {0xD1D9, 0xD1F3, prLVT}, // Lo [27] HANGUL SYLLABLE TWAEG..HANGUL SYLLABLE TWAEH
+ {0xD1F4, 0xD1F4, prLV}, // Lo HANGUL SYLLABLE TOE
+ {0xD1F5, 0xD20F, prLVT}, // Lo [27] HANGUL SYLLABLE TOEG..HANGUL SYLLABLE TOEH
+ {0xD210, 0xD210, prLV}, // Lo HANGUL SYLLABLE TYO
+ {0xD211, 0xD22B, prLVT}, // Lo [27] HANGUL SYLLABLE TYOG..HANGUL SYLLABLE TYOH
+ {0xD22C, 0xD22C, prLV}, // Lo HANGUL SYLLABLE TU
+ {0xD22D, 0xD247, prLVT}, // Lo [27] HANGUL SYLLABLE TUG..HANGUL SYLLABLE TUH
+ {0xD248, 0xD248, prLV}, // Lo HANGUL SYLLABLE TWEO
+ {0xD249, 0xD263, prLVT}, // Lo [27] HANGUL SYLLABLE TWEOG..HANGUL SYLLABLE TWEOH
+ {0xD264, 0xD264, prLV}, // Lo HANGUL SYLLABLE TWE
+ {0xD265, 0xD27F, prLVT}, // Lo [27] HANGUL SYLLABLE TWEG..HANGUL SYLLABLE TWEH
+ {0xD280, 0xD280, prLV}, // Lo HANGUL SYLLABLE TWI
+ {0xD281, 0xD29B, prLVT}, // Lo [27] HANGUL SYLLABLE TWIG..HANGUL SYLLABLE TWIH
+ {0xD29C, 0xD29C, prLV}, // Lo HANGUL SYLLABLE TYU
+ {0xD29D, 0xD2B7, prLVT}, // Lo [27] HANGUL SYLLABLE TYUG..HANGUL SYLLABLE TYUH
+ {0xD2B8, 0xD2B8, prLV}, // Lo HANGUL SYLLABLE TEU
+ {0xD2B9, 0xD2D3, prLVT}, // Lo [27] HANGUL SYLLABLE TEUG..HANGUL SYLLABLE TEUH
+ {0xD2D4, 0xD2D4, prLV}, // Lo HANGUL SYLLABLE TYI
+ {0xD2D5, 0xD2EF, prLVT}, // Lo [27] HANGUL SYLLABLE TYIG..HANGUL SYLLABLE TYIH
+ {0xD2F0, 0xD2F0, prLV}, // Lo HANGUL SYLLABLE TI
+ {0xD2F1, 0xD30B, prLVT}, // Lo [27] HANGUL SYLLABLE TIG..HANGUL SYLLABLE TIH
+ {0xD30C, 0xD30C, prLV}, // Lo HANGUL SYLLABLE PA
+ {0xD30D, 0xD327, prLVT}, // Lo [27] HANGUL SYLLABLE PAG..HANGUL SYLLABLE PAH
+ {0xD328, 0xD328, prLV}, // Lo HANGUL SYLLABLE PAE
+ {0xD329, 0xD343, prLVT}, // Lo [27] HANGUL SYLLABLE PAEG..HANGUL SYLLABLE PAEH
+ {0xD344, 0xD344, prLV}, // Lo HANGUL SYLLABLE PYA
+ {0xD345, 0xD35F, prLVT}, // Lo [27] HANGUL SYLLABLE PYAG..HANGUL SYLLABLE PYAH
+ {0xD360, 0xD360, prLV}, // Lo HANGUL SYLLABLE PYAE
+ {0xD361, 0xD37B, prLVT}, // Lo [27] HANGUL SYLLABLE PYAEG..HANGUL SYLLABLE PYAEH
+ {0xD37C, 0xD37C, prLV}, // Lo HANGUL SYLLABLE PEO
+ {0xD37D, 0xD397, prLVT}, // Lo [27] HANGUL SYLLABLE PEOG..HANGUL SYLLABLE PEOH
+ {0xD398, 0xD398, prLV}, // Lo HANGUL SYLLABLE PE
+ {0xD399, 0xD3B3, prLVT}, // Lo [27] HANGUL SYLLABLE PEG..HANGUL SYLLABLE PEH
+ {0xD3B4, 0xD3B4, prLV}, // Lo HANGUL SYLLABLE PYEO
+ {0xD3B5, 0xD3CF, prLVT}, // Lo [27] HANGUL SYLLABLE PYEOG..HANGUL SYLLABLE PYEOH
+ {0xD3D0, 0xD3D0, prLV}, // Lo HANGUL SYLLABLE PYE
+ {0xD3D1, 0xD3EB, prLVT}, // Lo [27] HANGUL SYLLABLE PYEG..HANGUL SYLLABLE PYEH
+ {0xD3EC, 0xD3EC, prLV}, // Lo HANGUL SYLLABLE PO
+ {0xD3ED, 0xD407, prLVT}, // Lo [27] HANGUL SYLLABLE POG..HANGUL SYLLABLE POH
+ {0xD408, 0xD408, prLV}, // Lo HANGUL SYLLABLE PWA
+ {0xD409, 0xD423, prLVT}, // Lo [27] HANGUL SYLLABLE PWAG..HANGUL SYLLABLE PWAH
+ {0xD424, 0xD424, prLV}, // Lo HANGUL SYLLABLE PWAE
+ {0xD425, 0xD43F, prLVT}, // Lo [27] HANGUL SYLLABLE PWAEG..HANGUL SYLLABLE PWAEH
+ {0xD440, 0xD440, prLV}, // Lo HANGUL SYLLABLE POE
+ {0xD441, 0xD45B, prLVT}, // Lo [27] HANGUL SYLLABLE POEG..HANGUL SYLLABLE POEH
+ {0xD45C, 0xD45C, prLV}, // Lo HANGUL SYLLABLE PYO
+ {0xD45D, 0xD477, prLVT}, // Lo [27] HANGUL SYLLABLE PYOG..HANGUL SYLLABLE PYOH
+ {0xD478, 0xD478, prLV}, // Lo HANGUL SYLLABLE PU
+ {0xD479, 0xD493, prLVT}, // Lo [27] HANGUL SYLLABLE PUG..HANGUL SYLLABLE PUH
+ {0xD494, 0xD494, prLV}, // Lo HANGUL SYLLABLE PWEO
+ {0xD495, 0xD4AF, prLVT}, // Lo [27] HANGUL SYLLABLE PWEOG..HANGUL SYLLABLE PWEOH
+ {0xD4B0, 0xD4B0, prLV}, // Lo HANGUL SYLLABLE PWE
+ {0xD4B1, 0xD4CB, prLVT}, // Lo [27] HANGUL SYLLABLE PWEG..HANGUL SYLLABLE PWEH
+ {0xD4CC, 0xD4CC, prLV}, // Lo HANGUL SYLLABLE PWI
+ {0xD4CD, 0xD4E7, prLVT}, // Lo [27] HANGUL SYLLABLE PWIG..HANGUL SYLLABLE PWIH
+ {0xD4E8, 0xD4E8, prLV}, // Lo HANGUL SYLLABLE PYU
+ {0xD4E9, 0xD503, prLVT}, // Lo [27] HANGUL SYLLABLE PYUG..HANGUL SYLLABLE PYUH
+ {0xD504, 0xD504, prLV}, // Lo HANGUL SYLLABLE PEU
+ {0xD505, 0xD51F, prLVT}, // Lo [27] HANGUL SYLLABLE PEUG..HANGUL SYLLABLE PEUH
+ {0xD520, 0xD520, prLV}, // Lo HANGUL SYLLABLE PYI
+ {0xD521, 0xD53B, prLVT}, // Lo [27] HANGUL SYLLABLE PYIG..HANGUL SYLLABLE PYIH
+ {0xD53C, 0xD53C, prLV}, // Lo HANGUL SYLLABLE PI
+ {0xD53D, 0xD557, prLVT}, // Lo [27] HANGUL SYLLABLE PIG..HANGUL SYLLABLE PIH
+ {0xD558, 0xD558, prLV}, // Lo HANGUL SYLLABLE HA
+ {0xD559, 0xD573, prLVT}, // Lo [27] HANGUL SYLLABLE HAG..HANGUL SYLLABLE HAH
+ {0xD574, 0xD574, prLV}, // Lo HANGUL SYLLABLE HAE
+ {0xD575, 0xD58F, prLVT}, // Lo [27] HANGUL SYLLABLE HAEG..HANGUL SYLLABLE HAEH
+ {0xD590, 0xD590, prLV}, // Lo HANGUL SYLLABLE HYA
+ {0xD591, 0xD5AB, prLVT}, // Lo [27] HANGUL SYLLABLE HYAG..HANGUL SYLLABLE HYAH
+ {0xD5AC, 0xD5AC, prLV}, // Lo HANGUL SYLLABLE HYAE
+ {0xD5AD, 0xD5C7, prLVT}, // Lo [27] HANGUL SYLLABLE HYAEG..HANGUL SYLLABLE HYAEH
+ {0xD5C8, 0xD5C8, prLV}, // Lo HANGUL SYLLABLE HEO
+ {0xD5C9, 0xD5E3, prLVT}, // Lo [27] HANGUL SYLLABLE HEOG..HANGUL SYLLABLE HEOH
+ {0xD5E4, 0xD5E4, prLV}, // Lo HANGUL SYLLABLE HE
+ {0xD5E5, 0xD5FF, prLVT}, // Lo [27] HANGUL SYLLABLE HEG..HANGUL SYLLABLE HEH
+ {0xD600, 0xD600, prLV}, // Lo HANGUL SYLLABLE HYEO
+ {0xD601, 0xD61B, prLVT}, // Lo [27] HANGUL SYLLABLE HYEOG..HANGUL SYLLABLE HYEOH
+ {0xD61C, 0xD61C, prLV}, // Lo HANGUL SYLLABLE HYE
+ {0xD61D, 0xD637, prLVT}, // Lo [27] HANGUL SYLLABLE HYEG..HANGUL SYLLABLE HYEH
+ {0xD638, 0xD638, prLV}, // Lo HANGUL SYLLABLE HO
+ {0xD639, 0xD653, prLVT}, // Lo [27] HANGUL SYLLABLE HOG..HANGUL SYLLABLE HOH
+ {0xD654, 0xD654, prLV}, // Lo HANGUL SYLLABLE HWA
+ {0xD655, 0xD66F, prLVT}, // Lo [27] HANGUL SYLLABLE HWAG..HANGUL SYLLABLE HWAH
+ {0xD670, 0xD670, prLV}, // Lo HANGUL SYLLABLE HWAE
+ {0xD671, 0xD68B, prLVT}, // Lo [27] HANGUL SYLLABLE HWAEG..HANGUL SYLLABLE HWAEH
+ {0xD68C, 0xD68C, prLV}, // Lo HANGUL SYLLABLE HOE
+ {0xD68D, 0xD6A7, prLVT}, // Lo [27] HANGUL SYLLABLE HOEG..HANGUL SYLLABLE HOEH
+ {0xD6A8, 0xD6A8, prLV}, // Lo HANGUL SYLLABLE HYO
+ {0xD6A9, 0xD6C3, prLVT}, // Lo [27] HANGUL SYLLABLE HYOG..HANGUL SYLLABLE HYOH
+ {0xD6C4, 0xD6C4, prLV}, // Lo HANGUL SYLLABLE HU
+ {0xD6C5, 0xD6DF, prLVT}, // Lo [27] HANGUL SYLLABLE HUG..HANGUL SYLLABLE HUH
+ {0xD6E0, 0xD6E0, prLV}, // Lo HANGUL SYLLABLE HWEO
+ {0xD6E1, 0xD6FB, prLVT}, // Lo [27] HANGUL SYLLABLE HWEOG..HANGUL SYLLABLE HWEOH
+ {0xD6FC, 0xD6FC, prLV}, // Lo HANGUL SYLLABLE HWE
+ {0xD6FD, 0xD717, prLVT}, // Lo [27] HANGUL SYLLABLE HWEG..HANGUL SYLLABLE HWEH
+ {0xD718, 0xD718, prLV}, // Lo HANGUL SYLLABLE HWI
+ {0xD719, 0xD733, prLVT}, // Lo [27] HANGUL SYLLABLE HWIG..HANGUL SYLLABLE HWIH
+ {0xD734, 0xD734, prLV}, // Lo HANGUL SYLLABLE HYU
+ {0xD735, 0xD74F, prLVT}, // Lo [27] HANGUL SYLLABLE HYUG..HANGUL SYLLABLE HYUH
+ {0xD750, 0xD750, prLV}, // Lo HANGUL SYLLABLE HEU
+ {0xD751, 0xD76B, prLVT}, // Lo [27] HANGUL SYLLABLE HEUG..HANGUL SYLLABLE HEUH
+ {0xD76C, 0xD76C, prLV}, // Lo HANGUL SYLLABLE HYI
+ {0xD76D, 0xD787, prLVT}, // Lo [27] HANGUL SYLLABLE HYIG..HANGUL SYLLABLE HYIH
+ {0xD788, 0xD788, prLV}, // Lo HANGUL SYLLABLE HI
+ {0xD789, 0xD7A3, prLVT}, // Lo [27] HANGUL SYLLABLE HIG..HANGUL SYLLABLE HIH
+ {0xD7B0, 0xD7C6, prV}, // Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E
+ {0xD7CB, 0xD7FB, prT}, // Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH
+ {0xFB1E, 0xFB1E, prExtend}, // Mn HEBREW POINT JUDEO-SPANISH VARIKA
+ {0xFE00, 0xFE0F, prExtend}, // Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
+ {0xFE20, 0xFE2F, prExtend}, // Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF
+ {0xFEFF, 0xFEFF, prControl}, // Cf ZERO WIDTH NO-BREAK SPACE
+ {0xFF9E, 0xFF9F, prExtend}, // Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK
+ {0xFFF0, 0xFFF8, prControl}, // Cn [9] <reserved-FFF0>..<reserved-FFF8>
+ {0xFFF9, 0xFFFB, prControl}, // Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR
+ {0x101FD, 0x101FD, prExtend}, // Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE
+ {0x102E0, 0x102E0, prExtend}, // Mn COPTIC EPACT THOUSANDS MARK
+ {0x10376, 0x1037A, prExtend}, // Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII
+ {0x10A01, 0x10A03, prExtend}, // Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R
+ {0x10A05, 0x10A06, prExtend}, // Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O
+ {0x10A0C, 0x10A0F, prExtend}, // Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA
+ {0x10A38, 0x10A3A, prExtend}, // Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW
+ {0x10A3F, 0x10A3F, prExtend}, // Mn KHAROSHTHI VIRAMA
+ {0x10AE5, 0x10AE6, prExtend}, // Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW
+ {0x10D24, 0x10D27, prExtend}, // Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI
+ {0x10F46, 0x10F50, prExtend}, // Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW
+ {0x11000, 0x11000, prSpacingMark}, // Mc BRAHMI SIGN CANDRABINDU
+ {0x11001, 0x11001, prExtend}, // Mn BRAHMI SIGN ANUSVARA
+ {0x11002, 0x11002, prSpacingMark}, // Mc BRAHMI SIGN VISARGA
+ {0x11038, 0x11046, prExtend}, // Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA
+ {0x1107F, 0x11081, prExtend}, // Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA
+ {0x11082, 0x11082, prSpacingMark}, // Mc KAITHI SIGN VISARGA
+ {0x110B0, 0x110B2, prSpacingMark}, // Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II
+ {0x110B3, 0x110B6, prExtend}, // Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI
+ {0x110B7, 0x110B8, prSpacingMark}, // Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU
+ {0x110B9, 0x110BA, prExtend}, // Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA
+ {0x110BD, 0x110BD, prPreprend}, // Cf KAITHI NUMBER SIGN
+ {0x110CD, 0x110CD, prPreprend}, // Cf KAITHI NUMBER SIGN ABOVE
+ {0x11100, 0x11102, prExtend}, // Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA
+ {0x11127, 0x1112B, prExtend}, // Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU
+ {0x1112C, 0x1112C, prSpacingMark}, // Mc CHAKMA VOWEL SIGN E
+ {0x1112D, 0x11134, prExtend}, // Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA
+ {0x11145, 0x11146, prSpacingMark}, // Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI
+ {0x11173, 0x11173, prExtend}, // Mn MAHAJANI SIGN NUKTA
+ {0x11180, 0x11181, prExtend}, // Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA
+ {0x11182, 0x11182, prSpacingMark}, // Mc SHARADA SIGN VISARGA
+ {0x111B3, 0x111B5, prSpacingMark}, // Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II
+ {0x111B6, 0x111BE, prExtend}, // Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O
+ {0x111BF, 0x111C0, prSpacingMark}, // Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA
+ {0x111C2, 0x111C3, prPreprend}, // Lo [2] SHARADA SIGN JIHVAMULIYA..SHARADA SIGN UPADHMANIYA
+ {0x111C9, 0x111CC, prExtend}, // Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK
+ {0x1122C, 0x1122E, prSpacingMark}, // Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II
+ {0x1122F, 0x11231, prExtend}, // Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI
+ {0x11232, 0x11233, prSpacingMark}, // Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU
+ {0x11234, 0x11234, prExtend}, // Mn KHOJKI SIGN ANUSVARA
+ {0x11235, 0x11235, prSpacingMark}, // Mc KHOJKI SIGN VIRAMA
+ {0x11236, 0x11237, prExtend}, // Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA
+ {0x1123E, 0x1123E, prExtend}, // Mn KHOJKI SIGN SUKUN
+ {0x112DF, 0x112DF, prExtend}, // Mn KHUDAWADI SIGN ANUSVARA
+ {0x112E0, 0x112E2, prSpacingMark}, // Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II
+ {0x112E3, 0x112EA, prExtend}, // Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA
+ {0x11300, 0x11301, prExtend}, // Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU
+ {0x11302, 0x11303, prSpacingMark}, // Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA
+ {0x1133B, 0x1133C, prExtend}, // Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA
+ {0x1133E, 0x1133E, prExtend}, // Mc GRANTHA VOWEL SIGN AA
+ {0x1133F, 0x1133F, prSpacingMark}, // Mc GRANTHA VOWEL SIGN I
+ {0x11340, 0x11340, prExtend}, // Mn GRANTHA VOWEL SIGN II
+ {0x11341, 0x11344, prSpacingMark}, // Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR
+ {0x11347, 0x11348, prSpacingMark}, // Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI
+ {0x1134B, 0x1134D, prSpacingMark}, // Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA
+ {0x11357, 0x11357, prExtend}, // Mc GRANTHA AU LENGTH MARK
+ {0x11362, 0x11363, prSpacingMark}, // Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL
+ {0x11366, 0x1136C, prExtend}, // Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX
+ {0x11370, 0x11374, prExtend}, // Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA
+ {0x11435, 0x11437, prSpacingMark}, // Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II
+ {0x11438, 0x1143F, prExtend}, // Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI
+ {0x11440, 0x11441, prSpacingMark}, // Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU
+ {0x11442, 0x11444, prExtend}, // Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA
+ {0x11445, 0x11445, prSpacingMark}, // Mc NEWA SIGN VISARGA
+ {0x11446, 0x11446, prExtend}, // Mn NEWA SIGN NUKTA
+ {0x1145E, 0x1145E, prExtend}, // Mn NEWA SANDHI MARK
+ {0x114B0, 0x114B0, prExtend}, // Mc TIRHUTA VOWEL SIGN AA
+ {0x114B1, 0x114B2, prSpacingMark}, // Mc [2] TIRHUTA VOWEL SIGN I..TIRHUTA VOWEL SIGN II
+ {0x114B3, 0x114B8, prExtend}, // Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL
+ {0x114B9, 0x114B9, prSpacingMark}, // Mc TIRHUTA VOWEL SIGN E
+ {0x114BA, 0x114BA, prExtend}, // Mn TIRHUTA VOWEL SIGN SHORT E
+ {0x114BB, 0x114BC, prSpacingMark}, // Mc [2] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN O
+ {0x114BD, 0x114BD, prExtend}, // Mc TIRHUTA VOWEL SIGN SHORT O
+ {0x114BE, 0x114BE, prSpacingMark}, // Mc TIRHUTA VOWEL SIGN AU
+ {0x114BF, 0x114C0, prExtend}, // Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA
+ {0x114C1, 0x114C1, prSpacingMark}, // Mc TIRHUTA SIGN VISARGA
+ {0x114C2, 0x114C3, prExtend}, // Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA
+ {0x115AF, 0x115AF, prExtend}, // Mc SIDDHAM VOWEL SIGN AA
+ {0x115B0, 0x115B1, prSpacingMark}, // Mc [2] SIDDHAM VOWEL SIGN I..SIDDHAM VOWEL SIGN II
+ {0x115B2, 0x115B5, prExtend}, // Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR
+ {0x115B8, 0x115BB, prSpacingMark}, // Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU
+ {0x115BC, 0x115BD, prExtend}, // Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA
+ {0x115BE, 0x115BE, prSpacingMark}, // Mc SIDDHAM SIGN VISARGA
+ {0x115BF, 0x115C0, prExtend}, // Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA
+ {0x115DC, 0x115DD, prExtend}, // Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU
+ {0x11630, 0x11632, prSpacingMark}, // Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II
+ {0x11633, 0x1163A, prExtend}, // Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI
+ {0x1163B, 0x1163C, prSpacingMark}, // Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU
+ {0x1163D, 0x1163D, prExtend}, // Mn MODI SIGN ANUSVARA
+ {0x1163E, 0x1163E, prSpacingMark}, // Mc MODI SIGN VISARGA
+ {0x1163F, 0x11640, prExtend}, // Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA
+ {0x116AB, 0x116AB, prExtend}, // Mn TAKRI SIGN ANUSVARA
+ {0x116AC, 0x116AC, prSpacingMark}, // Mc TAKRI SIGN VISARGA
+ {0x116AD, 0x116AD, prExtend}, // Mn TAKRI VOWEL SIGN AA
+ {0x116AE, 0x116AF, prSpacingMark}, // Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II
+ {0x116B0, 0x116B5, prExtend}, // Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU
+ {0x116B6, 0x116B6, prSpacingMark}, // Mc TAKRI SIGN VIRAMA
+ {0x116B7, 0x116B7, prExtend}, // Mn TAKRI SIGN NUKTA
+ {0x1171D, 0x1171F, prExtend}, // Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA
+ {0x11720, 0x11721, prSpacingMark}, // Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA
+ {0x11722, 0x11725, prExtend}, // Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU
+ {0x11726, 0x11726, prSpacingMark}, // Mc AHOM VOWEL SIGN E
+ {0x11727, 0x1172B, prExtend}, // Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER
+ {0x1182C, 0x1182E, prSpacingMark}, // Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II
+ {0x1182F, 0x11837, prExtend}, // Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA
+ {0x11838, 0x11838, prSpacingMark}, // Mc DOGRA SIGN VISARGA
+ {0x11839, 0x1183A, prExtend}, // Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA
+ {0x119D1, 0x119D3, prSpacingMark}, // Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II
+ {0x119D4, 0x119D7, prExtend}, // Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR
+ {0x119DA, 0x119DB, prExtend}, // Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI
+ {0x119DC, 0x119DF, prSpacingMark}, // Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA
+ {0x119E0, 0x119E0, prExtend}, // Mn NANDINAGARI SIGN VIRAMA
+ {0x119E4, 0x119E4, prSpacingMark}, // Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E
+ {0x11A01, 0x11A0A, prExtend}, // Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK
+ {0x11A33, 0x11A38, prExtend}, // Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA
+ {0x11A39, 0x11A39, prSpacingMark}, // Mc ZANABAZAR SQUARE SIGN VISARGA
+ {0x11A3A, 0x11A3A, prPreprend}, // Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA
+ {0x11A3B, 0x11A3E, prExtend}, // Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA
+ {0x11A47, 0x11A47, prExtend}, // Mn ZANABAZAR SQUARE SUBJOINER
+ {0x11A51, 0x11A56, prExtend}, // Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE
+ {0x11A57, 0x11A58, prSpacingMark}, // Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU
+ {0x11A59, 0x11A5B, prExtend}, // Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK
+ {0x11A84, 0x11A89, prPreprend}, // Lo [6] SOYOMBO SIGN JIHVAMULIYA..SOYOMBO CLUSTER-INITIAL LETTER SA
+ {0x11A8A, 0x11A96, prExtend}, // Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA
+ {0x11A97, 0x11A97, prSpacingMark}, // Mc SOYOMBO SIGN VISARGA
+ {0x11A98, 0x11A99, prExtend}, // Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER
+ {0x11C2F, 0x11C2F, prSpacingMark}, // Mc BHAIKSUKI VOWEL SIGN AA
+ {0x11C30, 0x11C36, prExtend}, // Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L
+ {0x11C38, 0x11C3D, prExtend}, // Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA
+ {0x11C3E, 0x11C3E, prSpacingMark}, // Mc BHAIKSUKI SIGN VISARGA
+ {0x11C3F, 0x11C3F, prExtend}, // Mn BHAIKSUKI SIGN VIRAMA
+ {0x11C92, 0x11CA7, prExtend}, // Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA
+ {0x11CA9, 0x11CA9, prSpacingMark}, // Mc MARCHEN SUBJOINED LETTER YA
+ {0x11CAA, 0x11CB0, prExtend}, // Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA
+ {0x11CB1, 0x11CB1, prSpacingMark}, // Mc MARCHEN VOWEL SIGN I
+ {0x11CB2, 0x11CB3, prExtend}, // Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E
+ {0x11CB4, 0x11CB4, prSpacingMark}, // Mc MARCHEN VOWEL SIGN O
+ {0x11CB5, 0x11CB6, prExtend}, // Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU
+ {0x11D31, 0x11D36, prExtend}, // Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R
+ {0x11D3A, 0x11D3A, prExtend}, // Mn MASARAM GONDI VOWEL SIGN E
+ {0x11D3C, 0x11D3D, prExtend}, // Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O
+ {0x11D3F, 0x11D45, prExtend}, // Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA
+ {0x11D46, 0x11D46, prPreprend}, // Lo MASARAM GONDI REPHA
+ {0x11D47, 0x11D47, prExtend}, // Mn MASARAM GONDI RA-KARA
+ {0x11D8A, 0x11D8E, prSpacingMark}, // Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU
+ {0x11D90, 0x11D91, prExtend}, // Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI
+ {0x11D93, 0x11D94, prSpacingMark}, // Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU
+ {0x11D95, 0x11D95, prExtend}, // Mn GUNJALA GONDI SIGN ANUSVARA
+ {0x11D96, 0x11D96, prSpacingMark}, // Mc GUNJALA GONDI SIGN VISARGA
+ {0x11D97, 0x11D97, prExtend}, // Mn GUNJALA GONDI VIRAMA
+ {0x11EF3, 0x11EF4, prExtend}, // Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U
+ {0x11EF5, 0x11EF6, prSpacingMark}, // Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O
+ {0x13430, 0x13438, prControl}, // Cf [9] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END SEGMENT
+ {0x16AF0, 0x16AF4, prExtend}, // Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE
+ {0x16B30, 0x16B36, prExtend}, // Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM
+ {0x16F4F, 0x16F4F, prExtend}, // Mn MIAO SIGN CONSONANT MODIFIER BAR
+ {0x16F51, 0x16F87, prSpacingMark}, // Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI
+ {0x16F8F, 0x16F92, prExtend}, // Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW
+ {0x1BC9D, 0x1BC9E, prExtend}, // Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK
+ {0x1BCA0, 0x1BCA3, prControl}, // Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
+ {0x1D165, 0x1D165, prExtend}, // Mc MUSICAL SYMBOL COMBINING STEM
+ {0x1D166, 0x1D166, prSpacingMark}, // Mc MUSICAL SYMBOL COMBINING SPRECHGESANG STEM
+ {0x1D167, 0x1D169, prExtend}, // Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3
+ {0x1D16D, 0x1D16D, prSpacingMark}, // Mc MUSICAL SYMBOL COMBINING AUGMENTATION DOT
+ {0x1D16E, 0x1D172, prExtend}, // Mc [5] MUSICAL SYMBOL COMBINING FLAG-1..MUSICAL SYMBOL COMBINING FLAG-5
+ {0x1D173, 0x1D17A, prControl}, // Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
+ {0x1D17B, 0x1D182, prExtend}, // Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE
+ {0x1D185, 0x1D18B, prExtend}, // Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE
+ {0x1D1AA, 0x1D1AD, prExtend}, // Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO
+ {0x1D242, 0x1D244, prExtend}, // Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME
+ {0x1DA00, 0x1DA36, prExtend}, // Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN
+ {0x1DA3B, 0x1DA6C, prExtend}, // Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT
+ {0x1DA75, 0x1DA75, prExtend}, // Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS
+ {0x1DA84, 0x1DA84, prExtend}, // Mn SIGNWRITING LOCATION HEAD NECK
+ {0x1DA9B, 0x1DA9F, prExtend}, // Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6
+ {0x1DAA1, 0x1DAAF, prExtend}, // Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16
+ {0x1E000, 0x1E006, prExtend}, // Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE
+ {0x1E008, 0x1E018, prExtend}, // Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU
+ {0x1E01B, 0x1E021, prExtend}, // Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI
+ {0x1E023, 0x1E024, prExtend}, // Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS
+ {0x1E026, 0x1E02A, prExtend}, // Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA
+ {0x1E130, 0x1E136, prExtend}, // Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D
+ {0x1E2EC, 0x1E2EF, prExtend}, // Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI
+ {0x1E8D0, 0x1E8D6, prExtend}, // Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS
+ {0x1E944, 0x1E94A, prExtend}, // Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA
+ {0x1F000, 0x1F02B, prExtendedPictographic}, // 5.1 [44] (๐Ÿ€€..๐Ÿ€ซ) MAHJONG TILE EAST WIND..MAHJONG TILE BACK
+ {0x1F02C, 0x1F02F, prExtendedPictographic}, // NA [4] (๐Ÿ€ฌ..๐Ÿ€ฏ) <reserved-1F02C>..<reserved-1F02F>
+ {0x1F030, 0x1F093, prExtendedPictographic}, // 5.1[100] (๐Ÿ€ฐ..๐Ÿ‚“) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06
+ {0x1F094, 0x1F09F, prExtendedPictographic}, // NA [12] (๐Ÿ‚”..๐Ÿ‚Ÿ) <reserved-1F094>..<reserved-1F09F>
+ {0x1F0A0, 0x1F0AE, prExtendedPictographic}, // 6.0 [15] (๐Ÿ‚ ..๐Ÿ‚ฎ) PLAYING CARD BACK..PLAYING CARD KING OF SPADES
+ {0x1F0AF, 0x1F0B0, prExtendedPictographic}, // NA [2] (๐Ÿ‚ฏ..๐Ÿ‚ฐ) <reserved-1F0AF>..<reserved-1F0B0>
+ {0x1F0B1, 0x1F0BE, prExtendedPictographic}, // 6.0 [14] (๐Ÿ‚ฑ..๐Ÿ‚พ) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS
+ {0x1F0BF, 0x1F0BF, prExtendedPictographic}, // 7.0 [1] (๐Ÿ‚ฟ) PLAYING CARD RED JOKER
+ {0x1F0C0, 0x1F0C0, prExtendedPictographic}, // NA [1] (๐Ÿƒ€) <reserved-1F0C0>
+ {0x1F0C1, 0x1F0CF, prExtendedPictographic}, // 6.0 [15] (๐Ÿƒ..๐Ÿƒ) PLAYING CARD ACE OF DIAMONDS..joker
+ {0x1F0D0, 0x1F0D0, prExtendedPictographic}, // NA [1] (๐Ÿƒ) <reserved-1F0D0>
+ {0x1F0D1, 0x1F0DF, prExtendedPictographic}, // 6.0 [15] (๐Ÿƒ‘..๐ŸƒŸ) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER
+ {0x1F0E0, 0x1F0F5, prExtendedPictographic}, // 7.0 [22] (๐Ÿƒ ..๐Ÿƒต) PLAYING CARD FOOL..PLAYING CARD TRUMP-21
+ {0x1F0F6, 0x1F0FF, prExtendedPictographic}, // NA [10] (๐Ÿƒถ..๐Ÿƒฟ) <reserved-1F0F6>..<reserved-1F0FF>
+ {0x1F10D, 0x1F10F, prExtendedPictographic}, // NA [3] (๐Ÿ„..๐Ÿ„) <reserved-1F10D>..<reserved-1F10F>
+ {0x1F12F, 0x1F12F, prExtendedPictographic}, // 11.0 [1] (๐Ÿ„ฏ) COPYLEFT SYMBOL
+ {0x1F16C, 0x1F16C, prExtendedPictographic}, // 12.0 [1] (๐Ÿ…ฌ) RAISED MR SIGN
+ {0x1F16D, 0x1F16F, prExtendedPictographic}, // NA [3] (๐Ÿ…ญ..๐Ÿ…ฏ) <reserved-1F16D>..<reserved-1F16F>
+ {0x1F170, 0x1F171, prExtendedPictographic}, // 6.0 [2] (๐Ÿ…ฐ๏ธ..๐Ÿ…ฑ๏ธ) A button (blood type)..B button (blood type)
+ {0x1F17E, 0x1F17E, prExtendedPictographic}, // 6.0 [1] (๐Ÿ…พ๏ธ) O button (blood type)
+ {0x1F17F, 0x1F17F, prExtendedPictographic}, // 5.2 [1] (๐Ÿ…ฟ๏ธ) P button
+ {0x1F18E, 0x1F18E, prExtendedPictographic}, // 6.0 [1] (๐Ÿ†Ž) AB button (blood type)
+ {0x1F191, 0x1F19A, prExtendedPictographic}, // 6.0 [10] (๐Ÿ†‘..๐Ÿ†š) CL button..VS button
+ {0x1F1AD, 0x1F1E5, prExtendedPictographic}, // NA [57] (๐Ÿ†ญ..๐Ÿ‡ฅ) <reserved-1F1AD>..<reserved-1F1E5>
+ {0x1F1E6, 0x1F1FF, prRegionalIndicator}, // So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z
+ {0x1F201, 0x1F202, prExtendedPictographic}, // 6.0 [2] (๐Ÿˆ..๐Ÿˆ‚๏ธ) Japanese โ€œhereโ€ button..Japanese โ€œservice chargeโ€ button
+ {0x1F203, 0x1F20F, prExtendedPictographic}, // NA [13] (๐Ÿˆƒ..๐Ÿˆ) <reserved-1F203>..<reserved-1F20F>
+ {0x1F21A, 0x1F21A, prExtendedPictographic}, // 5.2 [1] (๐Ÿˆš) Japanese โ€œfree of chargeโ€ button
+ {0x1F22F, 0x1F22F, prExtendedPictographic}, // 5.2 [1] (๐Ÿˆฏ) Japanese โ€œreservedโ€ button
+ {0x1F232, 0x1F23A, prExtendedPictographic}, // 6.0 [9] (๐Ÿˆฒ..๐Ÿˆบ) Japanese โ€œprohibitedโ€ button..Japanese โ€œopen for businessโ€ button
+ {0x1F23C, 0x1F23F, prExtendedPictographic}, // NA [4] (๐Ÿˆผ..๐Ÿˆฟ) <reserved-1F23C>..<reserved-1F23F>
+ {0x1F249, 0x1F24F, prExtendedPictographic}, // NA [7] (๐Ÿ‰‰..๐Ÿ‰) <reserved-1F249>..<reserved-1F24F>
+ {0x1F250, 0x1F251, prExtendedPictographic}, // 6.0 [2] (๐Ÿ‰..๐Ÿ‰‘) Japanese โ€œbargainโ€ button..Japanese โ€œacceptableโ€ button
+ {0x1F252, 0x1F25F, prExtendedPictographic}, // NA [14] (๐Ÿ‰’..๐Ÿ‰Ÿ) <reserved-1F252>..<reserved-1F25F>
+ {0x1F260, 0x1F265, prExtendedPictographic}, // 10.0 [6] (๐Ÿ‰ ..๐Ÿ‰ฅ) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI
+ {0x1F266, 0x1F2FF, prExtendedPictographic}, // NA[154] (๐Ÿ‰ฆ..๐Ÿ‹ฟ) <reserved-1F266>..<reserved-1F2FF>
+ {0x1F300, 0x1F320, prExtendedPictographic}, // 6.0 [33] (๐ŸŒ€..๐ŸŒ ) cyclone..shooting star
+ {0x1F321, 0x1F32C, prExtendedPictographic}, // 7.0 [12] (๐ŸŒก๏ธ..๐ŸŒฌ๏ธ) thermometer..wind face
+ {0x1F32D, 0x1F32F, prExtendedPictographic}, // 8.0 [3] (๐ŸŒญ..๐ŸŒฏ) hot dog..burrito
+ {0x1F330, 0x1F335, prExtendedPictographic}, // 6.0 [6] (๐ŸŒฐ..๐ŸŒต) chestnut..cactus
+ {0x1F336, 0x1F336, prExtendedPictographic}, // 7.0 [1] (๐ŸŒถ๏ธ) hot pepper
+ {0x1F337, 0x1F37C, prExtendedPictographic}, // 6.0 [70] (๐ŸŒท..๐Ÿผ) tulip..baby bottle
+ {0x1F37D, 0x1F37D, prExtendedPictographic}, // 7.0 [1] (๐Ÿฝ๏ธ) fork and knife with plate
+ {0x1F37E, 0x1F37F, prExtendedPictographic}, // 8.0 [2] (๐Ÿพ..๐Ÿฟ) bottle with popping cork..popcorn
+ {0x1F380, 0x1F393, prExtendedPictographic}, // 6.0 [20] (๐ŸŽ€..๐ŸŽ“) ribbon..graduation cap
+ {0x1F394, 0x1F39F, prExtendedPictographic}, // 7.0 [12] (๐ŸŽ”..๐ŸŽŸ๏ธ) HEART WITH TIP ON THE LEFT..admission tickets
+ {0x1F3A0, 0x1F3C4, prExtendedPictographic}, // 6.0 [37] (๐ŸŽ ..๐Ÿ„) carousel horse..person surfing
+ {0x1F3C5, 0x1F3C5, prExtendedPictographic}, // 7.0 [1] (๐Ÿ…) sports medal
+ {0x1F3C6, 0x1F3CA, prExtendedPictographic}, // 6.0 [5] (๐Ÿ†..๐ŸŠ) trophy..person swimming
+ {0x1F3CB, 0x1F3CE, prExtendedPictographic}, // 7.0 [4] (๐Ÿ‹๏ธ..๐ŸŽ๏ธ) person lifting weights..racing car
+ {0x1F3CF, 0x1F3D3, prExtendedPictographic}, // 8.0 [5] (๐Ÿ..๐Ÿ“) cricket game..ping pong
+ {0x1F3D4, 0x1F3DF, prExtendedPictographic}, // 7.0 [12] (๐Ÿ”๏ธ..๐ŸŸ๏ธ) snow-capped mountain..stadium
+ {0x1F3E0, 0x1F3F0, prExtendedPictographic}, // 6.0 [17] (๐Ÿ ..๐Ÿฐ) house..castle
+ {0x1F3F1, 0x1F3F7, prExtendedPictographic}, // 7.0 [7] (๐Ÿฑ..๐Ÿท๏ธ) WHITE PENNANT..label
+ {0x1F3F8, 0x1F3FA, prExtendedPictographic}, // 8.0 [3] (๐Ÿธ..๐Ÿบ) badminton..amphora
+ {0x1F3FB, 0x1F3FF, prExtend}, // Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6
+ {0x1F400, 0x1F43E, prExtendedPictographic}, // 6.0 [63] (๐Ÿ€..๐Ÿพ) rat..paw prints
+ {0x1F43F, 0x1F43F, prExtendedPictographic}, // 7.0 [1] (๐Ÿฟ๏ธ) chipmunk
+ {0x1F440, 0x1F440, prExtendedPictographic}, // 6.0 [1] (๐Ÿ‘€) eyes
+ {0x1F441, 0x1F441, prExtendedPictographic}, // 7.0 [1] (๐Ÿ‘๏ธ) eye
+ {0x1F442, 0x1F4F7, prExtendedPictographic}, // 6.0[182] (๐Ÿ‘‚..๐Ÿ“ท) ear..camera
+ {0x1F4F8, 0x1F4F8, prExtendedPictographic}, // 7.0 [1] (๐Ÿ“ธ) camera with flash
+ {0x1F4F9, 0x1F4FC, prExtendedPictographic}, // 6.0 [4] (๐Ÿ“น..๐Ÿ“ผ) video camera..videocassette
+ {0x1F4FD, 0x1F4FE, prExtendedPictographic}, // 7.0 [2] (๐Ÿ“ฝ๏ธ..๐Ÿ“พ) film projector..PORTABLE STEREO
+ {0x1F4FF, 0x1F4FF, prExtendedPictographic}, // 8.0 [1] (๐Ÿ“ฟ) prayer beads
+ {0x1F500, 0x1F53D, prExtendedPictographic}, // 6.0 [62] (๐Ÿ”€..๐Ÿ”ฝ) shuffle tracks button..downwards button
+ {0x1F546, 0x1F54A, prExtendedPictographic}, // 7.0 [5] (๐Ÿ•†..๐Ÿ•Š๏ธ) WHITE LATIN CROSS..dove
+ {0x1F54B, 0x1F54F, prExtendedPictographic}, // 8.0 [5] (๐Ÿ•‹..๐Ÿ•) kaaba..BOWL OF HYGIEIA
+ {0x1F550, 0x1F567, prExtendedPictographic}, // 6.0 [24] (๐Ÿ•..๐Ÿ•ง) one oโ€™clock..twelve-thirty
+ {0x1F568, 0x1F579, prExtendedPictographic}, // 7.0 [18] (๐Ÿ•จ..๐Ÿ•น๏ธ) RIGHT SPEAKER..joystick
+ {0x1F57A, 0x1F57A, prExtendedPictographic}, // 9.0 [1] (๐Ÿ•บ) man dancing
+ {0x1F57B, 0x1F5A3, prExtendedPictographic}, // 7.0 [41] (๐Ÿ•ป..๐Ÿ–ฃ) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX
+ {0x1F5A4, 0x1F5A4, prExtendedPictographic}, // 9.0 [1] (๐Ÿ–ค) black heart
+ {0x1F5A5, 0x1F5FA, prExtendedPictographic}, // 7.0 [86] (๐Ÿ–ฅ๏ธ..๐Ÿ—บ๏ธ) desktop computer..world map
+ {0x1F5FB, 0x1F5FF, prExtendedPictographic}, // 6.0 [5] (๐Ÿ—ป..๐Ÿ—ฟ) mount fuji..moai
+ {0x1F600, 0x1F600, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜€) grinning face
+ {0x1F601, 0x1F610, prExtendedPictographic}, // 6.0 [16] (๐Ÿ˜..๐Ÿ˜) beaming face with smiling eyes..neutral face
+ {0x1F611, 0x1F611, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜‘) expressionless face
+ {0x1F612, 0x1F614, prExtendedPictographic}, // 6.0 [3] (๐Ÿ˜’..๐Ÿ˜”) unamused face..pensive face
+ {0x1F615, 0x1F615, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜•) confused face
+ {0x1F616, 0x1F616, prExtendedPictographic}, // 6.0 [1] (๐Ÿ˜–) confounded face
+ {0x1F617, 0x1F617, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜—) kissing face
+ {0x1F618, 0x1F618, prExtendedPictographic}, // 6.0 [1] (๐Ÿ˜˜) face blowing a kiss
+ {0x1F619, 0x1F619, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜™) kissing face with smiling eyes
+ {0x1F61A, 0x1F61A, prExtendedPictographic}, // 6.0 [1] (๐Ÿ˜š) kissing face with closed eyes
+ {0x1F61B, 0x1F61B, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜›) face with tongue
+ {0x1F61C, 0x1F61E, prExtendedPictographic}, // 6.0 [3] (๐Ÿ˜œ..๐Ÿ˜ž) winking face with tongue..disappointed face
+ {0x1F61F, 0x1F61F, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜Ÿ) worried face
+ {0x1F620, 0x1F625, prExtendedPictographic}, // 6.0 [6] (๐Ÿ˜ ..๐Ÿ˜ฅ) angry face..sad but relieved face
+ {0x1F626, 0x1F627, prExtendedPictographic}, // 6.1 [2] (๐Ÿ˜ฆ..๐Ÿ˜ง) frowning face with open mouth..anguished face
+ {0x1F628, 0x1F62B, prExtendedPictographic}, // 6.0 [4] (๐Ÿ˜จ..๐Ÿ˜ซ) fearful face..tired face
+ {0x1F62C, 0x1F62C, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜ฌ) grimacing face
+ {0x1F62D, 0x1F62D, prExtendedPictographic}, // 6.0 [1] (๐Ÿ˜ญ) loudly crying face
+ {0x1F62E, 0x1F62F, prExtendedPictographic}, // 6.1 [2] (๐Ÿ˜ฎ..๐Ÿ˜ฏ) face with open mouth..hushed face
+ {0x1F630, 0x1F633, prExtendedPictographic}, // 6.0 [4] (๐Ÿ˜ฐ..๐Ÿ˜ณ) anxious face with sweat..flushed face
+ {0x1F634, 0x1F634, prExtendedPictographic}, // 6.1 [1] (๐Ÿ˜ด) sleeping face
+ {0x1F635, 0x1F640, prExtendedPictographic}, // 6.0 [12] (๐Ÿ˜ต..๐Ÿ™€) dizzy face..weary cat
+ {0x1F641, 0x1F642, prExtendedPictographic}, // 7.0 [2] (๐Ÿ™..๐Ÿ™‚) slightly frowning face..slightly smiling face
+ {0x1F643, 0x1F644, prExtendedPictographic}, // 8.0 [2] (๐Ÿ™ƒ..๐Ÿ™„) upside-down face..face with rolling eyes
+ {0x1F645, 0x1F64F, prExtendedPictographic}, // 6.0 [11] (๐Ÿ™…..๐Ÿ™) person gesturing NO..folded hands
+ {0x1F680, 0x1F6C5, prExtendedPictographic}, // 6.0 [70] (๐Ÿš€..๐Ÿ›…) rocket..left luggage
+ {0x1F6C6, 0x1F6CF, prExtendedPictographic}, // 7.0 [10] (๐Ÿ›†..๐Ÿ›๏ธ) TRIANGLE WITH ROUNDED CORNERS..bed
+ {0x1F6D0, 0x1F6D0, prExtendedPictographic}, // 8.0 [1] (๐Ÿ›) place of worship
+ {0x1F6D1, 0x1F6D2, prExtendedPictographic}, // 9.0 [2] (๐Ÿ›‘..๐Ÿ›’) stop sign..shopping cart
+ {0x1F6D3, 0x1F6D4, prExtendedPictographic}, // 10.0 [2] (๐Ÿ›“..๐Ÿ›”) STUPA..PAGODA
+ {0x1F6D5, 0x1F6D5, prExtendedPictographic}, // 12.0 [1] (๐Ÿ›•) hindu temple
+ {0x1F6D6, 0x1F6DF, prExtendedPictographic}, // NA [10] (๐Ÿ›–..๐Ÿ›Ÿ) <reserved-1F6D6>..<reserved-1F6DF>
+ {0x1F6E0, 0x1F6EC, prExtendedPictographic}, // 7.0 [13] (๐Ÿ› ๏ธ..๐Ÿ›ฌ) hammer and wrench..airplane arrival
+ {0x1F6ED, 0x1F6EF, prExtendedPictographic}, // NA [3] (๐Ÿ›ญ..๐Ÿ›ฏ) <reserved-1F6ED>..<reserved-1F6EF>
+ {0x1F6F0, 0x1F6F3, prExtendedPictographic}, // 7.0 [4] (๐Ÿ›ฐ๏ธ..๐Ÿ›ณ๏ธ) satellite..passenger ship
+ {0x1F6F4, 0x1F6F6, prExtendedPictographic}, // 9.0 [3] (๐Ÿ›ด..๐Ÿ›ถ) kick scooter..canoe
+ {0x1F6F7, 0x1F6F8, prExtendedPictographic}, // 10.0 [2] (๐Ÿ›ท..๐Ÿ›ธ) sled..flying saucer
+ {0x1F6F9, 0x1F6F9, prExtendedPictographic}, // 11.0 [1] (๐Ÿ›น) skateboard
+ {0x1F6FA, 0x1F6FA, prExtendedPictographic}, // 12.0 [1] (๐Ÿ›บ) auto rickshaw
+ {0x1F6FB, 0x1F6FF, prExtendedPictographic}, // NA [5] (๐Ÿ›ป..๐Ÿ›ฟ) <reserved-1F6FB>..<reserved-1F6FF>
+ {0x1F774, 0x1F77F, prExtendedPictographic}, // NA [12] (๐Ÿด..๐Ÿฟ) <reserved-1F774>..<reserved-1F77F>
+ {0x1F7D5, 0x1F7D8, prExtendedPictographic}, // 11.0 [4] (๐ŸŸ•..๐ŸŸ˜) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE
+ {0x1F7D9, 0x1F7DF, prExtendedPictographic}, // NA [7] (๐ŸŸ™..๐ŸŸŸ) <reserved-1F7D9>..<reserved-1F7DF>
+ {0x1F7E0, 0x1F7EB, prExtendedPictographic}, // 12.0 [12] (๐ŸŸ ..๐ŸŸซ) orange circle..brown square
+ {0x1F7EC, 0x1F7FF, prExtendedPictographic}, // NA [20] (๐ŸŸฌ..๐ŸŸฟ) <reserved-1F7EC>..<reserved-1F7FF>
+ {0x1F80C, 0x1F80F, prExtendedPictographic}, // NA [4] (๐Ÿ Œ..๐Ÿ ) <reserved-1F80C>..<reserved-1F80F>
+ {0x1F848, 0x1F84F, prExtendedPictographic}, // NA [8] (๐Ÿกˆ..๐Ÿก) <reserved-1F848>..<reserved-1F84F>
+ {0x1F85A, 0x1F85F, prExtendedPictographic}, // NA [6] (๐Ÿกš..๐ŸกŸ) <reserved-1F85A>..<reserved-1F85F>
+ {0x1F888, 0x1F88F, prExtendedPictographic}, // NA [8] (๐Ÿขˆ..๐Ÿข) <reserved-1F888>..<reserved-1F88F>
+ {0x1F8AE, 0x1F8FF, prExtendedPictographic}, // NA [82] (๐Ÿขฎ..๐Ÿฃฟ) <reserved-1F8AE>..<reserved-1F8FF>
+ {0x1F90C, 0x1F90C, prExtendedPictographic}, // NA [1] (๐ŸคŒ) <reserved-1F90C>
+ {0x1F90D, 0x1F90F, prExtendedPictographic}, // 12.0 [3] (๐Ÿค..๐Ÿค) white heart..pinching hand
+ {0x1F910, 0x1F918, prExtendedPictographic}, // 8.0 [9] (๐Ÿค..๐Ÿค˜) zipper-mouth face..sign of the horns
+ {0x1F919, 0x1F91E, prExtendedPictographic}, // 9.0 [6] (๐Ÿค™..๐Ÿคž) call me hand..crossed fingers
+ {0x1F91F, 0x1F91F, prExtendedPictographic}, // 10.0 [1] (๐ŸคŸ) love-you gesture
+ {0x1F920, 0x1F927, prExtendedPictographic}, // 9.0 [8] (๐Ÿค ..๐Ÿคง) cowboy hat face..sneezing face
+ {0x1F928, 0x1F92F, prExtendedPictographic}, // 10.0 [8] (๐Ÿคจ..๐Ÿคฏ) face with raised eyebrow..exploding head
+ {0x1F930, 0x1F930, prExtendedPictographic}, // 9.0 [1] (๐Ÿคฐ) pregnant woman
+ {0x1F931, 0x1F932, prExtendedPictographic}, // 10.0 [2] (๐Ÿคฑ..๐Ÿคฒ) breast-feeding..palms up together
+ {0x1F933, 0x1F93A, prExtendedPictographic}, // 9.0 [8] (๐Ÿคณ..๐Ÿคบ) selfie..person fencing
+ {0x1F93C, 0x1F93E, prExtendedPictographic}, // 9.0 [3] (๐Ÿคผ..๐Ÿคพ) people wrestling..person playing handball
+ {0x1F93F, 0x1F93F, prExtendedPictographic}, // 12.0 [1] (๐Ÿคฟ) diving mask
+ {0x1F940, 0x1F945, prExtendedPictographic}, // 9.0 [6] (๐Ÿฅ€..๐Ÿฅ…) wilted flower..goal net
+ {0x1F947, 0x1F94B, prExtendedPictographic}, // 9.0 [5] (๐Ÿฅ‡..๐Ÿฅ‹) 1st place medal..martial arts uniform
+ {0x1F94C, 0x1F94C, prExtendedPictographic}, // 10.0 [1] (๐ŸฅŒ) curling stone
+ {0x1F94D, 0x1F94F, prExtendedPictographic}, // 11.0 [3] (๐Ÿฅ..๐Ÿฅ) lacrosse..flying disc
+ {0x1F950, 0x1F95E, prExtendedPictographic}, // 9.0 [15] (๐Ÿฅ..๐Ÿฅž) croissant..pancakes
+ {0x1F95F, 0x1F96B, prExtendedPictographic}, // 10.0 [13] (๐ŸฅŸ..๐Ÿฅซ) dumpling..canned food
+ {0x1F96C, 0x1F970, prExtendedPictographic}, // 11.0 [5] (๐Ÿฅฌ..๐Ÿฅฐ) leafy green..smiling face with hearts
+ {0x1F971, 0x1F971, prExtendedPictographic}, // 12.0 [1] (๐Ÿฅฑ) yawning face
+ {0x1F972, 0x1F972, prExtendedPictographic}, // NA [1] (๐Ÿฅฒ) <reserved-1F972>
+ {0x1F973, 0x1F976, prExtendedPictographic}, // 11.0 [4] (๐Ÿฅณ..๐Ÿฅถ) partying face..cold face
+ {0x1F977, 0x1F979, prExtendedPictographic}, // NA [3] (๐Ÿฅท..๐Ÿฅน) <reserved-1F977>..<reserved-1F979>
+ {0x1F97A, 0x1F97A, prExtendedPictographic}, // 11.0 [1] (๐Ÿฅบ) pleading face
+ {0x1F97B, 0x1F97B, prExtendedPictographic}, // 12.0 [1] (๐Ÿฅป) sari
+ {0x1F97C, 0x1F97F, prExtendedPictographic}, // 11.0 [4] (๐Ÿฅผ..๐Ÿฅฟ) lab coat..flat shoe
+ {0x1F980, 0x1F984, prExtendedPictographic}, // 8.0 [5] (๐Ÿฆ€..๐Ÿฆ„) crab..unicorn
+ {0x1F985, 0x1F991, prExtendedPictographic}, // 9.0 [13] (๐Ÿฆ…..๐Ÿฆ‘) eagle..squid
+ {0x1F992, 0x1F997, prExtendedPictographic}, // 10.0 [6] (๐Ÿฆ’..๐Ÿฆ—) giraffe..cricket
+ {0x1F998, 0x1F9A2, prExtendedPictographic}, // 11.0 [11] (๐Ÿฆ˜..๐Ÿฆข) kangaroo..swan
+ {0x1F9A3, 0x1F9A4, prExtendedPictographic}, // NA [2] (๐Ÿฆฃ..๐Ÿฆค) <reserved-1F9A3>..<reserved-1F9A4>
+ {0x1F9A5, 0x1F9AA, prExtendedPictographic}, // 12.0 [6] (๐Ÿฆฅ..๐Ÿฆช) sloth..oyster
+ {0x1F9AB, 0x1F9AD, prExtendedPictographic}, // NA [3] (๐Ÿฆซ..๐Ÿฆญ) <reserved-1F9AB>..<reserved-1F9AD>
+ {0x1F9AE, 0x1F9AF, prExtendedPictographic}, // 12.0 [2] (๐Ÿฆฎ..๐Ÿฆฏ) guide dog..probing cane
+ {0x1F9B0, 0x1F9B9, prExtendedPictographic}, // 11.0 [10] (๐Ÿฆฐ..๐Ÿฆน) red hair..supervillain
+ {0x1F9BA, 0x1F9BF, prExtendedPictographic}, // 12.0 [6] (๐Ÿฆบ..๐Ÿฆฟ) safety vest..mechanical leg
+ {0x1F9C0, 0x1F9C0, prExtendedPictographic}, // 8.0 [1] (๐Ÿง€) cheese wedge
+ {0x1F9C1, 0x1F9C2, prExtendedPictographic}, // 11.0 [2] (๐Ÿง..๐Ÿง‚) cupcake..salt
+ {0x1F9C3, 0x1F9CA, prExtendedPictographic}, // 12.0 [8] (๐Ÿงƒ..๐ŸงŠ) beverage box..ice cube
+ {0x1F9CB, 0x1F9CC, prExtendedPictographic}, // NA [2] (๐Ÿง‹..๐ŸงŒ) <reserved-1F9CB>..<reserved-1F9CC>
+ {0x1F9CD, 0x1F9CF, prExtendedPictographic}, // 12.0 [3] (๐Ÿง..๐Ÿง) person standing..deaf person
+ {0x1F9D0, 0x1F9E6, prExtendedPictographic}, // 10.0 [23] (๐Ÿง..๐Ÿงฆ) face with monocle..socks
+ {0x1F9E7, 0x1F9FF, prExtendedPictographic}, // 11.0 [25] (๐Ÿงง..๐Ÿงฟ) red envelope..nazar amulet
+ {0x1FA00, 0x1FA53, prExtendedPictographic}, // 12.0 [84] (๐Ÿจ€..๐Ÿฉ“) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP
+ {0x1FA54, 0x1FA5F, prExtendedPictographic}, // NA [12] (๐Ÿฉ”..๐ŸฉŸ) <reserved-1FA54>..<reserved-1FA5F>
+ {0x1FA60, 0x1FA6D, prExtendedPictographic}, // 11.0 [14] (๐Ÿฉ ..๐Ÿฉญ) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER
+ {0x1FA6E, 0x1FA6F, prExtendedPictographic}, // NA [2] (๐Ÿฉฎ..๐Ÿฉฏ) <reserved-1FA6E>..<reserved-1FA6F>
+ {0x1FA70, 0x1FA73, prExtendedPictographic}, // 12.0 [4] (๐Ÿฉฐ..๐Ÿฉณ) ballet shoes..shorts
+ {0x1FA74, 0x1FA77, prExtendedPictographic}, // NA [4] (๐Ÿฉด..๐Ÿฉท) <reserved-1FA74>..<reserved-1FA77>
+ {0x1FA78, 0x1FA7A, prExtendedPictographic}, // 12.0 [3] (๐Ÿฉธ..๐Ÿฉบ) drop of blood..stethoscope
+ {0x1FA7B, 0x1FA7F, prExtendedPictographic}, // NA [5] (๐Ÿฉป..๐Ÿฉฟ) <reserved-1FA7B>..<reserved-1FA7F>
+ {0x1FA80, 0x1FA82, prExtendedPictographic}, // 12.0 [3] (๐Ÿช€..๐Ÿช‚) yo-yo..parachute
+ {0x1FA83, 0x1FA8F, prExtendedPictographic}, // NA [13] (๐Ÿชƒ..๐Ÿช) <reserved-1FA83>..<reserved-1FA8F>
+ {0x1FA90, 0x1FA95, prExtendedPictographic}, // 12.0 [6] (๐Ÿช..๐Ÿช•) ringed planet..banjo
+ {0x1FA96, 0x1FFFD, prExtendedPictographic}, // NA[1384] (๐Ÿช–..๐Ÿฟฝ) <reserved-1FA96>..<reserved-1FFFD>
+ {0xE0000, 0xE0000, prControl}, // Cn <reserved-E0000>
+ {0xE0001, 0xE0001, prControl}, // Cf LANGUAGE TAG
+ {0xE0002, 0xE001F, prControl}, // Cn [30] <reserved-E0002>..<reserved-E001F>
+ {0xE0020, 0xE007F, prExtend}, // Cf [96] TAG SPACE..CANCEL TAG
+ {0xE0080, 0xE00FF, prControl}, // Cn [128] <reserved-E0080>..<reserved-E00FF>
+ {0xE0100, 0xE01EF, prExtend}, // Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
+ {0xE01F0, 0xE0FFF, prControl}, // Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
+}
+
+// property returns the Unicode property value (see constants above) of the
+// given code point.
+func property(r rune) int {
+ // Run a binary search.
+ from := 0
+ to := len(codePoints)
+ for to > from {
+ middle := (from + to) / 2
+ cpRange := codePoints[middle]
+ if int(r) < cpRange[0] {
+ to = middle
+ continue
+ }
+ if int(r) > cpRange[1] {
+ from = middle + 1
+ continue
+ }
+ return cpRange[2]
+ }
+ return prAny
+}
diff --git a/vendor/github.com/tinylib/msgp/msgp/errors.go b/vendor/github.com/tinylib/msgp/msgp/errors.go
index cc78a980..921e8553 100644
--- a/vendor/github.com/tinylib/msgp/msgp/errors.go
+++ b/vendor/github.com/tinylib/msgp/msgp/errors.go
@@ -123,6 +123,9 @@ func (e errWrapped) Resumable() bool {
return resumableDefault
}
+// Unwrap returns the cause.
+func (e errWrapped) Unwrap() error { return e.cause }
+
type errShort struct{}
func (e errShort) Error() string { return "msgp: too few bytes left to read object" }
diff --git a/vendor/github.com/tinylib/msgp/msgp/json.go b/vendor/github.com/tinylib/msgp/msgp/json.go
index 77601e52..0e11e603 100644
--- a/vendor/github.com/tinylib/msgp/msgp/json.go
+++ b/vendor/github.com/tinylib/msgp/msgp/json.go
@@ -206,7 +206,7 @@ func rwFloat32(dst jsWriter, src *Reader) (int, error) {
if err != nil {
return 0, err
}
- src.scratch = strconv.AppendFloat(src.scratch[:0], float64(f), 'f', -1, 64)
+ src.scratch = strconv.AppendFloat(src.scratch[:0], float64(f), 'f', -1, 32)
return dst.Write(src.scratch)
}
@@ -215,7 +215,7 @@ func rwFloat64(dst jsWriter, src *Reader) (int, error) {
if err != nil {
return 0, err
}
- src.scratch = strconv.AppendFloat(src.scratch[:0], f, 'f', -1, 32)
+ src.scratch = strconv.AppendFloat(src.scratch[:0], f, 'f', -1, 64)
return dst.Write(src.scratch)
}
diff --git a/vendor/github.com/tinylib/msgp/msgp/read.go b/vendor/github.com/tinylib/msgp/msgp/read.go
index aa668c57..fe2de9e0 100644
--- a/vendor/github.com/tinylib/msgp/msgp/read.go
+++ b/vendor/github.com/tinylib/msgp/msgp/read.go
@@ -126,6 +126,11 @@ func NewReaderSize(r io.Reader, sz int) *Reader {
return &Reader{R: fwd.NewReaderSize(r, sz)}
}
+// NewReaderBuf returns a *Reader with a provided buffer.
+func NewReaderBuf(r io.Reader, buf []byte) *Reader {
+ return &Reader{R: fwd.NewReaderBuf(r, buf)}
+}
+
// Reader wraps an io.Reader and provides
// methods to read MessagePack-encoded values
// from it. Readers are buffered.
diff --git a/vendor/github.com/tinylib/msgp/msgp/read_bytes.go b/vendor/github.com/tinylib/msgp/msgp/read_bytes.go
index e4199757..f6674507 100644
--- a/vendor/github.com/tinylib/msgp/msgp/read_bytes.go
+++ b/vendor/github.com/tinylib/msgp/msgp/read_bytes.go
@@ -253,6 +253,46 @@ func ReadArrayHeaderBytes(b []byte) (sz uint32, o []byte, err error) {
}
}
+// ReadBytesHeader reads the 'bin' header size
+// off of 'b' and returns the size and remaining bytes.
+// Possible errors:
+// - ErrShortBytes (too few bytes)
+// - TypeError{} (not a bin object)
+func ReadBytesHeader(b []byte) (sz uint32, o []byte, err error) {
+ if len(b) < 1 {
+ return 0, nil, ErrShortBytes
+ }
+ switch b[0] {
+ case mbin8:
+ if len(b) < 2 {
+ err = ErrShortBytes
+ return
+ }
+ sz = uint32(b[1])
+ o = b[2:]
+ return
+ case mbin16:
+ if len(b) < 3 {
+ err = ErrShortBytes
+ return
+ }
+ sz = uint32(big.Uint16(b[1:]))
+ o = b[3:]
+ return
+ case mbin32:
+ if len(b) < 5 {
+ err = ErrShortBytes
+ return
+ }
+ sz = big.Uint32(b[1:])
+ o = b[5:]
+ return
+ default:
+ err = badPrefix(BinType, b[0])
+ return
+ }
+}
+
// ReadNilBytes tries to read a "nil" byte
// off of 'b' and return the remaining bytes.
// Possible errors:
diff --git a/vendor/github.com/tinylib/msgp/msgp/unsafe.go b/vendor/github.com/tinylib/msgp/msgp/unsafe.go
index 3978b6ff..d9fb3535 100644
--- a/vendor/github.com/tinylib/msgp/msgp/unsafe.go
+++ b/vendor/github.com/tinylib/msgp/msgp/unsafe.go
@@ -3,7 +3,6 @@
package msgp
import (
- "reflect"
"unsafe"
)
@@ -24,18 +23,14 @@ const (
// THIS IS EVIL CODE.
// YOU HAVE BEEN WARNED.
func UnsafeString(b []byte) string {
- sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
- return *(*string)(unsafe.Pointer(&reflect.StringHeader{Data: sh.Data, Len: sh.Len}))
+ return *(*string)(unsafe.Pointer(&b))
}
// UnsafeBytes returns the string as a byte slice
-// THIS SHOULD ONLY BE USED BY THE CODE GENERATOR.
-// THIS IS EVIL CODE.
-// YOU HAVE BEEN WARNED.
+//
+// Deprecated:
+// Since this code is no longer used by the code generator,
+// UnsafeBytes(s) is precisely equivalent to []byte(s)
func UnsafeBytes(s string) []byte {
- return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
- Len: len(s),
- Cap: len(s),
- Data: (*(*reflect.StringHeader)(unsafe.Pointer(&s))).Data,
- }))
+ return []byte(s)
}
diff --git a/vendor/github.com/tinylib/msgp/msgp/write.go b/vendor/github.com/tinylib/msgp/msgp/write.go
index fb1947c5..407ec1f8 100644
--- a/vendor/github.com/tinylib/msgp/msgp/write.go
+++ b/vendor/github.com/tinylib/msgp/msgp/write.go
@@ -10,6 +10,11 @@ import (
"time"
)
+const (
+ // min buffer size for the writer
+ minWriterSize = 18
+)
+
// Sizer is an interface implemented
// by types that can estimate their
// size when MessagePack encoded.
@@ -120,16 +125,27 @@ func NewWriter(w io.Writer) *Writer {
// NewWriterSize returns a writer with a custom buffer size.
func NewWriterSize(w io.Writer, sz int) *Writer {
- // we must be able to require() 18
+ // we must be able to require() 'minWriterSize'
// contiguous bytes, so that is the
// practical minimum buffer size
- if sz < 18 {
- sz = 18
+ if sz < minWriterSize {
+ sz = minWriterSize
}
+ buf := make([]byte, sz)
+ return NewWriterBuf(w, buf)
+}
+// NewWriterBuf returns a writer with a provided buffer.
+// 'buf' is not used when the capacity is smaller than 18,
+// custom buffer is allocated instead.
+func NewWriterBuf(w io.Writer, buf []byte) *Writer {
+ if cap(buf) < minWriterSize {
+ buf = make([]byte, minWriterSize)
+ }
+ buf = buf[:cap(buf)]
return &Writer{
w: w,
- buf: make([]byte, sz),
+ buf: buf,
}
}
diff --git a/vendor/github.com/tinylib/msgp/msgp/write_bytes.go b/vendor/github.com/tinylib/msgp/msgp/write_bytes.go
index eaa03c46..93d6d764 100644
--- a/vendor/github.com/tinylib/msgp/msgp/write_bytes.go
+++ b/vendor/github.com/tinylib/msgp/msgp/write_bytes.go
@@ -193,6 +193,26 @@ func AppendBytes(b []byte, bts []byte) []byte {
return o[:n+copy(o[n:], bts)]
}
+// AppendBytesHeader appends an 'bin' header with
+// the given size to the slice.
+func AppendBytesHeader(b []byte, sz uint32) []byte {
+ var o []byte
+ var n int
+ switch {
+ case sz <= math.MaxUint8:
+ o, n = ensure(b, 2)
+ prefixu8(o[n:], mbin8, uint8(sz))
+ return o
+ case sz <= math.MaxUint16:
+ o, n = ensure(b, 3)
+ prefixu16(o[n:], mbin16, uint16(sz))
+ return o
+ }
+ o, n = ensure(b, 5)
+ prefixu32(o[n:], mbin32, sz)
+ return o
+}
+
// AppendBool appends a bool to the slice
func AppendBool(b []byte, t bool) []byte {
if t {
diff --git a/vendor/github.com/wiggin77/merror/.travis.yml b/vendor/github.com/wiggin77/merror/.travis.yml
new file mode 100644
index 00000000..9899b387
--- /dev/null
+++ b/vendor/github.com/wiggin77/merror/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+sudo: false
+before_script:
+ - go vet ./...
+ \ No newline at end of file
diff --git a/vendor/github.com/wiggin77/merror/README.md b/vendor/github.com/wiggin77/merror/README.md
index 8a31687f..964c7ac8 100644
--- a/vendor/github.com/wiggin77/merror/README.md
+++ b/vendor/github.com/wiggin77/merror/README.md
@@ -1,2 +1,6 @@
# merror
-Multiple Error aggregator for Golang.
+
+[![GoDoc](https://godoc.org/github.com/wiggin77/merror?status.svg)](https://godoc.org/github.com/wiggin77/merror)
+[![Build Status](https://travis-ci.org/wiggin77/merror.svg?branch=master)](https://travis-ci.org/wiggin77/merror)
+
+Multiple Error aggregator for Go.
diff --git a/vendor/github.com/wiggin77/merror/go.mod b/vendor/github.com/wiggin77/merror/go.mod
index 44982f78..ba207ace 100644
--- a/vendor/github.com/wiggin77/merror/go.mod
+++ b/vendor/github.com/wiggin77/merror/go.mod
@@ -1 +1,5 @@
module github.com/wiggin77/merror
+
+go 1.15
+
+require github.com/stretchr/testify v1.6.1
diff --git a/vendor/github.com/wiggin77/merror/go.sum b/vendor/github.com/wiggin77/merror/go.sum
new file mode 100644
index 00000000..56d62e7c
--- /dev/null
+++ b/vendor/github.com/wiggin77/merror/go.sum
@@ -0,0 +1,10 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/vendor/github.com/wiggin77/merror/merror.go b/vendor/github.com/wiggin77/merror/merror.go
index 01f19913..def75354 100644
--- a/vendor/github.com/wiggin77/merror/merror.go
+++ b/vendor/github.com/wiggin77/merror/merror.go
@@ -1,9 +1,13 @@
package merror
+import "sync"
+
// MError represents zero or more errors that can be
// accumulated via the `Append` method.
type MError struct {
- cap int
+ cap int
+
+ mux sync.RWMutex
errors []error
overflow int
formatter FormatterFunc
@@ -34,6 +38,10 @@ func (me *MError) Append(err error) {
if err == nil {
return
}
+
+ me.mux.Lock()
+ defer me.mux.Unlock()
+
if me.cap > 0 && len(me.errors) >= me.cap {
me.overflow++
} else {
@@ -41,26 +49,41 @@ func (me *MError) Append(err error) {
}
}
-// Errors returns an array of the `error` instances that have been
+// Errors returns a slice of the `error` instances that have been
// appended to this `MError`.
func (me *MError) Errors() []error {
- return me.errors
+ me.mux.RLock()
+ defer me.mux.RUnlock()
+
+ errs := make([]error, len(me.errors))
+ copy(errs, me.errors)
+
+ return errs
}
// Len returns the number of errors that have been appended.
func (me *MError) Len() int {
+ me.mux.RLock()
+ defer me.mux.RUnlock()
+
return len(me.errors)
}
// Overflow returns the number of errors that have been truncated
// because maximum capacity was exceeded.
func (me *MError) Overflow() int {
+ me.mux.RLock()
+ defer me.mux.RUnlock()
+
return me.overflow
}
// SetFormatter sets the `FormatterFunc` to be used when `Error` is
// called. The previous `FormatterFunc` is returned.
func (me *MError) SetFormatter(f FormatterFunc) (old FormatterFunc) {
+ me.mux.Lock()
+ defer me.mux.Unlock()
+
old = me.formatter
me.formatter = f
return
@@ -69,7 +92,14 @@ func (me *MError) SetFormatter(f FormatterFunc) (old FormatterFunc) {
// ErrorOrNil returns nil if this `MError` contains no errors,
// otherwise this `MError` is returned.
func (me *MError) ErrorOrNil() error {
- if me == nil || len(me.errors) == 0 {
+ if me == nil {
+ return nil
+ }
+
+ me.mux.RLock()
+ defer me.mux.RUnlock()
+
+ if len(me.errors) == 0 {
return nil
}
return me
@@ -79,6 +109,9 @@ func (me *MError) ErrorOrNil() error {
// The output format depends on the `Formatter` set for this
// merror instance, or the global formatter if none set.
func (me *MError) Error() string {
+ me.mux.RLock()
+ defer me.mux.RUnlock()
+
f := me.formatter
if f == nil {
f = GlobalFormatter