Compare commits
	
		
			122 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6f131250f1 | ||
|   | 221a63d980 | ||
|   | d02eda147c | ||
|   | 9b25716136 | ||
|   | 6628a47f23 | ||
|   | ec0e6bc3f8 | ||
|   | d2c02be3a0 | ||
|   | 594492fbdd | ||
|   | bd9ea7a88d | ||
|   | 51327a4056 | ||
|   | 33bd60528b | ||
|   | 7e54474111 | ||
|   | e307069d62 | ||
|   | 91db63294c | ||
|   | fd04e08c9c | ||
|   | 6576409d60 | ||
|   | 045cb2058c | ||
|   | d03afc12fd | ||
|   | 48799a3cff | ||
|   | dba259e9f1 | ||
|   | 07885f5810 | ||
|   | 696c518550 | ||
|   | 411ef2691c | ||
|   | fc6074ea9f | ||
|   | ab1670e2ce | ||
|   | 9142a33bbf | ||
|   | f6eefa4ecc | ||
|   | f1db166ac4 | ||
|   | 887c2bc56d | ||
|   | f0738a93c3 | ||
|   | 75381c2c6e | ||
|   | bf0b9959d1 | ||
|   | 406a54b597 | ||
|   | be04d1a862 | ||
|   | 85b2d5a124 | ||
|   | 521a7ed7b0 | ||
|   | 529b188164 | ||
|   | 8d307d8134 | ||
|   | 8c675b52bc | ||
|   | aa51aa2aa0 | ||
|   | 86865c6da5 | ||
|   | 45296100df | ||
|   | 1605fbc012 | ||
|   | c6c92e273d | ||
|   | 467b373c43 | ||
|   | 72ce7f06e9 | ||
|   | 346a7284f7 | ||
|   | ee4ac67081 | ||
|   | 5a93d14d75 | ||
|   | 96a47a60ad | ||
|   | b24a47ad7f | ||
|   | cd1fd1bb7c | ||
|   | d44df7b6e6 | ||
|   | 9d1ac0c84b | ||
|   | 76af9cba5a | ||
|   | b69fc30902 | ||
|   | c3174f4de9 | ||
|   | 99ce68e9ba | ||
|   | 0cf73673a9 | ||
|   | 08f442dc7b | ||
|   | 8a8b95228c | ||
|   | 31a752fa21 | ||
|   | a83831e68d | ||
|   | a12a8d4fe2 | ||
|   | e57f3a7e6c | ||
|   | 68fbed9281 | ||
|   | 8bfaa007d5 | ||
|   | 76360f89c1 | ||
|   | d525230abd | ||
|   | b4aa637d41 | ||
|   | 7c4334d0de | ||
|   | 062be8d7c9 | ||
|   | db25ee59c5 | ||
|   | 4b0bc6d0bf | ||
|   | 8c0b04b995 | ||
|   | e5989adf92 | ||
|   | 9e5da2f9d7 | ||
|   | a284a228a3 | ||
|   | 2133e0d1be | ||
|   | a6f37f1d61 | ||
|   | 9de9151826 | ||
|   | fdd5ada98c | ||
|   | 80fcf18e24 | ||
|   | ab94b5ca7a | ||
|   | 8d2ce56c37 | ||
|   | 1ec324354b | ||
|   | 16be6601c8 | ||
|   | 98027446c8 | ||
|   | f2f1d874e1 | ||
|   | 25a72113b1 | ||
|   | 79c4ad5015 | ||
|   | e24f1c7c87 | ||
|   | dbf8a326d5 | ||
|   | 0bc9c70c66 | ||
|   | 594d2155e3 | ||
|   | 20dbd71306 | ||
|   | 6a727b9723 | ||
|   | 02a5bc096f | ||
|   | 2110db6f0c | ||
|   | 2bac867382 | ||
|   | 5fbd8a3be0 | ||
|   | ad6440b603 | ||
|   | 064b6a915f | ||
|   | 1578ebb0e2 | ||
|   | 73525a4bbc | ||
|   | d62f49d1fc | ||
|   | 63b88e77f2 | ||
|   | 3d8f15c20b | ||
|   | cac5d56d60 | ||
|   | bd2a672c14 | ||
|   | 82396e73f5 | ||
|   | ba928b169d | ||
|   | 4fed720f97 | ||
|   | 78238c85d4 | ||
|   | 4f2ae7b73f | ||
|   | f82a9cc7ac | ||
|   | cce7624ab8 | ||
|   | c5ecd09172 | ||
|   | 7b21c1c2f4 | ||
|   | f8714d81f5 | ||
|   | 8622656005 | ||
|   | 52237fadb6 | 
							
								
								
									
										26
									
								
								.github/ISSUE_TEMPLATE/Bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.github/ISSUE_TEMPLATE/Bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| --- | ||||
| name: Bug report | ||||
| about: Create a report to help us improve. (Check the FAQ on the wiki first) | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Describe the bug** | ||||
| A clear and concise description of what the bug is. | ||||
|  | ||||
| **To Reproduce** | ||||
| Steps to reproduce the behavior: | ||||
|  | ||||
| **Expected behavior** | ||||
| A clear and concise description of what you expected to happen. | ||||
|  | ||||
| **Screenshots/debug logs** | ||||
| If applicable, add screenshots to help explain your problem. | ||||
| Use logs from running `matterbridge -debug` if possible. | ||||
|  | ||||
| **Environment (please complete the following information):** | ||||
|  - OS: [e.g. linux] | ||||
|  - Matterbridge version: output of  `matterbridge -version` | ||||
|  - If self compiled: output of `git rev-parse HEAD` | ||||
|  | ||||
| **Additional context** | ||||
| Please add your configuration file  (be sure to exclude or anonymize private data (tokens/passwords)) | ||||
							
								
								
									
										17
									
								
								.github/ISSUE_TEMPLATE/Feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.github/ISSUE_TEMPLATE/Feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| --- | ||||
| name: Feature request | ||||
| about: Suggest an idea for this project | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Is your feature request related to a problem? Please describe.** | ||||
| A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] | ||||
|  | ||||
| **Describe the solution you'd like** | ||||
| A clear and concise description of what you want to happen. | ||||
|  | ||||
| **Describe alternatives you've considered** | ||||
| A clear and concise description of any alternative solutions or features you've considered. | ||||
|  | ||||
| **Additional context** | ||||
| Add any other context or screenshots about the feature request here. | ||||
| @@ -1,7 +1,7 @@ | ||||
| language: go | ||||
| go: | ||||
|     #- 1.7.x | ||||
|     - 1.9.x | ||||
|     - 1.10.x | ||||
|       # - tip | ||||
|  | ||||
| # we have everything vendored | ||||
| @@ -36,13 +36,15 @@ before_script: | ||||
| script: | ||||
|  #- test -z $(gofmt -s -l $GO_FILES)  # Fail if a .go file hasn't been formatted with gofmt | ||||
|   - go test -v -race $PKGS            # Run all the tests with the race detector enabled | ||||
|   - go vet $PKGS                      # go vet is the official Go static analyzer | ||||
|  #  - go vet $PKGS                      # go vet is the official Go static analyzer | ||||
|   - megacheck $PKGS                   # "go vet on steroids" + linter | ||||
|   - /bin/bash ci/bintray.sh | ||||
|   #- golint -set_exit_status $PKGS     # one last linter | ||||
|  | ||||
| deploy: | ||||
|   provider: bintray | ||||
|   edge: | ||||
|     branch: v1.8.47 | ||||
|   file: ci/deploy.json | ||||
|   user: 42wim | ||||
|   key: | ||||
|   | ||||
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,16 +1,19 @@ | ||||
| # matterbridge | ||||
| Click on one of the badges below to join the chat    | ||||
|  | ||||
| [](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) [](https://inverse.chat) [](https://www.twitch.tv/matterbridge) | ||||
| [](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) [](https://inverse.chat) [](https://www.twitch.tv/matterbridge) [](https://matterbridge.zulipchat.com/register/) | ||||
|  | ||||
| [](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam and ssh-chat | ||||
| Has a REST API.   | ||||
| Simple bridge between IRC, XMPP, Gitter, Mattermost, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam, ssh-chat and Zulip | ||||
| Has a REST API.    | ||||
| Minecraft server chat support via [MatterLink](https://github.com/elytra/MatterLink) | ||||
|  | ||||
| **Mattermost isn't required to run matterbridge. It bridges between any supported protocol.**    | ||||
| (The name matterbridge is a remnant when it was only bridging mattermost) | ||||
|  | ||||
| # Table of Contents | ||||
|  * [Features](https://github.com/42wim/matterbridge/wiki/Features) | ||||
|  * [Requirements](#requirements) | ||||
| @@ -46,7 +49,7 @@ Used by at least 2 projects. Feel free to make a PR to add your project to this | ||||
|  | ||||
| # Requirements | ||||
| Accounts to one of the supported bridges | ||||
| * [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.x | ||||
| * [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.x, 5.x | ||||
| * [IRC](http://www.mirc.com/servers.html) | ||||
| * [XMPP](https://jabber.org) | ||||
| * [Gitter](https://gitter.im) | ||||
| @@ -59,17 +62,20 @@ Accounts to one of the supported bridges | ||||
| * [Steam](https://store.steampowered.com/) | ||||
| * [Twitch](https://twitch.tv) | ||||
| * [Ssh-chat](https://github.com/shazow/ssh-chat) | ||||
| * [Zulip](https://zulipchat.com) | ||||
|  | ||||
| # Screenshots | ||||
| See https://github.com/42wim/matterbridge/wiki | ||||
|  | ||||
| # Installing | ||||
| ## Binaries | ||||
| * Latest stable release [v1.8.0](https://github.com/42wim/matterbridge/releases/latest) | ||||
| * Latest stable release [v1.11.0](https://github.com/42wim/matterbridge/releases/latest) | ||||
| * Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)   | ||||
|  | ||||
| ## Building | ||||
| Go 1.8+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) | ||||
| Go 1.8+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH](https://golang.org/doc/code.html#GOPATH). | ||||
|  | ||||
| After Go is setup, download matterbridge to your $GOPATH directory.  | ||||
|  | ||||
| ``` | ||||
| cd $GOPATH | ||||
| @@ -181,11 +187,14 @@ Want to tip ? | ||||
| * btc: 1N7cKHj5SfqBHBzDJ6kad4BzeqUBBS2zhs | ||||
|  | ||||
| # Thanks | ||||
| [](https://www.digitalocean.com/) for sponsoring demo/testing droplets. | ||||
|  | ||||
| Matterbridge wouldn't exist without these libraries: | ||||
| * discord - https://github.com/bwmarrin/discordgo | ||||
| * echo - https://github.com/labstack/echo | ||||
| * gitter - https://github.com/sromku/go-gitter | ||||
| * gops - https://github.com/google/gops | ||||
| * gozulipbot - https://github.com/ifo/gozulipbot | ||||
| * irc - https://github.com/lrstanley/girc | ||||
| * mattermost - https://github.com/mattermost/platform | ||||
| * matrix - https://github.com/matrix-org/gomatrix | ||||
| @@ -193,3 +202,4 @@ Matterbridge wouldn't exist without these libraries: | ||||
| * steam - https://github.com/Philipp15b/go-steam | ||||
| * telegram - https://github.com/go-telegram-bot-api/telegram-bot-api | ||||
| * xmpp - https://github.com/mattn/go-xmpp | ||||
| * zulip - https://github.com/ifo/gozulipbot | ||||
|   | ||||
| @@ -2,20 +2,21 @@ package api | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/labstack/echo" | ||||
| 	"github.com/labstack/echo/middleware" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/zfjagann/golang-ring" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/labstack/echo" | ||||
| 	"github.com/labstack/echo/middleware" | ||||
| 	"github.com/zfjagann/golang-ring" | ||||
| ) | ||||
|  | ||||
| type Api struct { | ||||
| 	Messages ring.Ring | ||||
| 	sync.RWMutex | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| type ApiMessage struct { | ||||
| @@ -26,34 +27,27 @@ type ApiMessage struct { | ||||
| 	Gateway  string `json:"gateway"` | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "api" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Api { | ||||
| 	b := &Api{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Api{Config: cfg} | ||||
| 	e := echo.New() | ||||
| 	e.HideBanner = true | ||||
| 	e.HidePort = true | ||||
| 	b.Messages = ring.Ring{} | ||||
| 	b.Messages.SetCapacity(b.Config.Buffer) | ||||
| 	if b.Config.Token != "" { | ||||
| 	b.Messages.SetCapacity(b.GetInt("Buffer")) | ||||
| 	if b.GetString("Token") != "" { | ||||
| 		e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { | ||||
| 			return key == b.Config.Token, nil | ||||
| 			return key == b.GetString("Token"), nil | ||||
| 		})) | ||||
| 	} | ||||
| 	e.GET("/api/messages", b.handleMessages) | ||||
| 	e.GET("/api/stream", b.handleStream) | ||||
| 	e.POST("/api/message", b.handlePostMessage) | ||||
| 	go func() { | ||||
| 		if b.Config.BindAddress == "" { | ||||
| 			flog.Fatalf("No BindAddress configured.") | ||||
| 		if b.GetString("BindAddress") == "" { | ||||
| 			b.Log.Fatalf("No BindAddress configured.") | ||||
| 		} | ||||
| 		flog.Infof("Listening on %s", b.Config.BindAddress) | ||||
| 		flog.Fatal(e.Start(b.Config.BindAddress)) | ||||
| 		b.Log.Infof("Listening on %s", b.GetString("BindAddress")) | ||||
| 		b.Log.Fatal(e.Start(b.GetString("BindAddress"))) | ||||
| 	}() | ||||
| 	return b | ||||
| } | ||||
| @@ -92,7 +86,7 @@ func (b *Api) handlePostMessage(c echo.Context) error { | ||||
| 	message.Account = b.Account | ||||
| 	message.ID = "" | ||||
| 	message.Timestamp = time.Now() | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", message.Username, "api") | ||||
| 	b.Log.Debugf("Sending message from %s on %s to gateway", message.Username, "api") | ||||
| 	b.Remote <- message | ||||
| 	return c.JSON(http.StatusOK, message) | ||||
| } | ||||
|   | ||||
							
								
								
									
										110
									
								
								bridge/bridge.go
									
									
									
									
									
								
							
							
						
						
									
										110
									
								
								bridge/bridge.go
									
									
									
									
									
								
							| @@ -1,19 +1,7 @@ | ||||
| package bridge | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/api" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/discord" | ||||
| 	"github.com/42wim/matterbridge/bridge/gitter" | ||||
| 	"github.com/42wim/matterbridge/bridge/irc" | ||||
| 	"github.com/42wim/matterbridge/bridge/matrix" | ||||
| 	"github.com/42wim/matterbridge/bridge/mattermost" | ||||
| 	"github.com/42wim/matterbridge/bridge/rocketchat" | ||||
| 	"github.com/42wim/matterbridge/bridge/slack" | ||||
| 	"github.com/42wim/matterbridge/bridge/sshchat" | ||||
| 	"github.com/42wim/matterbridge/bridge/steam" | ||||
| 	"github.com/42wim/matterbridge/bridge/telegram" | ||||
| 	"github.com/42wim/matterbridge/bridge/xmpp" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
|  | ||||
| 	"strings" | ||||
| @@ -27,22 +15,28 @@ type Bridger interface { | ||||
| } | ||||
|  | ||||
| type Bridge struct { | ||||
| 	Config config.Protocol | ||||
| 	Bridger | ||||
| 	Name     string | ||||
| 	Account  string | ||||
| 	Protocol string | ||||
| 	Channels map[string]config.ChannelInfo | ||||
| 	Joined   map[string]bool | ||||
| 	Log      *log.Entry | ||||
| 	Config   *config.Config | ||||
| 	General  *config.Protocol | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": "bridge"}) | ||||
| type Config struct { | ||||
| 	//	General *config.Protocol | ||||
| 	Remote chan config.Message | ||||
| 	Log    *log.Entry | ||||
| 	*Bridge | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge { | ||||
| // Factory is the factory function to create a bridge | ||||
| type Factory func(*Config) Bridger | ||||
|  | ||||
| func New(bridge *config.Bridge) *Bridge { | ||||
| 	b := new(Bridge) | ||||
| 	b.Channels = make(map[string]config.ChannelInfo) | ||||
| 	accInfo := strings.Split(bridge.Account, ".") | ||||
| @@ -52,49 +46,6 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid | ||||
| 	b.Protocol = protocol | ||||
| 	b.Account = bridge.Account | ||||
| 	b.Joined = make(map[string]bool) | ||||
| 	bridgeConfig := &config.BridgeConfig{General: &cfg.General, Account: bridge.Account, Remote: c} | ||||
|  | ||||
| 	// override config from environment | ||||
| 	config.OverrideCfgFromEnv(cfg, protocol, name) | ||||
| 	switch protocol { | ||||
| 	case "mattermost": | ||||
| 		bridgeConfig.Config = cfg.Mattermost[name] | ||||
| 		b.Bridger = bmattermost.New(bridgeConfig) | ||||
| 	case "irc": | ||||
| 		bridgeConfig.Config = cfg.IRC[name] | ||||
| 		b.Bridger = birc.New(bridgeConfig) | ||||
| 	case "gitter": | ||||
| 		bridgeConfig.Config = cfg.Gitter[name] | ||||
| 		b.Bridger = bgitter.New(bridgeConfig) | ||||
| 	case "slack": | ||||
| 		bridgeConfig.Config = cfg.Slack[name] | ||||
| 		b.Bridger = bslack.New(bridgeConfig) | ||||
| 	case "xmpp": | ||||
| 		bridgeConfig.Config = cfg.Xmpp[name] | ||||
| 		b.Bridger = bxmpp.New(bridgeConfig) | ||||
| 	case "discord": | ||||
| 		bridgeConfig.Config = cfg.Discord[name] | ||||
| 		b.Bridger = bdiscord.New(bridgeConfig) | ||||
| 	case "telegram": | ||||
| 		bridgeConfig.Config = cfg.Telegram[name] | ||||
| 		b.Bridger = btelegram.New(bridgeConfig) | ||||
| 	case "rocketchat": | ||||
| 		bridgeConfig.Config = cfg.Rocketchat[name] | ||||
| 		b.Bridger = brocketchat.New(bridgeConfig) | ||||
| 	case "matrix": | ||||
| 		bridgeConfig.Config = cfg.Matrix[name] | ||||
| 		b.Bridger = bmatrix.New(bridgeConfig) | ||||
| 	case "steam": | ||||
| 		bridgeConfig.Config = cfg.Steam[name] | ||||
| 		b.Bridger = bsteam.New(bridgeConfig) | ||||
| 	case "sshchat": | ||||
| 		bridgeConfig.Config = cfg.Sshchat[name] | ||||
| 		b.Bridger = bsshchat.New(bridgeConfig) | ||||
| 	case "api": | ||||
| 		bridgeConfig.Config = cfg.Api[name] | ||||
| 		b.Bridger = api.New(bridgeConfig) | ||||
| 	} | ||||
| 	b.Config = bridgeConfig.Config | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| @@ -106,7 +57,7 @@ func (b *Bridge) JoinChannels() error { | ||||
| func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map[string]bool) error { | ||||
| 	for ID, channel := range channels { | ||||
| 		if !exists[ID] { | ||||
| 			flog.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID) | ||||
| 			b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID) | ||||
| 			err := b.JoinChannel(channel) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| @@ -116,3 +67,38 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bridge) GetBool(key string) bool { | ||||
| 	if b.Config.GetBool(b.Account + "." + key) { | ||||
| 		return b.Config.GetBool(b.Account + "." + key) | ||||
| 	} | ||||
| 	return b.Config.GetBool("general." + key) | ||||
| } | ||||
|  | ||||
| func (b *Bridge) GetInt(key string) int { | ||||
| 	if b.Config.GetInt(b.Account+"."+key) != 0 { | ||||
| 		return b.Config.GetInt(b.Account + "." + key) | ||||
| 	} | ||||
| 	return b.Config.GetInt("general." + key) | ||||
| } | ||||
|  | ||||
| func (b *Bridge) GetString(key string) string { | ||||
| 	if b.Config.GetString(b.Account+"."+key) != "" { | ||||
| 		return b.Config.GetString(b.Account + "." + key) | ||||
| 	} | ||||
| 	return b.Config.GetString("general." + key) | ||||
| } | ||||
|  | ||||
| func (b *Bridge) GetStringSlice(key string) []string { | ||||
| 	if len(b.Config.GetStringSlice(b.Account+"."+key)) != 0 { | ||||
| 		return b.Config.GetStringSlice(b.Account + "." + key) | ||||
| 	} | ||||
| 	return b.Config.GetStringSlice("general." + key) | ||||
| } | ||||
|  | ||||
| func (b *Bridge) GetStringSlice2D(key string) [][]string { | ||||
| 	if len(b.Config.GetStringSlice2D(b.Account+"."+key)) != 0 { | ||||
| 		return b.Config.GetStringSlice2D(b.Account + "." + key) | ||||
| 	} | ||||
| 	return b.Config.GetStringSlice2D("general." + key) | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,16 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"github.com/BurntSushi/toml" | ||||
| 	"log" | ||||
| 	"bytes" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fsnotify/fsnotify" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/spf13/viper" | ||||
| 	prefixed "github.com/x-cray/logrus-prefixed-formatter" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -59,7 +63,9 @@ type Protocol struct { | ||||
| 	BindAddress            string // mattermost, slack // DEPRECATED | ||||
| 	Buffer                 int    // api | ||||
| 	Charset                string // irc | ||||
| 	ColorNicks             bool   // only irc for now | ||||
| 	Debug                  bool   // general | ||||
| 	DebugLevel             int    // only for irc now | ||||
| 	EditSuffix             string // mattermost, slack, discord, telegram, gitter | ||||
| 	EditDisable            bool   // mattermost, slack, discord, telegram, gitter | ||||
| 	IconURL                string // mattermost, slack | ||||
| @@ -68,6 +74,8 @@ type Protocol struct { | ||||
| 	Jid                    string // xmpp | ||||
| 	Label                  string // all protocols | ||||
| 	Login                  string // mattermost, matrix | ||||
| 	MediaDownloadBlackList []string | ||||
| 	MediaDownloadPath      string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server. | ||||
| 	MediaDownloadSize      int    // all protocols | ||||
| 	MediaServerDownload    string | ||||
| 	MediaServerUpload      string | ||||
| @@ -85,10 +93,13 @@ type Protocol struct { | ||||
| 	NickServPassword       string     // IRC | ||||
| 	NicksPerRow            int        // mattermost, slack | ||||
| 	NoHomeServerSuffix     bool       // matrix | ||||
| 	NoSendJoinPart         bool       // all protocols | ||||
| 	NoTLS                  bool       // mattermost | ||||
| 	Password               string     // IRC,mattermost,XMPP,matrix | ||||
| 	PrefixMessagesWithNick bool       // mattemost, slack | ||||
| 	Protocol               string     // all protocols | ||||
| 	QuoteDisable           bool       // telegram | ||||
| 	QuoteFormat            string     // telegram | ||||
| 	RejoinDelay            int        // IRC | ||||
| 	ReplaceMessages        [][]string // all protocols | ||||
| 	ReplaceNicks           [][]string // all protocols | ||||
| @@ -101,6 +112,7 @@ type Protocol struct { | ||||
| 	StripNick              bool       // all protocols | ||||
| 	Team                   string     // mattermost | ||||
| 	Token                  string     // gitter, slack, discord, api | ||||
| 	Topic                  string     // zulip | ||||
| 	URL                    string     // mattermost, slack // DEPRECATED | ||||
| 	UseAPI                 bool       // mattermost, slack | ||||
| 	UseSASL                bool       // IRC | ||||
| @@ -114,7 +126,7 @@ type Protocol struct { | ||||
| } | ||||
|  | ||||
| type ChannelOptions struct { | ||||
| 	Key        string // irc | ||||
| 	Key        string // irc, xmpp | ||||
| 	WebhookURL string // discord | ||||
| } | ||||
|  | ||||
| @@ -140,9 +152,9 @@ type SameChannelGateway struct { | ||||
| 	Accounts []string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| type ConfigValues struct { | ||||
| 	Api                map[string]Protocol | ||||
| 	IRC                map[string]Protocol | ||||
| 	Irc                map[string]Protocol | ||||
| 	Mattermost         map[string]Protocol | ||||
| 	Matrix             map[string]Protocol | ||||
| 	Slack              map[string]Protocol | ||||
| @@ -153,90 +165,117 @@ type Config struct { | ||||
| 	Telegram           map[string]Protocol | ||||
| 	Rocketchat         map[string]Protocol | ||||
| 	Sshchat            map[string]Protocol | ||||
| 	Zulip              map[string]Protocol | ||||
| 	General            Protocol | ||||
| 	Gateway            []Gateway | ||||
| 	SameChannelGateway []SameChannelGateway | ||||
| } | ||||
|  | ||||
| type BridgeConfig struct { | ||||
| 	Config  Protocol | ||||
| 	General *Protocol | ||||
| 	Account string | ||||
| 	Remote  chan Message | ||||
| type Config struct { | ||||
| 	v *viper.Viper | ||||
| 	*ConfigValues | ||||
| 	sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewConfig(cfgfile string) *Config { | ||||
| 	var cfg Config | ||||
| 	if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil { | ||||
| 	log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false}) | ||||
| 	flog := log.WithFields(log.Fields{"prefix": "config"}) | ||||
| 	var cfg ConfigValues | ||||
| 	viper.SetConfigType("toml") | ||||
| 	viper.SetConfigFile(cfgfile) | ||||
| 	viper.SetEnvPrefix("matterbridge") | ||||
| 	viper.AddConfigPath(".") | ||||
| 	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) | ||||
| 	viper.AutomaticEnv() | ||||
| 	f, err := os.Open(cfgfile) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	fail := false | ||||
| 	for k, v := range cfg.Mattermost { | ||||
| 		res := Deprecated(v, "mattermost."+k) | ||||
| 		if res { | ||||
| 			fail = res | ||||
| 		} | ||||
| 	err = viper.ReadConfig(f) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	for k, v := range cfg.Slack { | ||||
| 		res := Deprecated(v, "slack."+k) | ||||
| 		if res { | ||||
| 			fail = res | ||||
| 		} | ||||
| 	} | ||||
| 	for k, v := range cfg.Rocketchat { | ||||
| 		res := Deprecated(v, "rocketchat."+k) | ||||
| 		if res { | ||||
| 			fail = res | ||||
| 		} | ||||
| 	} | ||||
| 	if fail { | ||||
| 		log.Fatalf("Fix your config. Please see changelog for more information") | ||||
| 	err = viper.Unmarshal(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("blah", err) | ||||
| 	} | ||||
| 	mycfg := new(Config) | ||||
| 	mycfg.v = viper.GetViper() | ||||
| 	if cfg.General.MediaDownloadSize == 0 { | ||||
| 		cfg.General.MediaDownloadSize = 1000000 | ||||
| 	} | ||||
| 	return &cfg | ||||
| 	viper.WatchConfig() | ||||
| 	viper.OnConfigChange(func(e fsnotify.Event) { | ||||
| 		flog.Println("Config file changed:", e.Name) | ||||
| 	}) | ||||
|  | ||||
| 	mycfg.ConfigValues = &cfg | ||||
| 	return mycfg | ||||
| } | ||||
|  | ||||
| func OverrideCfgFromEnv(cfg *Config, protocol string, account string) { | ||||
| 	var protoCfg Protocol | ||||
| 	val := reflect.ValueOf(cfg).Elem() | ||||
| 	// loop over the Config struct | ||||
| 	for i := 0; i < val.NumField(); i++ { | ||||
| 		typeField := val.Type().Field(i) | ||||
| 		// look for the protocol map (both lowercase) | ||||
| 		if strings.ToLower(typeField.Name) == protocol { | ||||
| 			// get the Protocol struct from the map | ||||
| 			data := val.Field(i).MapIndex(reflect.ValueOf(account)) | ||||
| 			protoCfg = data.Interface().(Protocol) | ||||
| 			protoStruct := reflect.ValueOf(&protoCfg).Elem() | ||||
| 			// loop over the found protocol struct | ||||
| 			for i := 0; i < protoStruct.NumField(); i++ { | ||||
| 				typeField := protoStruct.Type().Field(i) | ||||
| 				// build our environment key (eg MATTERBRIDGE_MATTERMOST_WORK_LOGIN) | ||||
| 				key := "matterbridge_" + protocol + "_" + account + "_" + typeField.Name | ||||
| 				key = strings.ToUpper(key) | ||||
| 				// search the environment | ||||
| 				res := os.Getenv(key) | ||||
| 				// if it exists and the current field is a string | ||||
| 				// then update the current field | ||||
| 				if res != "" { | ||||
| 					fieldVal := protoStruct.Field(i) | ||||
| 					if fieldVal.Kind() == reflect.String { | ||||
| 						log.Printf("config: overriding %s from env with %s\n", key, res) | ||||
| 						fieldVal.Set(reflect.ValueOf(res)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			// update the map with the modified Protocol (cfg.Protocol[account] = Protocol) | ||||
| 			val.Field(i).SetMapIndex(reflect.ValueOf(account), reflect.ValueOf(protoCfg)) | ||||
| 			break | ||||
| 		} | ||||
| func NewConfigFromString(input []byte) *Config { | ||||
| 	var cfg ConfigValues | ||||
| 	viper.SetConfigType("toml") | ||||
| 	err := viper.ReadConfig(bytes.NewBuffer(input)) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	err = viper.Unmarshal(&cfg) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	mycfg := new(Config) | ||||
| 	mycfg.v = viper.GetViper() | ||||
| 	mycfg.ConfigValues = &cfg | ||||
| 	return mycfg | ||||
| } | ||||
|  | ||||
| func GetIconURL(msg *Message, cfg *Protocol) string { | ||||
| 	iconURL := cfg.IconURL | ||||
| func (c *Config) GetBool(key string) bool { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	//	log.Debugf("getting bool %s = %#v", key, c.v.GetBool(key)) | ||||
| 	return c.v.GetBool(key) | ||||
| } | ||||
|  | ||||
| func (c *Config) GetInt(key string) int { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	//	log.Debugf("getting int %s = %d", key, c.v.GetInt(key)) | ||||
| 	return c.v.GetInt(key) | ||||
| } | ||||
|  | ||||
| func (c *Config) GetString(key string) string { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	//	log.Debugf("getting String %s = %s", key, c.v.GetString(key)) | ||||
| 	return c.v.GetString(key) | ||||
| } | ||||
|  | ||||
| func (c *Config) GetStringSlice(key string) []string { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	// log.Debugf("getting StringSlice %s = %#v", key, c.v.GetStringSlice(key)) | ||||
| 	return c.v.GetStringSlice(key) | ||||
| } | ||||
|  | ||||
| func (c *Config) GetStringSlice2D(key string) [][]string { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
| 	result := [][]string{} | ||||
| 	if res, ok := c.v.Get(key).([]interface{}); ok { | ||||
| 		for _, entry := range res { | ||||
| 			result2 := []string{} | ||||
| 			for _, entry2 := range entry.([]interface{}) { | ||||
| 				result2 = append(result2, entry2.(string)) | ||||
| 			} | ||||
| 			result = append(result, result2) | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func GetIconURL(msg *Message, iconURL string) string { | ||||
| 	info := strings.Split(msg.Account, ".") | ||||
| 	protocol := info[0] | ||||
| 	name := info[1] | ||||
| @@ -245,17 +284,3 @@ func GetIconURL(msg *Message, cfg *Protocol) string { | ||||
| 	iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1) | ||||
| 	return iconURL | ||||
| } | ||||
|  | ||||
| func Deprecated(cfg Protocol, account string) bool { | ||||
| 	if cfg.BindAddress != "" { | ||||
| 		log.Printf("ERROR: %s BindAddress is deprecated, you need to change it to WebhookBindAddress.", account) | ||||
| 	} else if cfg.URL != "" { | ||||
| 		log.Printf("ERROR: %s URL is deprecated, you need to change it to WebhookURL.", account) | ||||
| 	} else if cfg.UseAPI { | ||||
| 		log.Printf("ERROR: %s UseAPI is deprecated, it's enabled by default, please remove it from your config file.", account) | ||||
| 	} else { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| 	//log.Fatalf("ERROR: Fix your config: %s", account) | ||||
| } | ||||
|   | ||||
| @@ -2,16 +2,18 @@ package bdiscord | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| type bdiscord struct { | ||||
| type Bdiscord struct { | ||||
| 	c              *discordgo.Session | ||||
| 	Channels       []*discordgo.Channel | ||||
| 	Nick           string | ||||
| @@ -22,82 +24,74 @@ type bdiscord struct { | ||||
| 	webhookToken   string | ||||
| 	channelInfoMap map[string]*config.ChannelInfo | ||||
| 	sync.RWMutex | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "discord" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *bdiscord { | ||||
| 	b := &bdiscord{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bdiscord{Config: cfg} | ||||
| 	b.userMemberMap = make(map[string]*discordgo.Member) | ||||
| 	b.channelInfoMap = make(map[string]*config.ChannelInfo) | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		flog.Debug("Configuring Discord Incoming Webhook") | ||||
| 		b.webhookID, b.webhookToken = b.splitURL(b.Config.WebhookURL) | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		b.Log.Debug("Configuring Discord Incoming Webhook") | ||||
| 		b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL")) | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Connect() error { | ||||
| func (b *Bdiscord) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	if b.Config.WebhookURL == "" { | ||||
| 		flog.Info("Connecting using token") | ||||
| 	var token string | ||||
| 	b.Log.Info("Connecting") | ||||
| 	if b.GetString("WebhookURL") == "" { | ||||
| 		b.Log.Info("Connecting using token") | ||||
| 	} else { | ||||
| 		flog.Info("Connecting using webhookurl (for posting) and token") | ||||
| 		b.Log.Info("Connecting using webhookurl (for posting) and token") | ||||
| 	} | ||||
| 	if !strings.HasPrefix(b.Config.Token, "Bot ") { | ||||
| 		b.Config.Token = "Bot " + b.Config.Token | ||||
| 	if !strings.HasPrefix(b.GetString("Token"), "Bot ") { | ||||
| 		token = "Bot " + b.GetString("Token") | ||||
| 	} | ||||
| 	b.c, err = discordgo.New(b.Config.Token) | ||||
| 	b.c, err = discordgo.New(token) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	b.c.AddHandler(b.messageCreate) | ||||
| 	b.c.AddHandler(b.memberUpdate) | ||||
| 	b.c.AddHandler(b.messageUpdate) | ||||
| 	b.c.AddHandler(b.messageDelete) | ||||
| 	err = b.c.Open() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	guilds, err := b.c.UserGuilds(100, "", "") | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	userinfo, err := b.c.User("@me") | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	b.Nick = userinfo.Username | ||||
| 	for _, guild := range guilds { | ||||
| 		if guild.Name == b.Config.Server { | ||||
| 		if guild.Name == b.GetString("Server") { | ||||
| 			b.Channels, err = b.c.GuildChannels(guild.ID) | ||||
| 			b.guildID = guild.ID | ||||
| 			if err != nil { | ||||
| 				flog.Debugf("%#v", err) | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for _, channel := range b.Channels { | ||||
| 		b.Log.Debugf("found channel %#v", channel) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Disconnect() error { | ||||
| 	return nil | ||||
| func (b *Bdiscord) Disconnect() error { | ||||
| 	return b.c.Close() | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||
| func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	b.channelInfoMap[channel.ID] = &channel | ||||
| 	idcheck := strings.Split(channel.Name, "ID:") | ||||
| 	if len(idcheck) > 1 { | ||||
| @@ -106,103 +100,117 @@ func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| func (b *Bdiscord) Send(msg config.Message) (string, error) { | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	channelID := b.getChannelID(msg.Channel) | ||||
| 	if channelID == "" { | ||||
| 		flog.Errorf("Could not find channelID for %v", msg.Channel) | ||||
| 		return "", nil | ||||
| 		return "", fmt.Errorf("Could not find channelID for %v", msg.Channel) | ||||
| 	} | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		msg.Text = "_" + msg.Text + "_" | ||||
| 	} | ||||
|  | ||||
| 	// use initial webhook | ||||
| 	wID := b.webhookID | ||||
| 	wToken := b.webhookToken | ||||
|  | ||||
| 	// check if have a channel specific webhook | ||||
| 	if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok { | ||||
| 		if ci.Options.WebhookURL != "" { | ||||
| 			wID, wToken = b.splitURL(ci.Options.WebhookURL) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if wID == "" { | ||||
| 		flog.Debugf("Broadcasting using token (API)") | ||||
| 		if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 			if msg.ID == "" { | ||||
| 				return "", nil | ||||
| 			} | ||||
| 			err := b.c.ChannelMessageDelete(channelID, msg.ID) | ||||
| 			return "", err | ||||
| 	// Use webhook to send the message | ||||
| 	if wID != "" { | ||||
| 		// skip events | ||||
| 		if msg.Event != "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		if msg.ID != "" { | ||||
| 			_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text) | ||||
| 			return msg.ID, err | ||||
| 		} | ||||
|  | ||||
| 		if msg.Extra != nil { | ||||
| 			for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 				b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text) | ||||
| 			} | ||||
| 			// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 			if len(msg.Extra["file"]) > 0 { | ||||
| 				var err error | ||||
| 				for _, f := range msg.Extra["file"] { | ||||
| 					fi := f.(config.FileInfo) | ||||
| 					files := []*discordgo.File{} | ||||
| 					files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)}) | ||||
| 					_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files}) | ||||
| 					if err != nil { | ||||
| 						flog.Errorf("file upload failed: %#v", err) | ||||
| 					} | ||||
| 				} | ||||
| 				return "", nil | ||||
| 		b.Log.Debugf("Broadcasting using Webhook") | ||||
| 		for _, f := range msg.Extra["file"] { | ||||
| 			fi := f.(config.FileInfo) | ||||
| 			if fi.URL != "" { | ||||
| 				msg.Text += fi.URL + " " | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return res.ID, err | ||||
| 		err := b.c.WebhookExecute( | ||||
| 			wID, | ||||
| 			wToken, | ||||
| 			true, | ||||
| 			&discordgo.WebhookParams{ | ||||
| 				Content:   msg.Text, | ||||
| 				Username:  msg.Username, | ||||
| 				AvatarURL: msg.Avatar, | ||||
| 			}) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	flog.Debugf("Broadcasting using Webhook") | ||||
| 	err := b.c.WebhookExecute( | ||||
| 		wID, | ||||
| 		wToken, | ||||
| 		true, | ||||
| 		&discordgo.WebhookParams{ | ||||
| 			Content:   msg.Text, | ||||
| 			Username:  msg.Username, | ||||
| 			AvatarURL: msg.Avatar, | ||||
| 		}) | ||||
| 	return "", err | ||||
|  | ||||
| 	b.Log.Debugf("Broadcasting using token (API)") | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		err := b.c.ChannelMessageDelete(channelID, msg.ID) | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			return b.handleUploadFile(&msg, channelID) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Edit message | ||||
| 	if msg.ID != "" { | ||||
| 		_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text) | ||||
| 		return msg.ID, err | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return res.ID, err | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { | ||||
| func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { | ||||
| 	rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EVENT_MSG_DELETE, Text: config.EVENT_MSG_DELETE} | ||||
| 	rmsg.Channel = b.getChannelName(m.ChannelID) | ||||
| 	if b.UseChannelID { | ||||
| 		rmsg.Channel = "ID:" + m.ChannelID | ||||
| 	} | ||||
| 	flog.Debugf("Sending message from %s to gateway", b.Account) | ||||
| 	flog.Debugf("Message is %#v", rmsg) | ||||
| 	b.Log.Debugf("<= Sending message from %s to gateway", b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { | ||||
| 	if b.Config.EditDisable { | ||||
| func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { | ||||
| 	if b.GetBool("EditDisable") { | ||||
| 		return | ||||
| 	} | ||||
| 	// only when message is actually edited | ||||
| 	if m.Message.EditedTimestamp != "" { | ||||
| 		flog.Debugf("Sending edit message") | ||||
| 		m.Content = m.Content + b.Config.EditSuffix | ||||
| 		b.Log.Debugf("Sending edit message") | ||||
| 		m.Content = m.Content + b.GetString("EditSuffix") | ||||
| 		b.messageCreate(s, (*discordgo.MessageCreate)(m)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	var err error | ||||
|  | ||||
| 	// not relay our own messages | ||||
| 	if m.Author.Username == b.Nick { | ||||
| 		return | ||||
| @@ -212,70 +220,73 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// add the url of the attachments to content | ||||
| 	if len(m.Attachments) > 0 { | ||||
| 		for _, attach := range m.Attachments { | ||||
| 			m.Content = m.Content + "\n" + attach.URL | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var text string | ||||
| 	rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID} | ||||
|  | ||||
| 	if m.Content != "" { | ||||
| 		flog.Debugf("Receiving message %#v", m.Message) | ||||
| 		b.Log.Debugf("== Receiving event %#v", m.Message) | ||||
| 		m.Message.Content = b.stripCustomoji(m.Message.Content) | ||||
| 		m.Message.Content = b.replaceChannelMentions(m.Message.Content) | ||||
| 		text, err = m.ContentWithMoreMentionsReplaced(b.c) | ||||
| 		rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("ContentWithMoreMentionsReplaced failed: %s", err) | ||||
| 			text = m.ContentWithMentionsReplaced() | ||||
| 			b.Log.Errorf("ContentWithMoreMentionsReplaced failed: %s", err) | ||||
| 			rmsg.Text = m.ContentWithMentionsReplaced() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", | ||||
| 		UserID: m.Author.ID, ID: m.ID} | ||||
|  | ||||
| 	// set channel name | ||||
| 	rmsg.Channel = b.getChannelName(m.ChannelID) | ||||
| 	if b.UseChannelID { | ||||
| 		rmsg.Channel = "ID:" + m.ChannelID | ||||
| 	} | ||||
|  | ||||
| 	if !b.Config.UseUserName { | ||||
| 	// set username | ||||
| 	if !b.GetBool("UseUserName") { | ||||
| 		rmsg.Username = b.getNick(m.Author) | ||||
| 	} else { | ||||
| 		rmsg.Username = m.Author.Username | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.ShowEmbeds && m.Message.Embeds != nil { | ||||
| 	// if we have embedded content add it to text | ||||
| 	if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil { | ||||
| 		for _, embed := range m.Message.Embeds { | ||||
| 			text = text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n" | ||||
| 			rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// no empty messages | ||||
| 	if text == "" { | ||||
| 	if rmsg.Text == "" { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	text, ok := b.replaceAction(text) | ||||
| 	// do we have a /me action | ||||
| 	var ok bool | ||||
| 	rmsg.Text, ok = b.replaceAction(rmsg.Text) | ||||
| 	if ok { | ||||
| 		rmsg.Event = config.EVENT_USER_ACTION | ||||
| 	} | ||||
|  | ||||
| 	rmsg.Text = text | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account) | ||||
| 	flog.Debugf("Message is %#v", rmsg) | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account) | ||||
| 	b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { | ||||
| func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { | ||||
| 	b.Lock() | ||||
| 	if _, ok := b.userMemberMap[m.Member.User.ID]; ok { | ||||
| 		flog.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick) | ||||
| 		b.Log.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick) | ||||
| 	} | ||||
| 	b.userMemberMap[m.Member.User.ID] = m.Member | ||||
| 	b.Unlock() | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getNick(user *discordgo.User) string { | ||||
| func (b *Bdiscord) getNick(user *discordgo.User) string { | ||||
| 	var err error | ||||
| 	b.Lock() | ||||
| 	defer b.Unlock() | ||||
| @@ -302,7 +313,7 @@ func (b *bdiscord) getNick(user *discordgo.User) string { | ||||
| 	return user.Username | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getChannelID(name string) string { | ||||
| func (b *Bdiscord) getChannelID(name string) string { | ||||
| 	idcheck := strings.Split(name, "ID:") | ||||
| 	if len(idcheck) > 1 { | ||||
| 		return idcheck[1] | ||||
| @@ -315,7 +326,7 @@ func (b *bdiscord) getChannelID(name string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getChannelName(id string) string { | ||||
| func (b *Bdiscord) getChannelName(id string) string { | ||||
| 	for _, channel := range b.Channels { | ||||
| 		if channel.ID == id { | ||||
| 			return channel.Name | ||||
| @@ -324,7 +335,7 @@ func (b *bdiscord) getChannelName(id string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) replaceChannelMentions(text string) string { | ||||
| func (b *Bdiscord) replaceChannelMentions(text string) string { | ||||
| 	var err error | ||||
| 	re := regexp.MustCompile("<#[0-9]+>") | ||||
| 	text = re.ReplaceAllStringFunc(text, func(m string) string { | ||||
| @@ -343,31 +354,31 @@ func (b *bdiscord) replaceChannelMentions(text string) string { | ||||
| 	return text | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) replaceAction(text string) (string, bool) { | ||||
| func (b *Bdiscord) replaceAction(text string) (string, bool) { | ||||
| 	if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") { | ||||
| 		return strings.Replace(text, "_", "", -1), true | ||||
| 	} | ||||
| 	return text, false | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) stripCustomoji(text string) string { | ||||
| func (b *Bdiscord) stripCustomoji(text string) string { | ||||
| 	// <:doge:302803592035958784> | ||||
| 	re := regexp.MustCompile("<(:.*?:)[0-9]+>") | ||||
| 	return re.ReplaceAllString(text, `$1`) | ||||
| } | ||||
|  | ||||
| // splitURL splits a webhookURL and returns the id and token | ||||
| func (b *bdiscord) splitURL(url string) (string, string) { | ||||
| func (b *Bdiscord) splitURL(url string) (string, string) { | ||||
| 	webhookURLSplit := strings.Split(url, "/") | ||||
| 	if len(webhookURLSplit) != 7 { | ||||
| 		log.Fatalf("%s is no correct discord WebhookURL", url) | ||||
| 		b.Log.Fatalf("%s is no correct discord WebhookURL", url) | ||||
| 	} | ||||
| 	return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1] | ||||
| } | ||||
|  | ||||
| // useWebhook returns true if we have a webhook defined somewhere | ||||
| func (b *bdiscord) useWebhook() bool { | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| func (b *Bdiscord) useWebhook() bool { | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		return true | ||||
| 	} | ||||
| 	for _, channel := range b.channelInfoMap { | ||||
| @@ -379,9 +390,9 @@ func (b *bdiscord) useWebhook() bool { | ||||
| } | ||||
|  | ||||
| // isWebhookID returns true if the specified id is used in a defined webhook | ||||
| func (b *bdiscord) isWebhookID(id string) bool { | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		wID, _ := b.splitURL(b.Config.WebhookURL) | ||||
| func (b *Bdiscord) isWebhookID(id string) bool { | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		wID, _ := b.splitURL(b.GetString("WebhookURL")) | ||||
| 		if wID == id { | ||||
| 			return true | ||||
| 		} | ||||
| @@ -396,3 +407,18 @@ func (b *bdiscord) isWebhookID(id string) bool { | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) { | ||||
| 	var err error | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		files := []*discordgo.File{} | ||||
| 		files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)}) | ||||
| 		_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files}) | ||||
| 		if err != nil { | ||||
| 			return "", fmt.Errorf("file upload failed: %#v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|   | ||||
| @@ -2,11 +2,12 @@ package bgitter | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/go-gitter" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Bgitter struct { | ||||
| @@ -14,31 +15,26 @@ type Bgitter struct { | ||||
| 	User  *gitter.User | ||||
| 	Users []gitter.User | ||||
| 	Rooms []gitter.Room | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "gitter" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bgitter { | ||||
| 	return &Bgitter{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bgitter{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	b.c = gitter.New(b.Config.Token) | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c = gitter.New(b.GetString("Token")) | ||||
| 	b.User, err = b.c.GetUser() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Rooms, _ = b.c.GetRooms() | ||||
| 	b.Rooms, err = b.c.GetRooms() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -74,8 +70,9 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||
| 		for event := range stream.Event { | ||||
| 			switch ev := event.Data.(type) { | ||||
| 			case *gitter.MessageReceived: | ||||
| 				// ignore message sent from ourselves | ||||
| 				if ev.Message.From.ID != b.User.ID { | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account) | ||||
| 					b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account) | ||||
| 					rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, | ||||
| 						Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID, | ||||
| 						ID: ev.Message.ID} | ||||
| @@ -83,11 +80,11 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||
| 						rmsg.Event = config.EVENT_USER_ACTION | ||||
| 						rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1) | ||||
| 					} | ||||
| 					flog.Debugf("Message is %#v", rmsg) | ||||
| 					b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 					b.Remote <- rmsg | ||||
| 				} | ||||
| 			case *gitter.GitterConnectionClosed: | ||||
| 				flog.Errorf("connection with gitter closed for room %s", room) | ||||
| 				b.Log.Errorf("connection with gitter closed for room %s", room) | ||||
| 			} | ||||
| 		} | ||||
| 	}(stream, room.URI) | ||||
| @@ -95,25 +92,39 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
| 	roomID := b.getRoomID(msg.Channel) | ||||
| 	if roomID == "" { | ||||
| 		flog.Errorf("Could not find roomID for %v", msg.Channel) | ||||
| 		b.Log.Errorf("Could not find roomID for %v", msg.Channel) | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		// gitter has no delete message api | ||||
| 		// gitter has no delete message api so we edit message to "" | ||||
| 		_, err := b.c.UpdateMessage(roomID, msg.ID, "") | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file (in gitter case send the upload URL because gitter has no native upload support) | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.c.SendMessage(roomID, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			return b.handleUploadFile(&msg, roomID) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Edit message | ||||
| 	if msg.ID != "" { | ||||
| 		flog.Debugf("updating message with id %s", msg.ID) | ||||
| 		b.Log.Debugf("updating message with id %s", msg.ID) | ||||
| 		_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| @@ -121,28 +132,7 @@ func (b *Bgitter) Send(msg config.Message) (string, error) { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.c.SendMessage(roomID, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.Comment != "" { | ||||
| 					msg.Text += fi.Comment + ": " | ||||
| 				} | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text = fi.URL | ||||
| 				} | ||||
| 				_, err := b.c.SendMessage(roomID, msg.Username+msg.Text) | ||||
| 				if err != nil { | ||||
| 					return "", err | ||||
| 				} | ||||
| 			} | ||||
| 			return "", nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| @@ -170,3 +160,23 @@ func (b *Bgitter) getAvatar(user string) string { | ||||
| 	} | ||||
| 	return avatar | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) handleUploadFile(msg *config.Message, roomID string) (string, error) { | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		if fi.Comment != "" { | ||||
| 			msg.Text += fi.Comment + ": " | ||||
| 		} | ||||
| 		if fi.URL != "" { | ||||
| 			msg.Text = fi.URL | ||||
| 			if fi.Comment != "" { | ||||
| 				msg.Text = fi.Comment + ": " + fi.URL | ||||
| 			} | ||||
| 		} | ||||
| 		_, err := b.c.SendMessage(roomID, msg.Username+msg.Text) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|   | ||||
| @@ -3,18 +3,29 @@ package helper | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| func DownloadFile(url string) (*[]byte, error) { | ||||
| 	return DownloadFileAuth(url, "") | ||||
| } | ||||
|  | ||||
| func DownloadFileAuth(url string, auth string) (*[]byte, error) { | ||||
| 	var buf bytes.Buffer | ||||
| 	client := &http.Client{ | ||||
| 		Timeout: time.Second * 5, | ||||
| 	} | ||||
| 	req, err := http.NewRequest("GET", url, nil) | ||||
| 	if auth != "" { | ||||
| 		req.Header.Add("Authorization", auth) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -61,3 +72,46 @@ func GetAvatar(av map[string]string, userid string, general *config.Protocol) st | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func HandleDownloadSize(flog *log.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error { | ||||
| 	// check blacklist here | ||||
| 	for _, entry := range general.MediaDownloadBlackList { | ||||
| 		if entry != "" { | ||||
| 			re, err := regexp.Compile(entry) | ||||
| 			if err != nil { | ||||
| 				flog.Errorf("incorrect regexp %s for %s", entry, msg.Account) | ||||
| 				continue | ||||
| 			} | ||||
| 			if re.MatchString(name) { | ||||
| 				return fmt.Errorf("Matching blacklist %s. Not downloading %s", entry, name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	flog.Debugf("Trying to download %#v with size %#v", name, size) | ||||
| 	if int(size) > general.MediaDownloadSize { | ||||
| 		msg.Event = config.EVENT_FILE_FAILURE_SIZE | ||||
| 		msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: name, Comment: msg.Text, Size: size}) | ||||
| 		return fmt.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, general.MediaDownloadSize) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func HandleDownloadData(flog *log.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) { | ||||
| 	var avatar bool | ||||
| 	flog.Debugf("Download OK %#v %#v", name, len(*data)) | ||||
| 	if msg.Event == config.EVENT_AVATAR_DOWNLOAD { | ||||
| 		avatar = true | ||||
| 	} | ||||
| 	msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, URL: url, Comment: comment, Avatar: avatar}) | ||||
| } | ||||
|  | ||||
| func RemoveEmptyNewLines(msg string) string { | ||||
| 	lines := "" | ||||
| 	for _, line := range strings.Split(msg, "\n") { | ||||
| 		if line != "" { | ||||
| 			lines += line + "\n" | ||||
| 		} | ||||
| 	} | ||||
| 	lines = strings.TrimRight(lines, "\n") | ||||
| 	return lines | ||||
| } | ||||
|   | ||||
| @@ -4,13 +4,7 @@ import ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/lrstanley/girc" | ||||
| 	"github.com/paulrosania/go-charset/charset" | ||||
| 	_ "github.com/paulrosania/go-charset/data" | ||||
| 	"github.com/saintfish/chardet" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"hash/crc32" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| @@ -20,40 +14,49 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/dfordsoft/golib/ic" | ||||
| 	"github.com/lrstanley/girc" | ||||
| 	"github.com/paulrosania/go-charset/charset" | ||||
| 	_ "github.com/paulrosania/go-charset/data" | ||||
| 	"github.com/saintfish/chardet" | ||||
| ) | ||||
|  | ||||
| type Birc struct { | ||||
| 	i               *girc.Client | ||||
| 	Nick            string | ||||
| 	names           map[string][]string | ||||
| 	connected       chan struct{} | ||||
| 	Local           chan config.Message // local queue for flood control | ||||
| 	FirstConnection bool | ||||
| 	i                                         *girc.Client | ||||
| 	Nick                                      string | ||||
| 	names                                     map[string][]string | ||||
| 	connected                                 chan struct{} | ||||
| 	Local                                     chan config.Message // local queue for flood control | ||||
| 	FirstConnection                           bool | ||||
| 	MessageDelay, MessageQueue, MessageLength int | ||||
|  | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "irc" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Birc { | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Birc{} | ||||
| 	b.BridgeConfig = cfg | ||||
| 	b.Nick = b.Config.Nick | ||||
| 	b.Config = cfg | ||||
| 	b.Nick = b.GetString("Nick") | ||||
| 	b.names = make(map[string][]string) | ||||
| 	b.connected = make(chan struct{}) | ||||
| 	if b.Config.MessageDelay == 0 { | ||||
| 		b.Config.MessageDelay = 1300 | ||||
| 	if b.GetInt("MessageDelay") == 0 { | ||||
| 		b.MessageDelay = 1300 | ||||
| 	} else { | ||||
| 		b.MessageDelay = b.GetInt("MessageDelay") | ||||
| 	} | ||||
| 	if b.Config.MessageQueue == 0 { | ||||
| 		b.Config.MessageQueue = 30 | ||||
| 	if b.GetInt("MessageQueue") == 0 { | ||||
| 		b.MessageQueue = 30 | ||||
| 	} else { | ||||
| 		b.MessageQueue = b.GetInt("MessageQueue") | ||||
| 	} | ||||
| 	if b.Config.MessageLength == 0 { | ||||
| 		b.Config.MessageLength = 400 | ||||
| 	if b.GetInt("MessageLength") == 0 { | ||||
| 		b.MessageLength = 400 | ||||
| 	} else { | ||||
| 		b.MessageLength = b.GetInt("MessageLength") | ||||
| 	} | ||||
| 	b.FirstConnection = true | ||||
| 	return b | ||||
| @@ -70,9 +73,9 @@ func (b *Birc) Command(msg *config.Message) string { | ||||
| } | ||||
|  | ||||
| func (b *Birc) Connect() error { | ||||
| 	b.Local = make(chan config.Message, b.Config.MessageQueue+10) | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	server, portstr, err := net.SplitHostPort(b.Config.Server) | ||||
| 	b.Local = make(chan config.Message, b.MessageQueue+10) | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	server, portstr, err := net.SplitHostPort(b.GetString("Server")) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -81,7 +84,7 @@ func (b *Birc) Connect() error { | ||||
| 		return err | ||||
| 	} | ||||
| 	// fix strict user handling of girc | ||||
| 	user := b.Config.Nick | ||||
| 	user := b.GetString("Nick") | ||||
| 	for !girc.IsValidUser(user) { | ||||
| 		if len(user) == 1 { | ||||
| 			user = "matterbridge" | ||||
| @@ -92,62 +95,65 @@ func (b *Birc) Connect() error { | ||||
|  | ||||
| 	i := girc.New(girc.Config{ | ||||
| 		Server:     server, | ||||
| 		ServerPass: b.Config.Password, | ||||
| 		ServerPass: b.GetString("Password"), | ||||
| 		Port:       port, | ||||
| 		Nick:       b.Config.Nick, | ||||
| 		Nick:       b.GetString("Nick"), | ||||
| 		User:       user, | ||||
| 		Name:       b.Config.Nick, | ||||
| 		SSL:        b.Config.UseTLS, | ||||
| 		TLSConfig:  &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, ServerName: server}, | ||||
| 		Name:       b.GetString("Nick"), | ||||
| 		SSL:        b.GetBool("UseTLS"), | ||||
| 		TLSConfig:  &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server}, | ||||
| 		PingDelay:  time.Minute, | ||||
| 	}) | ||||
|  | ||||
| 	if b.Config.UseSASL { | ||||
| 		i.Config.SASL = &girc.SASLPlain{b.Config.NickServNick, b.Config.NickServPassword} | ||||
| 	if b.GetBool("UseSASL") { | ||||
| 		i.Config.SASL = &girc.SASLPlain{b.GetString("NickServNick"), b.GetString("NickServPassword")} | ||||
| 	} | ||||
|  | ||||
| 	i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection) | ||||
| 	i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth) | ||||
| 	i.Handlers.Add("*", b.handleOther) | ||||
| 	i.Handlers.Add(girc.ALL_EVENTS, b.handleOther) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if err := i.Connect(); err != nil { | ||||
| 				flog.Errorf("error: %s", err) | ||||
| 				flog.Info("reconnecting in 30 seconds...") | ||||
| 				time.Sleep(30 * time.Second) | ||||
| 				i.Handlers.Clear(girc.RPL_WELCOME) | ||||
| 				i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) { | ||||
| 					b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||
| 					// set our correct nick on reconnect if necessary | ||||
| 					b.Nick = event.Source.Name | ||||
| 				}) | ||||
| 				b.Log.Errorf("disconnect: error: %s", err) | ||||
| 			} else { | ||||
| 				return | ||||
| 				b.Log.Info("disconnect: client requested quit") | ||||
| 			} | ||||
|  | ||||
| 			b.Log.Info("reconnecting in 30 seconds...") | ||||
| 			time.Sleep(30 * time.Second) | ||||
| 			i.Handlers.Clear(girc.RPL_WELCOME) | ||||
| 			i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) { | ||||
| 				b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||
| 				// set our correct nick on reconnect if necessary | ||||
| 				b.Nick = event.Source.Name | ||||
| 			}) | ||||
| 		} | ||||
| 	}() | ||||
| 	b.i = i | ||||
| 	select { | ||||
| 	case <-b.connected: | ||||
| 		flog.Info("Connection succeeded") | ||||
| 		b.Log.Info("Connection succeeded") | ||||
| 	case <-time.After(time.Second * 30): | ||||
| 		return fmt.Errorf("connection timed out") | ||||
| 	} | ||||
| 	//i.Debug = false | ||||
| 	i.Handlers.Clear("*") | ||||
| 	if b.GetInt("DebugLevel") == 0 { | ||||
| 		i.Handlers.Clear(girc.ALL_EVENTS) | ||||
| 	} | ||||
| 	go b.doSend() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) Disconnect() error { | ||||
| 	//b.i.Disconnect() | ||||
| 	b.i.Close() | ||||
| 	close(b.Local) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	if channel.Options.Key != "" { | ||||
| 		flog.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) | ||||
| 		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) | ||||
| 		b.i.Cmd.JoinKey(channel.Name, channel.Options.Key) | ||||
| 	} else { | ||||
| 		b.i.Cmd.Join(channel.Name) | ||||
| @@ -160,23 +166,38 @@ func (b *Birc) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
|  | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// we can be in between reconnects #385 | ||||
| 	if !b.i.IsConnected() { | ||||
| 		b.Log.Error("Not connected to server, dropping message") | ||||
| 	} | ||||
|  | ||||
| 	// Execute a command | ||||
| 	if strings.HasPrefix(msg.Text, "!") { | ||||
| 		b.Command(&msg) | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.Charset != "" { | ||||
| 		buf := new(bytes.Buffer) | ||||
| 		w, err := charset.NewWriter(b.Config.Charset, buf) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("charset from utf-8 conversion failed: %s", err) | ||||
| 			return "", err | ||||
| 	// convert to specified charset | ||||
| 	if b.GetString("Charset") != "" { | ||||
| 		switch b.GetString("Charset") { | ||||
| 		case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp": | ||||
| 			msg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text) | ||||
| 		default: | ||||
| 			buf := new(bytes.Buffer) | ||||
| 			w, err := charset.NewWriter(b.GetString("Charset"), buf) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("charset from utf-8 conversion failed: %s", err) | ||||
| 				return "", err | ||||
| 			} | ||||
| 			fmt.Fprint(w, msg.Text) | ||||
| 			w.Close() | ||||
| 			msg.Text = buf.String() | ||||
| 		} | ||||
| 		fmt.Fprintf(w, msg.Text) | ||||
| 		w.Close() | ||||
| 		msg.Text = buf.String() | ||||
| 	} | ||||
|  | ||||
| 	// Handle files | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.Local <- rmsg | ||||
| @@ -189,6 +210,9 @@ func (b *Birc) Send(msg config.Message) (string, error) { | ||||
| 				} | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text = fi.URL | ||||
| 					if fi.Comment != "" { | ||||
| 						msg.Text = fi.Comment + ": " + fi.URL | ||||
| 					} | ||||
| 				} | ||||
| 				b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event} | ||||
| 			} | ||||
| @@ -197,38 +221,45 @@ func (b *Birc) Send(msg config.Message) (string, error) { | ||||
| 	} | ||||
|  | ||||
| 	// split long messages on messageLength, to avoid clipped messages #281 | ||||
| 	if b.Config.MessageSplit { | ||||
| 		msg.Text = helper.SplitStringLength(msg.Text, b.Config.MessageLength) | ||||
| 	if b.GetBool("MessageSplit") { | ||||
| 		msg.Text = helper.SplitStringLength(msg.Text, b.MessageLength) | ||||
| 	} | ||||
| 	for _, text := range strings.Split(msg.Text, "\n") { | ||||
| 		if len(text) > b.Config.MessageLength { | ||||
| 			text = text[:b.Config.MessageLength-len(" <message clipped>")] | ||||
| 		if len(text) > b.MessageLength { | ||||
| 			text = text[:b.MessageLength-len(" <message clipped>")] | ||||
| 			if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError { | ||||
| 				text = text[:len(text)-size] | ||||
| 			} | ||||
| 			text += " <message clipped>" | ||||
| 		} | ||||
| 		if len(b.Local) < b.Config.MessageQueue { | ||||
| 			if len(b.Local) == b.Config.MessageQueue-1 { | ||||
| 		if len(b.Local) < b.MessageQueue { | ||||
| 			if len(b.Local) == b.MessageQueue-1 { | ||||
| 				text = text + " <message clipped>" | ||||
| 			} | ||||
| 			b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event} | ||||
| 		} else { | ||||
| 			flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | ||||
| 			b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) doSend() { | ||||
| 	rate := time.Millisecond * time.Duration(b.Config.MessageDelay) | ||||
| 	rate := time.Millisecond * time.Duration(b.MessageDelay) | ||||
| 	throttle := time.NewTicker(rate) | ||||
| 	for msg := range b.Local { | ||||
| 		<-throttle.C | ||||
| 		username := msg.Username | ||||
| 		if b.GetBool("Colornicks") { | ||||
| 			checksum := crc32.ChecksumIEEE([]byte(msg.Username)) | ||||
| 			colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes | ||||
| 			username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username) | ||||
| 		} | ||||
| 		if msg.Event == config.EVENT_USER_ACTION { | ||||
| 			b.i.Cmd.Action(msg.Channel, msg.Username+msg.Text) | ||||
| 			b.i.Cmd.Action(msg.Channel, username+msg.Text) | ||||
| 		} else { | ||||
| 			b.i.Cmd.Message(msg.Channel, msg.Username+msg.Text) | ||||
| 			b.Log.Debugf("Sending to channel %s", msg.Channel) | ||||
| 			b.i.Cmd.Message(msg.Channel, username+msg.Text) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -252,7 +283,7 @@ func (b *Birc) endNames(client *girc.Client, event girc.Event) { | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) { | ||||
| 	flog.Debug("Registering callbacks") | ||||
| 	b.Log.Debug("Registering callbacks") | ||||
| 	i := b.i | ||||
| 	b.Nick = event.Params[0] | ||||
|  | ||||
| @@ -271,107 +302,137 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) { | ||||
|  | ||||
| func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) { | ||||
| 	if len(event.Params) == 0 { | ||||
| 		flog.Debugf("handleJoinPart: empty Params? %#v", event) | ||||
| 		b.Log.Debugf("handleJoinPart: empty Params? %#v", event) | ||||
| 		return | ||||
| 	} | ||||
| 	channel := event.Params[0] | ||||
| 	channel := strings.ToLower(event.Params[0]) | ||||
| 	if event.Command == "KICK" { | ||||
| 		flog.Infof("Got kicked from %s by %s", channel, event.Source.Name) | ||||
| 		time.Sleep(time.Duration(b.Config.RejoinDelay) * time.Second) | ||||
| 		b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name) | ||||
| 		time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second) | ||||
| 		b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||
| 		return | ||||
| 	} | ||||
| 	if event.Command == "QUIT" { | ||||
| 		if event.Source.Name == b.Nick && strings.Contains(event.Trailing, "Ping timeout") { | ||||
| 			flog.Infof("%s reconnecting ..", b.Account) | ||||
| 			b.Log.Infof("%s reconnecting ..", b.Account) | ||||
| 			b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: channel, Account: b.Account, Event: config.EVENT_FAILURE} | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if event.Source.Name != b.Nick { | ||||
| 		flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 		b.Remote <- config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE} | ||||
| 		if b.GetBool("nosendjoinpart") { | ||||
| 			return | ||||
| 		} | ||||
| 		b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 		msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE} | ||||
| 		b.Log.Debugf("<= Message is %#v", msg) | ||||
| 		b.Remote <- msg | ||||
| 		return | ||||
| 	} | ||||
| 	flog.Debugf("handle %#v", event) | ||||
| 	b.Log.Debugf("handle %#v", event) | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleNotice(client *girc.Client, event girc.Event) { | ||||
| 	if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.Config.NickServNick { | ||||
| 		b.i.Cmd.Message(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword) | ||||
| 	if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.GetString("NickServNick") { | ||||
| 		b.i.Cmd.Message(b.GetString("NickServNick"), "IDENTIFY "+b.GetString("NickServPassword")) | ||||
| 	} else { | ||||
| 		b.handlePrivMsg(client, event) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleOther(client *girc.Client, event girc.Event) { | ||||
| 	if b.GetInt("DebugLevel") == 1 { | ||||
| 		if event.Command != "CLIENT_STATE_UPDATED" && | ||||
| 			event.Command != "CLIENT_GENERAL_UPDATED" { | ||||
| 			b.Log.Debugf("%#v", event.String()) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	switch event.Command { | ||||
| 	case "372", "375", "376", "250", "251", "252", "253", "254", "255", "265", "266", "002", "003", "004", "005": | ||||
| 		return | ||||
| 	} | ||||
| 	flog.Debugf("%#v", event.String()) | ||||
| 	b.Log.Debugf("%#v", event.String()) | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleOtherAuth(client *girc.Client, event girc.Event) { | ||||
| 	if strings.EqualFold(b.Config.NickServNick, "Q@CServe.quakenet.org") { | ||||
| 		flog.Debugf("Authenticating %s against %s", b.Config.NickServUsername, b.Config.NickServNick) | ||||
| 		b.i.Cmd.Message(b.Config.NickServNick, "AUTH "+b.Config.NickServUsername+" "+b.Config.NickServPassword) | ||||
| 	if strings.EqualFold(b.GetString("NickServNick"), "Q@CServe.quakenet.org") { | ||||
| 		b.Log.Debugf("Authenticating %s against %s", b.GetString("NickServUsername"), b.GetString("NickServNick")) | ||||
| 		b.i.Cmd.Message(b.GetString("NickServNick"), "AUTH "+b.GetString("NickServUsername")+" "+b.GetString("NickServPassword")) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { | ||||
| func (b *Birc) skipPrivMsg(event girc.Event) bool { | ||||
| 	// Our nick can be changed | ||||
| 	b.Nick = b.i.GetNick() | ||||
|  | ||||
| 	// freenode doesn't send 001 as first reply | ||||
| 	if event.Command == "NOTICE" { | ||||
| 		return | ||||
| 		return true | ||||
| 	} | ||||
| 	// don't forward queries to the bot | ||||
| 	if event.Params[0] == b.Nick { | ||||
| 		return | ||||
| 		return true | ||||
| 	} | ||||
| 	// don't forward message from ourself | ||||
| 	if event.Source.Name == b.Nick { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) { | ||||
| 	if b.skipPrivMsg(event) { | ||||
| 		return | ||||
| 	} | ||||
| 	rmsg := config.Message{Username: event.Source.Name, Channel: strings.ToLower(event.Params[0]), Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host} | ||||
| 	flog.Debugf("handlePrivMsg() %s %s %#v", event.Source.Name, event.Trailing, event) | ||||
| 	msg := "" | ||||
| 	b.Log.Debugf("== Receiving PRIVMSG: %s %s %#v", event.Source.Name, event.Trailing, event) | ||||
|  | ||||
| 	// set action event | ||||
| 	if event.IsAction() { | ||||
| 		rmsg.Event = config.EVENT_USER_ACTION | ||||
| 	} | ||||
| 	msg += event.StripAction() | ||||
|  | ||||
| 	// strip action, we made an event if it was an action | ||||
| 	rmsg.Text += event.StripAction() | ||||
|  | ||||
| 	// strip IRC colors | ||||
| 	re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`) | ||||
| 	msg = re.ReplaceAllString(msg, "") | ||||
| 	rmsg.Text = re.ReplaceAllString(rmsg.Text, "") | ||||
|  | ||||
| 	// start detecting the charset | ||||
| 	var r io.Reader | ||||
| 	var err error | ||||
| 	mycharset := b.Config.Charset | ||||
| 	mycharset := b.GetString("Charset") | ||||
| 	if mycharset == "" { | ||||
| 		// detect what were sending so that we convert it to utf-8 | ||||
| 		detector := chardet.NewTextDetector() | ||||
| 		result, err := detector.DetectBest([]byte(msg)) | ||||
| 		result, err := detector.DetectBest([]byte(rmsg.Text)) | ||||
| 		if err != nil { | ||||
| 			flog.Infof("detection failed for msg: %#v", msg) | ||||
| 			b.Log.Infof("detection failed for rmsg.Text: %#v", rmsg.Text) | ||||
| 			return | ||||
| 		} | ||||
| 		flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence) | ||||
| 		b.Log.Debugf("detected %s confidence %#v", result.Charset, result.Confidence) | ||||
| 		mycharset = result.Charset | ||||
| 		// if we're not sure, just pick ISO-8859-1 | ||||
| 		if result.Confidence < 80 { | ||||
| 			mycharset = "ISO-8859-1" | ||||
| 		} | ||||
| 	} | ||||
| 	r, err = charset.NewReader(mycharset, strings.NewReader(msg)) | ||||
| 	if err != nil { | ||||
| 		flog.Errorf("charset to utf-8 conversion failed: %s", err) | ||||
| 		return | ||||
| 	switch mycharset { | ||||
| 	case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp": | ||||
| 		rmsg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text) | ||||
| 	default: | ||||
| 		r, err = charset.NewReader(mycharset, strings.NewReader(rmsg.Text)) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("charset to utf-8 conversion failed: %s", err) | ||||
| 			return | ||||
| 		} | ||||
| 		output, _ := ioutil.ReadAll(r) | ||||
| 		rmsg.Text = string(output) | ||||
| 	} | ||||
| 	output, _ := ioutil.ReadAll(r) | ||||
| 	msg = string(output) | ||||
|  | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", event.Params[0], b.Account) | ||||
| 	rmsg.Text = msg | ||||
| 	b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account) | ||||
| 	b.Remote <- rmsg | ||||
| } | ||||
|  | ||||
| @@ -379,13 +440,13 @@ func (b *Birc) handleTopicWhoTime(client *girc.Client, event girc.Event) { | ||||
| 	parts := strings.Split(event.Params[2], "!") | ||||
| 	t, err := strconv.ParseInt(event.Params[3], 10, 64) | ||||
| 	if err != nil { | ||||
| 		flog.Errorf("Invalid time stamp: %s", event.Params[3]) | ||||
| 		b.Log.Errorf("Invalid time stamp: %s", event.Params[3]) | ||||
| 	} | ||||
| 	user := parts[0] | ||||
| 	if len(parts) > 1 { | ||||
| 		user += " [" + parts[1] + "]" | ||||
| 	} | ||||
| 	flog.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0)) | ||||
| 	b.Log.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0)) | ||||
| } | ||||
|  | ||||
| func (b *Birc) nicksPerRow() int { | ||||
|   | ||||
| @@ -2,14 +2,15 @@ package bmatrix | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"mime" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	matrix "github.com/matterbridge/gomatrix" | ||||
| ) | ||||
|  | ||||
| @@ -18,42 +19,33 @@ type Bmatrix struct { | ||||
| 	UserID  string | ||||
| 	RoomMap map[string]string | ||||
| 	sync.RWMutex | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "matrix" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bmatrix { | ||||
| 	b := &Bmatrix{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bmatrix{Config: cfg} | ||||
| 	b.RoomMap = make(map[string]string) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	b.mc, err = matrix.NewClient(b.Config.Server, "", "") | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	b.mc, err = matrix.NewClient(b.GetString("Server"), "", "") | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	resp, err := b.mc.Login(&matrix.ReqLogin{ | ||||
| 		Type:     "m.login.password", | ||||
| 		User:     b.Config.Login, | ||||
| 		Password: b.Config.Password, | ||||
| 		User:     b.GetString("Login"), | ||||
| 		Password: b.GetString("Password"), | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	b.mc.SetCredentials(resp.UserID, resp.AccessToken) | ||||
| 	b.UserID = resp.UserID | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go b.handlematrix() | ||||
| 	return nil | ||||
| } | ||||
| @@ -74,9 +66,22 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	channel := b.getRoomID(msg.Channel) | ||||
| 	// ignore delete messages | ||||
| 	b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel) | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		resp, err := b.mc.SendMessageEvent(channel, "m.room.message", | ||||
| 			matrix.TextMessage{"m.emote", msg.Username + msg.Text}) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return resp.EventID, err | ||||
| 	} | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| @@ -87,62 +92,22 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { | ||||
| 		} | ||||
| 		return resp.EventID, err | ||||
| 	} | ||||
| 	flog.Debugf("Sending to channel %s", channel) | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		resp, err := b.mc.SendMessageEvent(channel, "m.room.message", | ||||
| 			matrix.TextMessage{"m.emote", msg.Username + msg.Text}) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return resp.EventID, err | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.mc.SendText(channel, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				content := bytes.NewReader(*fi.Data) | ||||
| 				sp := strings.Split(fi.Name, ".") | ||||
| 				mtype := mime.TypeByExtension("." + sp[len(sp)-1]) | ||||
| 				if strings.Contains(mtype, "image") || | ||||
| 					strings.Contains(mtype, "video") { | ||||
| 					if fi.Comment != "" { | ||||
| 						_, err := b.mc.SendText(channel, msg.Username+fi.Comment) | ||||
| 						if err != nil { | ||||
| 							flog.Errorf("file comment failed: %#v", err) | ||||
| 						} | ||||
| 					} | ||||
| 					flog.Debugf("uploading file: %s %s", fi.Name, mtype) | ||||
| 					res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data))) | ||||
| 					if err != nil { | ||||
| 						flog.Errorf("file upload failed: %#v", err) | ||||
| 						continue | ||||
| 					} | ||||
| 					if strings.Contains(mtype, "video") { | ||||
| 						flog.Debugf("sendVideo %s", res.ContentURI) | ||||
| 						_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI) | ||||
| 						if err != nil { | ||||
| 							flog.Errorf("sendVideo failed: %#v", err) | ||||
| 						} | ||||
| 					} | ||||
| 					if strings.Contains(mtype, "image") { | ||||
| 						flog.Debugf("sendImage %s", res.ContentURI) | ||||
| 						_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI) | ||||
| 						if err != nil { | ||||
| 							flog.Errorf("sendImage failed: %#v", err) | ||||
| 						} | ||||
| 					} | ||||
| 					flog.Debugf("result: %#v", res) | ||||
| 				} | ||||
| 			} | ||||
| 			return "", nil | ||||
| 			return b.handleUploadFile(&msg, channel) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	// matrix has no editing support | ||||
|  | ||||
| 	// Post normal message | ||||
| 	resp, err := b.mc.SendText(channel, msg.Username+msg.Text) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| @@ -168,7 +133,7 @@ func (b *Bmatrix) handlematrix() error { | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if err := b.mc.Sync(); err != nil { | ||||
| 				flog.Println("Sync() returned ", err) | ||||
| 				b.Log.Println("Sync() returned ", err) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| @@ -176,24 +141,34 @@ func (b *Bmatrix) handlematrix() error { | ||||
| } | ||||
|  | ||||
| func (b *Bmatrix) handleEvent(ev *matrix.Event) { | ||||
| 	flog.Debugf("Received: %#v", ev) | ||||
| 	b.Log.Debugf("== Receiving event: %#v", ev) | ||||
| 	if ev.Sender != b.UserID { | ||||
| 		b.RLock() | ||||
| 		channel, ok := b.RoomMap[ev.RoomID] | ||||
| 		b.RUnlock() | ||||
| 		if !ok { | ||||
| 			flog.Debugf("Unknown room %s", ev.RoomID) | ||||
| 			b.Log.Debugf("Unknown room %s", ev.RoomID) | ||||
| 			return | ||||
| 		} | ||||
| 		username := ev.Sender[1:] | ||||
| 		if b.Config.NoHomeServerSuffix { | ||||
| 			re := regexp.MustCompile("(.*?):.*") | ||||
| 			username = re.ReplaceAllString(username, `$1`) | ||||
|  | ||||
| 		// TODO download avatar | ||||
|  | ||||
| 		// Create our message | ||||
| 		rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID} | ||||
|  | ||||
| 		// Text must be a string | ||||
| 		if rmsg.Text, ok = ev.Content["body"].(string); !ok { | ||||
| 			b.Log.Errorf("Content[body] wasn't a %T ?", rmsg.Text) | ||||
| 			return | ||||
| 		} | ||||
| 		var text string | ||||
| 		text, _ = ev.Content["body"].(string) | ||||
| 		rmsg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: ev.Sender} | ||||
| 		rmsg.ID = ev.ID | ||||
|  | ||||
| 		// Remove homeserver suffix if configured | ||||
| 		if b.GetBool("NoHomeServerSuffix") { | ||||
| 			re := regexp.MustCompile("(.*?):.*") | ||||
| 			rmsg.Username = re.ReplaceAllString(rmsg.Username, `$1`) | ||||
| 		} | ||||
|  | ||||
| 		// Delete event | ||||
| 		if ev.Type == "m.room.redaction" { | ||||
| 			rmsg.Event = config.EVENT_MSG_DELETE | ||||
| 			rmsg.ID = ev.Redacts | ||||
| @@ -201,50 +176,137 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) { | ||||
| 			b.Remote <- rmsg | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Do we have a /me action | ||||
| 		if ev.Content["msgtype"].(string) == "m.emote" { | ||||
| 			rmsg.Event = config.EVENT_USER_ACTION | ||||
| 		} | ||||
| 		if ev.Content["msgtype"] != nil && ev.Content["msgtype"].(string) == "m.image" || | ||||
| 			ev.Content["msgtype"].(string) == "m.video" || | ||||
| 			ev.Content["msgtype"].(string) == "m.file" { | ||||
| 			flog.Debugf("ev: %#v", ev) | ||||
| 			rmsg.Extra = make(map[string][]interface{}) | ||||
| 			url := ev.Content["url"].(string) | ||||
| 			url = strings.Replace(url, "mxc://", b.Config.Server+"/_matrix/media/v1/download/", -1) | ||||
| 			info := ev.Content["info"].(map[string]interface{}) | ||||
| 			size := info["size"].(float64) | ||||
| 			name := ev.Content["body"].(string) | ||||
| 			// check if we have an image uploaded without extension | ||||
| 			if !strings.Contains(name, ".") { | ||||
| 				if ev.Content["msgtype"].(string) == "m.image" { | ||||
| 					if mtype, ok := ev.Content["mimetype"].(string); ok { | ||||
| 						mext, _ := mime.ExtensionsByType(mtype) | ||||
| 						if len(mext) > 0 { | ||||
| 							name = name + mext[0] | ||||
| 						} | ||||
| 					} else { | ||||
| 						// just a default .png extension if we don't have mime info | ||||
| 						name = name + ".png" | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 		// Do we have attachments | ||||
| 		if b.containsAttachment(ev.Content) { | ||||
| 			err := b.handleDownloadFile(&rmsg, ev.Content) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("download failed: %#v", err) | ||||
| 			} | ||||
| 			flog.Debugf("trying to download %#v with size %#v", name, size) | ||||
| 			if size <= float64(b.General.MediaDownloadSize) { | ||||
| 				data, err := helper.DownloadFile(url) | ||||
| 				if err != nil { | ||||
| 					flog.Errorf("download %s failed %#v", url, err) | ||||
| 				} else { | ||||
| 					flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url)) | ||||
| 					rmsg.Extra["file"] = append(rmsg.Extra["file"], config.FileInfo{Name: name, Data: data}) | ||||
| 				} | ||||
| 			} else { | ||||
| 				flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, b.General.MediaDownloadSize) | ||||
| 				rmsg.Event = config.EVENT_FILE_FAILURE_SIZE | ||||
| 				rmsg.Extra[rmsg.Event] = append(rmsg.Extra[rmsg.Event], config.FileInfo{Name: name, Size: int64(size)}) | ||||
| 			} | ||||
| 			rmsg.Text = "" | ||||
| 		} | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account) | ||||
|  | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account) | ||||
| 		b.Remote <- rmsg | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handleDownloadFile handles file download | ||||
| func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]interface{}) error { | ||||
| 	var ( | ||||
| 		ok                        bool | ||||
| 		url, name, msgtype, mtype string | ||||
| 		info                      map[string]interface{} | ||||
| 		size                      float64 | ||||
| 	) | ||||
|  | ||||
| 	rmsg.Extra = make(map[string][]interface{}) | ||||
| 	if url, ok = content["url"].(string); !ok { | ||||
| 		return fmt.Errorf("url isn't a %T", url) | ||||
| 	} | ||||
| 	url = strings.Replace(url, "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/", -1) | ||||
|  | ||||
| 	if info, ok = content["info"].(map[string]interface{}); !ok { | ||||
| 		return fmt.Errorf("info isn't a %T", info) | ||||
| 	} | ||||
| 	if size, ok = info["size"].(float64); !ok { | ||||
| 		return fmt.Errorf("size isn't a %T", size) | ||||
| 	} | ||||
| 	if name, ok = content["body"].(string); !ok { | ||||
| 		return fmt.Errorf("name isn't a %T", name) | ||||
| 	} | ||||
| 	if msgtype, ok = content["msgtype"].(string); !ok { | ||||
| 		return fmt.Errorf("msgtype isn't a %T", msgtype) | ||||
| 	} | ||||
| 	if mtype, ok = info["mimetype"].(string); !ok { | ||||
| 		return fmt.Errorf("mtype isn't a %T", mtype) | ||||
| 	} | ||||
|  | ||||
| 	// check if we have an image uploaded without extension | ||||
| 	if !strings.Contains(name, ".") { | ||||
| 		if msgtype == "m.image" { | ||||
| 			mext, _ := mime.ExtensionsByType(mtype) | ||||
| 			if len(mext) > 0 { | ||||
| 				name = name + mext[0] | ||||
| 			} | ||||
| 		} else { | ||||
| 			// just a default .png extension if we don't have mime info | ||||
| 			name = name + ".png" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// check if the size is ok | ||||
| 	err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// actually download the file | ||||
| 	data, err := helper.DownloadFile(url) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("download %s failed %#v", url, err) | ||||
| 	} | ||||
| 	// add the downloaded data to the message | ||||
| 	helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string) (string, error) { | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		content := bytes.NewReader(*fi.Data) | ||||
| 		sp := strings.Split(fi.Name, ".") | ||||
| 		mtype := mime.TypeByExtension("." + sp[len(sp)-1]) | ||||
| 		if strings.Contains(mtype, "image") || | ||||
| 			strings.Contains(mtype, "video") { | ||||
| 			if fi.Comment != "" { | ||||
| 				_, err := b.mc.SendText(channel, msg.Username+fi.Comment) | ||||
| 				if err != nil { | ||||
| 					b.Log.Errorf("file comment failed: %#v", err) | ||||
| 				} | ||||
| 			} | ||||
| 			b.Log.Debugf("uploading file: %s %s", fi.Name, mtype) | ||||
| 			res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data))) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("file upload failed: %#v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			if strings.Contains(mtype, "video") { | ||||
| 				b.Log.Debugf("sendVideo %s", res.ContentURI) | ||||
| 				_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI) | ||||
| 				if err != nil { | ||||
| 					b.Log.Errorf("sendVideo failed: %#v", err) | ||||
| 				} | ||||
| 			} | ||||
| 			if strings.Contains(mtype, "image") { | ||||
| 				b.Log.Debugf("sendImage %s", res.ContentURI) | ||||
| 				_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI) | ||||
| 				if err != nil { | ||||
| 					b.Log.Errorf("sendImage failed: %#v", err) | ||||
| 				} | ||||
| 			} | ||||
| 			b.Log.Debugf("result: %#v", res) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // skipMessages returns true if this message should not be handled | ||||
| func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool { | ||||
| 	// Skip empty messages | ||||
| 	if content["msgtype"] == nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// Only allow image,video or file msgtypes | ||||
| 	if !(content["msgtype"].(string) == "m.image" || | ||||
| 		content["msgtype"].(string) == "m.video" || | ||||
| 		content["msgtype"].(string) == "m.file") { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|   | ||||
| @@ -3,51 +3,28 @@ package bmattermost | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/42wim/matterbridge/matterclient" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"strings" | ||||
| 	"github.com/rs/xid" | ||||
| ) | ||||
|  | ||||
| type MMhook struct { | ||||
| 	mh *matterhook.Client | ||||
| } | ||||
|  | ||||
| type MMapi struct { | ||||
| 	mc    *matterclient.MMClient | ||||
| 	mmMap map[string]string | ||||
| } | ||||
|  | ||||
| type MMMessage struct { | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| 	UserID   string | ||||
| 	ID       string | ||||
| 	Event    string | ||||
| 	Extra    map[string][]interface{} | ||||
| } | ||||
|  | ||||
| type Bmattermost struct { | ||||
| 	MMhook | ||||
| 	MMapi | ||||
| 	TeamId string | ||||
| 	*config.BridgeConfig | ||||
| 	mh     *matterhook.Client | ||||
| 	mc     *matterclient.MMClient | ||||
| 	uuid   string | ||||
| 	TeamID string | ||||
| 	*bridge.Config | ||||
| 	avatarMap map[string]string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "mattermost" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bmattermost { | ||||
| 	b := &Bmattermost{BridgeConfig: cfg, avatarMap: make(map[string]string)} | ||||
| 	b.mmMap = make(map[string]string) | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)} | ||||
| 	b.uuid = xid.New().String() | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| @@ -56,47 +33,47 @@ func (b *Bmattermost) Command(cmd string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Connect() error { | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		if b.Config.WebhookURL != "" { | ||||
| 			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 		} else if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (sending)") | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		if b.GetString("WebhookURL") != "" { | ||||
| 			b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} else if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (sending)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else if b.Config.Login != "" { | ||||
| 			flog.Info("Connecting using login/password (sending)") | ||||
| 		} else if b.GetString("Login") != "" { | ||||
| 			b.Log.Info("Connecting using login/password (sending)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else { | ||||
| 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 			b.Log.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} | ||||
| 		go b.handleMatter() | ||||
| 		return nil | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		flog.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		b.Log.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 			matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 				DisableServer: true}) | ||||
| 		if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (receiving)") | ||||
| 		if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (receiving)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			go b.handleMatter() | ||||
| 		} else if b.Config.Login != "" { | ||||
| 			flog.Info("Connecting using login/password (receiving)") | ||||
| 		} else if b.GetString("Login") != "" { | ||||
| 			b.Log.Info("Connecting using login/password (receiving)") | ||||
| 			err := b.apiLogin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| @@ -104,23 +81,23 @@ func (b *Bmattermost) Connect() error { | ||||
| 			go b.handleMatter() | ||||
| 		} | ||||
| 		return nil | ||||
| 	} else if b.Config.Token != "" { | ||||
| 		flog.Info("Connecting using token (sending and receiving)") | ||||
| 	} else if b.GetString("Token") != "" { | ||||
| 		b.Log.Info("Connecting using token (sending and receiving)") | ||||
| 		err := b.apiLogin() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		go b.handleMatter() | ||||
| 	} else if b.Config.Login != "" { | ||||
| 		flog.Info("Connecting using login/password (sending and receiving)") | ||||
| 	} else if b.GetString("Login") != "" { | ||||
| 		b.Log.Info("Connecting using login/password (sending and receiving)") | ||||
| 		err := b.apiLogin() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		go b.handleMatter() | ||||
| 	} | ||||
| 	if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" && b.Config.Token == "" { | ||||
| 		return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured.") | ||||
| 	if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Login") == "" && b.GetString("Token") == "" { | ||||
| 		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -131,7 +108,7 @@ func (b *Bmattermost) Disconnect() error { | ||||
|  | ||||
| func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	// we can only join channels using the API | ||||
| 	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" { | ||||
| 	if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" { | ||||
| 		id := b.mc.GetChannelId(channel.Name, "") | ||||
| 		if id == "" { | ||||
| 			return fmt.Errorf("Could not find channel ID for channel %s", channel.Name) | ||||
| @@ -142,136 +119,88 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		msg.Text = "*" + msg.Text + "*" | ||||
| 	} | ||||
| 	nick := msg.Username | ||||
| 	message := msg.Text | ||||
| 	channel := msg.Channel | ||||
|  | ||||
| 	// map the file SHA to our user (caches the avatar) | ||||
| 	if msg.Event == config.EVENT_AVATAR_DOWNLOAD { | ||||
| 		fi := msg.Extra["file"][0].(config.FileInfo) | ||||
| 		/* if we have a sha we have successfully uploaded the file to the media server, | ||||
| 		so we can now cache the sha */ | ||||
| 		if fi.SHA != "" { | ||||
| 			flog.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID) | ||||
| 			b.avatarMap[msg.UserID] = fi.SHA | ||||
| 		} | ||||
| 		return "", nil | ||||
| 		return b.cacheAvatar(&msg) | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.PrefixMessagesWithNick { | ||||
| 		message = nick + message | ||||
| 	// Use webhook to send the message | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		return b.sendWebhook(msg) | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
|  | ||||
| 		if msg.Extra != nil { | ||||
| 			for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 				matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL, Channel: channel, UserName: rmsg.Username, | ||||
| 					Text: rmsg.Text, Props: make(map[string]interface{})} | ||||
| 				matterMessage.Props["matterbridge"] = true | ||||
| 				b.mh.Send(matterMessage) | ||||
| 			} | ||||
| 			if len(msg.Extra["file"]) > 0 { | ||||
| 				for _, f := range msg.Extra["file"] { | ||||
| 					fi := f.(config.FileInfo) | ||||
| 					if fi.URL != "" { | ||||
| 						message += fi.URL | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 		matterMessage.IconURL = msg.Avatar | ||||
| 		matterMessage.Channel = channel | ||||
| 		matterMessage.UserName = nick | ||||
| 		matterMessage.Type = "" | ||||
| 		matterMessage.Text = message | ||||
| 		matterMessage.Props = make(map[string]interface{}) | ||||
| 		matterMessage.Props["matterbridge"] = true | ||||
| 		err := b.mh.Send(matterMessage) | ||||
| 		if err != nil { | ||||
| 			flog.Info(err) | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		return msg.ID, b.mc.DeleteMessage(msg.ID) | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.mc.PostMessage(b.mc.GetChannelId(channel, ""), rmsg.Username+rmsg.Text) | ||||
| 			b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, ""), rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			var err error | ||||
| 			var res, id string | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				id, err = b.mc.UploadFile(*fi.Data, b.mc.GetChannelId(channel, ""), fi.Name) | ||||
| 				if err != nil { | ||||
| 					flog.Debugf("ERROR %#v", err) | ||||
| 					return "", err | ||||
| 				} | ||||
| 				message = fi.Comment | ||||
| 				if b.Config.PrefixMessagesWithNick { | ||||
| 					message = nick + fi.Comment | ||||
| 				} | ||||
| 				res, err = b.mc.PostMessageWithFiles(b.mc.GetChannelId(channel, ""), message, []string{id}) | ||||
| 			} | ||||
| 			return res, err | ||||
| 			return b.handleUploadFile(&msg) | ||||
| 		} | ||||
| 	} | ||||
| 	if msg.ID != "" { | ||||
| 		return b.mc.EditMessage(msg.ID, message) | ||||
|  | ||||
| 	// Prepend nick if configured | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
| 	return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message) | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	if msg.ID != "" { | ||||
| 		return b.mc.EditMessage(msg.ID, msg.Text) | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, ""), msg.Text) | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) handleMatter() { | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		flog.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	messages := make(chan *config.Message) | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		b.Log.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(messages) | ||||
| 	} else { | ||||
| 		if b.Config.Token != "" { | ||||
| 			flog.Debugf("Choosing token based receiving") | ||||
| 		if b.GetString("Token") != "" { | ||||
| 			b.Log.Debugf("Choosing token based receiving") | ||||
| 		} else { | ||||
| 			flog.Debugf("Choosing login/password based receiving") | ||||
| 			b.Log.Debugf("Choosing login/password based receiving") | ||||
| 		} | ||||
| 		go b.handleMatterClient(mchan) | ||||
| 		go b.handleMatterClient(messages) | ||||
| 	} | ||||
| 	for message := range mchan { | ||||
| 		avatar := helper.GetAvatar(b.avatarMap, message.UserID, b.General) | ||||
| 		rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID, Event: message.Event, Extra: message.Extra, Avatar: avatar} | ||||
| 		text, ok := b.replaceAction(message.Text) | ||||
| 	var ok bool | ||||
| 	for message := range messages { | ||||
| 		message.Avatar = helper.GetAvatar(b.avatarMap, message.UserID, b.General) | ||||
| 		message.Account = b.Account | ||||
| 		message.Text, ok = b.replaceAction(message.Text) | ||||
| 		if ok { | ||||
| 			rmsg.Event = config.EVENT_USER_ACTION | ||||
| 			message.Event = config.EVENT_USER_ACTION | ||||
| 		} | ||||
| 		rmsg.Text = text | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 		flog.Debugf("Message is %#v", rmsg) | ||||
| 		b.Remote <- rmsg | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 		b.Log.Debugf("<= Message is %#v", message) | ||||
| 		b.Remote <- *message | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | ||||
| func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { | ||||
| 	for message := range b.mc.MessageChan { | ||||
| 		flog.Debugf("%#v", message.Raw.Data) | ||||
| 		if message.Type == "system_join_leave" || | ||||
| 			message.Type == "system_join_channel" || | ||||
| 			message.Type == "system_leave_channel" { | ||||
| 			flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 			b.Remote <- config.Message{Username: "system", Text: message.Text, Channel: message.Channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE} | ||||
| 			continue | ||||
| 		} | ||||
| 		if (message.Raw.Event == "post_edited") && b.Config.EditDisable { | ||||
| 		b.Log.Debugf("%#v", message.Raw.Data) | ||||
|  | ||||
| 		if b.skipMessage(message) { | ||||
| 			b.Log.Debugf("Skipped message: %#v", message) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| @@ -280,107 +209,87 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | ||||
| 			b.handleDownloadAvatar(message.UserID, message.Channel) | ||||
| 		} | ||||
|  | ||||
| 		m := &MMMessage{Extra: make(map[string][]interface{})} | ||||
| 		b.Log.Debugf("== Receiving event %#v", message) | ||||
|  | ||||
| 		rmsg := &config.Message{Username: message.Username, UserID: message.UserID, Channel: message.Channel, Text: message.Text, ID: message.Post.Id, Extra: make(map[string][]interface{})} | ||||
|  | ||||
| 		// handle mattermost post properties (override username and attachments) | ||||
| 		props := message.Post.Props | ||||
| 		if props != nil { | ||||
| 			if _, ok := props["matterbridge"].(bool); ok { | ||||
| 				flog.Debugf("sent by matterbridge, ignoring") | ||||
| 				continue | ||||
| 			} | ||||
| 			if _, ok := props["override_username"].(string); ok { | ||||
| 				message.Username = props["override_username"].(string) | ||||
| 				rmsg.Username = props["override_username"].(string) | ||||
| 			} | ||||
| 			if _, ok := props["attachments"].([]interface{}); ok { | ||||
| 				m.Extra["attachments"] = props["attachments"].([]interface{}) | ||||
| 			} | ||||
| 		} | ||||
| 		// do not post our own messages back to irc | ||||
| 		// only listen to message from our team | ||||
| 		if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") && | ||||
| 			b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId { | ||||
| 			// if the message has reactions don't repost it (for now, until we can correlate reaction with message) | ||||
| 			if message.Post.HasReactions { | ||||
| 				continue | ||||
| 			} | ||||
| 			flog.Debugf("Receiving from matterclient %#v", message) | ||||
| 			m.UserID = message.UserID | ||||
| 			m.Username = message.Username | ||||
| 			m.Channel = message.Channel | ||||
| 			m.Text = message.Text | ||||
| 			m.ID = message.Post.Id | ||||
| 			if message.Raw.Event == "post_edited" && !b.Config.EditDisable { | ||||
| 				m.Text = message.Text + b.Config.EditSuffix | ||||
| 			} | ||||
| 			if message.Raw.Event == "post_deleted" { | ||||
| 				m.Event = config.EVENT_MSG_DELETE | ||||
| 			} | ||||
| 			if len(message.Post.FileIds) > 0 { | ||||
| 				for _, id := range message.Post.FileIds { | ||||
| 					url, _ := b.mc.Client.GetFileLink(id) | ||||
| 					finfo, resp := b.mc.Client.GetFileInfo(id) | ||||
| 					if resp.Error != nil { | ||||
| 						continue | ||||
| 				rmsg.Extra["attachments"] = props["attachments"].([]interface{}) | ||||
| 				if rmsg.Text == "" { | ||||
| 					for _, attachment := range rmsg.Extra["attachments"] { | ||||
| 						attach := attachment.(map[string]interface{}) | ||||
| 						if attach["text"].(string) != "" { | ||||
| 							rmsg.Text += attach["text"].(string) | ||||
| 							continue | ||||
| 						} | ||||
| 						if attach["fallback"].(string) != "" { | ||||
| 							rmsg.Text += attach["fallback"].(string) | ||||
| 						} | ||||
| 					} | ||||
| 					flog.Debugf("trying to download %#v fileid %#v with size %#v", finfo.Name, finfo.Id, finfo.Size) | ||||
| 					if int(finfo.Size) > b.General.MediaDownloadSize { | ||||
| 						flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", finfo.Name, finfo.Size, b.General.MediaDownloadSize) | ||||
| 						m.Event = config.EVENT_FILE_FAILURE_SIZE | ||||
| 						m.Extra[m.Event] = append(m.Extra[m.Event], config.FileInfo{Name: finfo.Name, Comment: message.Text, Size: int64(finfo.Size)}) | ||||
| 						continue | ||||
| 					} | ||||
| 					data, resp := b.mc.Client.DownloadFile(id, true) | ||||
| 					if resp.Error != nil { | ||||
| 						flog.Errorf("download %s failed %#v", finfo.Name, resp.Error) | ||||
| 						continue | ||||
| 					} | ||||
| 					flog.Debugf("download OK %#v %#v", finfo.Name, len(data)) | ||||
| 					m.Extra["file"] = append(m.Extra["file"], config.FileInfo{Name: finfo.Name, Data: &data, URL: url, Comment: message.Text}) | ||||
| 				} | ||||
| 			} | ||||
| 			mchan <- m | ||||
| 		} | ||||
|  | ||||
| 		// create a text for bridges that don't support native editing | ||||
| 		if message.Raw.Event == "post_edited" && !b.GetBool("EditDisable") { | ||||
| 			rmsg.Text = message.Text + b.GetString("EditSuffix") | ||||
| 		} | ||||
|  | ||||
| 		if message.Raw.Event == "post_deleted" { | ||||
| 			rmsg.Event = config.EVENT_MSG_DELETE | ||||
| 		} | ||||
|  | ||||
| 		if len(message.Post.FileIds) > 0 { | ||||
| 			for _, id := range message.Post.FileIds { | ||||
| 				err := b.handleDownloadFile(rmsg, id) | ||||
| 				if err != nil { | ||||
| 					b.Log.Errorf("download failed: %s", err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		messages <- rmsg | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) { | ||||
| func (b *Bmattermost) handleMatterHook(messages chan *config.Message) { | ||||
| 	for { | ||||
| 		message := b.mh.Receive() | ||||
| 		flog.Debugf("Receiving from matterhook %#v", message) | ||||
| 		m := &MMMessage{} | ||||
| 		m.UserID = message.UserID | ||||
| 		m.Username = message.UserName | ||||
| 		m.Text = message.Text | ||||
| 		m.Channel = message.ChannelName | ||||
| 		mchan <- m | ||||
| 		b.Log.Debugf("Receiving from matterhook %#v", message) | ||||
| 		messages <- &config.Message{UserID: message.UserID, Username: message.UserName, Text: message.Text, Channel: message.ChannelName} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) apiLogin() error { | ||||
| 	password := b.Config.Password | ||||
| 	if b.Config.Token != "" { | ||||
| 		password = "MMAUTHTOKEN=" + b.Config.Token | ||||
| 	password := b.GetString("Password") | ||||
| 	if b.GetString("Token") != "" { | ||||
| 		password = "MMAUTHTOKEN=" + b.GetString("Token") | ||||
| 	} | ||||
|  | ||||
| 	b.mc = matterclient.New(b.Config.Login, password, | ||||
| 		b.Config.Team, b.Config.Server) | ||||
| 	if b.General.Debug { | ||||
| 	b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server")) | ||||
| 	if b.GetBool("debug") { | ||||
| 		b.mc.SetLogLevel("debug") | ||||
| 	} | ||||
| 	b.mc.SkipTLSVerify = b.Config.SkipTLSVerify | ||||
| 	b.mc.NoTLS = b.Config.NoTLS | ||||
| 	flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server) | ||||
| 	b.mc.SkipTLSVerify = b.GetBool("SkipTLSVerify") | ||||
| 	b.mc.NoTLS = b.GetBool("NoTLS") | ||||
| 	b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server")) | ||||
| 	err := b.mc.Login() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.TeamId = b.mc.GetTeamId() | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	b.TeamID = b.mc.GetTeamId() | ||||
| 	go b.mc.WsReceiver() | ||||
| 	go b.mc.StatusLoop() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // replaceAction replace the message with the correct action (/me) code | ||||
| func (b *Bmattermost) replaceAction(text string) (string, bool) { | ||||
| 	if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") { | ||||
| 		return strings.Replace(text, "*", "", -1), true | ||||
| @@ -388,26 +297,166 @@ func (b *Bmattermost) replaceAction(text string) (string, bool) { | ||||
| 	return text, false | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) cacheAvatar(msg *config.Message) (string, error) { | ||||
| 	fi := msg.Extra["file"][0].(config.FileInfo) | ||||
| 	/* if we have a sha we have successfully uploaded the file to the media server, | ||||
| 	so we can now cache the sha */ | ||||
| 	if fi.SHA != "" { | ||||
| 		b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID) | ||||
| 		b.avatarMap[msg.UserID] = fi.SHA | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // handleDownloadAvatar downloads the avatar of userid from channel | ||||
| // sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful. | ||||
| // logs an error message if it fails | ||||
| func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) { | ||||
| 	var name string | ||||
| 	msg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: userid, Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})} | ||||
| 	rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: userid, Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})} | ||||
| 	if _, ok := b.avatarMap[userid]; !ok { | ||||
| 		data, resp := b.mc.Client.GetProfileImage(userid, "") | ||||
| 		if resp.Error != nil { | ||||
| 			flog.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error) | ||||
| 			b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error) | ||||
| 			return | ||||
| 		} | ||||
| 		if len(data) <= b.General.MediaDownloadSize { | ||||
| 			name = userid + ".png" | ||||
| 			flog.Debugf("download OK %#v %#v", name, len(data)) | ||||
| 			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: &data, Avatar: true}) | ||||
| 			flog.Debugf("Sending avatar download message from %#v on %s to gateway", userid, b.Account) | ||||
| 			flog.Debugf("Message is %#v", msg) | ||||
| 			b.Remote <- msg | ||||
| 		} else { | ||||
| 			flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, len(data), b.General.MediaDownloadSize) | ||||
| 		err := helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General) | ||||
| 		if err != nil { | ||||
| 			b.Log.Error(err) | ||||
| 			return | ||||
| 		} | ||||
| 		helper.HandleDownloadData(b.Log, &rmsg, userid+".png", rmsg.Text, "", &data, b.General) | ||||
| 		b.Remote <- rmsg | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handleDownloadFile handles file download | ||||
| func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error { | ||||
| 	url, _ := b.mc.Client.GetFileLink(id) | ||||
| 	finfo, resp := b.mc.Client.GetFileInfo(id) | ||||
| 	if resp.Error != nil { | ||||
| 		return resp.Error | ||||
| 	} | ||||
| 	err := helper.HandleDownloadSize(b.Log, rmsg, finfo.Name, finfo.Size, b.General) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	data, resp := b.mc.Client.DownloadFile(id, true) | ||||
| 	if resp.Error != nil { | ||||
| 		return resp.Error | ||||
| 	} | ||||
| 	helper.HandleDownloadData(b.Log, rmsg, finfo.Name, rmsg.Text, url, &data, b.General) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) { | ||||
| 	var err error | ||||
| 	var res, id string | ||||
| 	channelID := b.mc.GetChannelId(msg.Channel, "") | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		msg.Text = fi.Comment | ||||
| 		if b.GetBool("PrefixMessagesWithNick") { | ||||
| 			msg.Text = msg.Username + msg.Text | ||||
| 		} | ||||
| 		res, err = b.mc.PostMessageWithFiles(channelID, msg.Text, []string{id}) | ||||
| 	} | ||||
| 	return res, err | ||||
| } | ||||
|  | ||||
| // sendWebhook uses the configured WebhookURL to send the message | ||||
| func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) { | ||||
| 	// skip events | ||||
| 	if msg.Event != "" { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
| 	if msg.Extra != nil { | ||||
| 		// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl")) | ||||
| 			matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text, Props: make(map[string]interface{})} | ||||
| 			matterMessage.Props["matterbridge_"+b.uuid] = true | ||||
| 			b.mh.Send(matterMessage) | ||||
| 		} | ||||
|  | ||||
| 		// webhook doesn't support file uploads, so we add the url manually | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text += fi.URL | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	iconURL := config.GetIconURL(&msg, b.GetString("iconurl")) | ||||
| 	matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text, Props: make(map[string]interface{})} | ||||
| 	if msg.Avatar != "" { | ||||
| 		matterMessage.IconURL = msg.Avatar | ||||
| 	} | ||||
| 	matterMessage.Props["matterbridge_"+b.uuid] = true | ||||
| 	err := b.mh.Send(matterMessage) | ||||
| 	if err != nil { | ||||
| 		b.Log.Info(err) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // skipMessages returns true if this message should not be handled | ||||
| func (b *Bmattermost) skipMessage(message *matterclient.Message) bool { | ||||
| 	// Handle join/leave | ||||
| 	if message.Type == "system_join_leave" || | ||||
| 		message.Type == "system_join_channel" || | ||||
| 		message.Type == "system_leave_channel" { | ||||
| 		if b.GetBool("nosendjoinpart") { | ||||
| 			return true | ||||
| 		} | ||||
| 		b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 		b.Remote <- config.Message{Username: "system", Text: message.Text, Channel: message.Channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE} | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Handle edited messages | ||||
| 	if (message.Raw.Event == "post_edited") && b.GetBool("EditDisable") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Ignore messages sent from matterbridge | ||||
| 	if message.Post.Props != nil { | ||||
| 		if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok { | ||||
| 			b.Log.Debugf("sent by matterbridge, ignoring") | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Ignore messages sent from a user logged in as the bot | ||||
| 	if b.mc.User.Username == message.Username { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// if the message has reactions don't repost it (for now, until we can correlate reaction with message) | ||||
| 	if message.Post.HasReactions { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// ignore messages from other teams than ours | ||||
| 	if message.Raw.Data["team_id"].(string) != b.TeamID { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// only handle posted, edited or deleted events | ||||
| 	if !(message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| package brocketchat | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/42wim/matterbridge/hook/rockethook" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type MMhook struct { | ||||
| @@ -15,18 +15,11 @@ type MMhook struct { | ||||
|  | ||||
| type Brocketchat struct { | ||||
| 	MMhook | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "rocketchat" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Brocketchat { | ||||
| 	return &Brocketchat{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Brocketchat{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Command(cmd string) string { | ||||
| @@ -34,11 +27,11 @@ func (b *Brocketchat) Command(cmd string) string { | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Connect() error { | ||||
| 	flog.Info("Connecting webhooks") | ||||
| 	b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 		matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 	b.Log.Info("Connecting webhooks") | ||||
| 	b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 		matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 			DisableServer: true}) | ||||
| 	b.rh = rockethook.New(b.Config.WebhookURL, rockethook.Config{BindAddress: b.Config.WebhookBindAddress}) | ||||
| 	b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 	go b.handleRocketHook() | ||||
| 	return nil | ||||
| } | ||||
| @@ -57,11 +50,11 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL, Channel: rmsg.Channel, UserName: rmsg.Username, | ||||
| 				Text: rmsg.Text} | ||||
| 			iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl")) | ||||
| 			matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text} | ||||
| 			b.mh.Send(matterMessage) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| @@ -74,14 +67,15 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 	iconURL := config.GetIconURL(&msg, b.GetString("iconurl")) | ||||
| 	matterMessage := matterhook.OMessage{IconURL: iconURL} | ||||
| 	matterMessage.Channel = msg.Channel | ||||
| 	matterMessage.UserName = msg.Username | ||||
| 	matterMessage.Type = "" | ||||
| 	matterMessage.Text = msg.Text | ||||
| 	err := b.mh.Send(matterMessage) | ||||
| 	if err != nil { | ||||
| 		flog.Info(err) | ||||
| 		b.Log.Info(err) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "", nil | ||||
| @@ -90,12 +84,12 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) { | ||||
| func (b *Brocketchat) handleRocketHook() { | ||||
| 	for { | ||||
| 		message := b.rh.Receive() | ||||
| 		flog.Debugf("Receiving from rockethook %#v", message) | ||||
| 		b.Log.Debugf("Receiving from rockethook %#v", message) | ||||
| 		// do not loop | ||||
| 		if message.UserName == b.Config.Nick { | ||||
| 		if message.UserName == b.GetString("Nick") { | ||||
| 			continue | ||||
| 		} | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account) | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.UserName, b.Account) | ||||
| 		b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -4,47 +4,37 @@ import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"html" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/nlopes/slack" | ||||
| 	"html" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"github.com/rs/xid" | ||||
| ) | ||||
|  | ||||
| type MMMessage struct { | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| 	UserID   string | ||||
| 	Raw      *slack.MessageEvent | ||||
| } | ||||
|  | ||||
| type Bslack struct { | ||||
| 	mh       *matterhook.Client | ||||
| 	sc       *slack.Client | ||||
| 	rtm      *slack.RTM | ||||
| 	Plus     bool | ||||
| 	Users    []slack.User | ||||
| 	si       *slack.Info | ||||
| 	channels []slack.Channel | ||||
| 	*config.BridgeConfig | ||||
| 	mh         *matterhook.Client | ||||
| 	sc         *slack.Client | ||||
| 	rtm        *slack.RTM | ||||
| 	Users      []slack.User | ||||
| 	Usergroups []slack.UserGroup | ||||
| 	si         *slack.Info | ||||
| 	channels   []slack.Channel | ||||
| 	uuid       string | ||||
| 	*bridge.Config | ||||
| 	sync.RWMutex | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "slack" | ||||
| const messageDeleted = "message_deleted" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bslack { | ||||
| 	return &Bslack{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bslack{Config: cfg, uuid: xid.New().String()} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Command(cmd string) string { | ||||
| @@ -52,71 +42,76 @@ func (b *Bslack) Command(cmd string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Connect() error { | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		if b.Config.WebhookURL != "" { | ||||
| 			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 		} else if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (sending)") | ||||
| 			b.sc = slack.New(b.Config.Token) | ||||
| 	b.RLock() | ||||
| 	defer b.RUnlock() | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		if b.GetString("WebhookURL") != "" { | ||||
| 			b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} else if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (sending)") | ||||
| 			b.sc = slack.New(b.GetString("Token")) | ||||
| 			b.rtm = b.sc.NewRTM() | ||||
| 			go b.rtm.ManageConnection() | ||||
| 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 			b.Log.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} else { | ||||
| 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 					BindAddress: b.Config.WebhookBindAddress}) | ||||
| 			b.Log.Info("Connecting using webhookbindaddress (receiving)") | ||||
| 			b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 				matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 					BindAddress: b.GetString("WebhookBindAddress")}) | ||||
| 		} | ||||
| 		go b.handleSlack() | ||||
| 		return nil | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		flog.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.Config.WebhookURL, | ||||
| 			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		b.Log.Info("Connecting using webhookurl (sending)") | ||||
| 		b.mh = matterhook.New(b.GetString("WebhookURL"), | ||||
| 			matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), | ||||
| 				DisableServer: true}) | ||||
| 		if b.Config.Token != "" { | ||||
| 			flog.Info("Connecting using token (receiving)") | ||||
| 			b.sc = slack.New(b.Config.Token) | ||||
| 		if b.GetString("Token") != "" { | ||||
| 			b.Log.Info("Connecting using token (receiving)") | ||||
| 			b.sc = slack.New(b.GetString("Token")) | ||||
| 			b.rtm = b.sc.NewRTM() | ||||
| 			go b.rtm.ManageConnection() | ||||
| 			go b.handleSlack() | ||||
| 		} | ||||
| 	} else if b.Config.Token != "" { | ||||
| 		flog.Info("Connecting using token (sending and receiving)") | ||||
| 		b.sc = slack.New(b.Config.Token) | ||||
| 	} else if b.GetString("Token") != "" { | ||||
| 		b.Log.Info("Connecting using token (sending and receiving)") | ||||
| 		b.sc = slack.New(b.GetString("Token")) | ||||
| 		b.rtm = b.sc.NewRTM() | ||||
| 		go b.rtm.ManageConnection() | ||||
| 		go b.handleSlack() | ||||
| 	} | ||||
| 	if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Token == "" { | ||||
| 		return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured.") | ||||
| 	if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Token") == "" { | ||||
| 		return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Disconnect() error { | ||||
| 	return nil | ||||
|  | ||||
| 	return b.rtm.Disconnect() | ||||
| } | ||||
|  | ||||
| func (b *Bslack) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	// we can only join channels using the API | ||||
| 	if b.sc != nil { | ||||
| 		if strings.HasPrefix(b.Config.Token, "xoxb") { | ||||
| 		if strings.HasPrefix(b.GetString("Token"), "xoxb") { | ||||
| 			// TODO check if bot has already joined channel | ||||
| 			return nil | ||||
| 		} | ||||
| 		_, err := b.sc.JoinChannel(channel.Name) | ||||
| 		if err != nil { | ||||
| 			if err.Error() != "name_taken" { | ||||
| 				return err | ||||
| 			switch err.Error() { | ||||
| 			case "name_taken", "restricted_action": | ||||
| 			case "default": | ||||
| 				{ | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -124,64 +119,25 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Make a action /me of the message | ||||
| 	if msg.Event == config.EVENT_USER_ACTION { | ||||
| 		msg.Text = "_" + msg.Text + "_" | ||||
| 	} | ||||
| 	nick := msg.Username | ||||
| 	message := msg.Text | ||||
| 	channel := msg.Channel | ||||
| 	if b.Config.PrefixMessagesWithNick { | ||||
| 		message = nick + " " + message | ||||
| 	} | ||||
| 	if b.Config.WebhookURL != "" { | ||||
| 		if msg.Extra != nil { | ||||
| 			for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 				matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL, Channel: channel, UserName: rmsg.Username, | ||||
| 					Text: rmsg.Text} | ||||
| 				b.mh.Send(matterMessage) | ||||
| 			} | ||||
| 			if len(msg.Extra["file"]) > 0 { | ||||
| 				for _, f := range msg.Extra["file"] { | ||||
| 					fi := f.(config.FileInfo) | ||||
| 					if fi.URL != "" { | ||||
| 						message += fi.URL | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 		matterMessage.Channel = channel | ||||
| 		matterMessage.UserName = nick | ||||
| 		matterMessage.Type = "" | ||||
| 		matterMessage.Text = message | ||||
| 		err := b.mh.Send(matterMessage) | ||||
| 		if err != nil { | ||||
| 			flog.Info(err) | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return "", nil | ||||
| 	// Use webhook to send the message | ||||
| 	if b.GetString("WebhookURL") != "" { | ||||
| 		return b.sendWebhook(msg) | ||||
| 	} | ||||
| 	schannel, err := b.getChannelByName(channel) | ||||
|  | ||||
| 	// get the slack channel | ||||
| 	schannel, err := b.getChannelByName(msg.Channel) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	np := slack.NewPostMessageParameters() | ||||
| 	if b.Config.PrefixMessagesWithNick { | ||||
| 		np.AsUser = true | ||||
| 	} | ||||
| 	np.Username = nick | ||||
| 	np.IconURL = config.GetIconURL(&msg, &b.Config) | ||||
| 	if msg.Avatar != "" { | ||||
| 		np.IconURL = msg.Avatar | ||||
| 	} | ||||
| 	np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"}) | ||||
| 	np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...) | ||||
|  | ||||
| 	// replace mentions | ||||
| 	np.LinkNames = 1 | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		// some protocols echo deletes, but with empty ID | ||||
| 		if msg.ID == "" { | ||||
| @@ -189,45 +145,73 @@ func (b *Bslack) Send(msg config.Message) (string, error) { | ||||
| 		} | ||||
| 		// we get a "slack <ID>", split it | ||||
| 		ts := strings.Fields(msg.ID) | ||||
| 		b.sc.DeleteMessage(schannel.ID, ts[1]) | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	// if we have no ID it means we're creating a new message, not updating an existing one | ||||
| 	if msg.ID != "" { | ||||
| 		ts := strings.Fields(msg.ID) | ||||
| 		b.sc.UpdateMessage(schannel.ID, ts[1], message) | ||||
| 		return "", nil | ||||
| 		_, _, err := b.sc.DeleteMessage(schannel.ID, ts[1]) | ||||
| 		if err != nil { | ||||
| 			return msg.ID, err | ||||
| 		} | ||||
| 		return msg.ID, nil | ||||
| 	} | ||||
|  | ||||
| 	// Prepend nick if configured | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
|  | ||||
| 	// Edit message if we have an ID | ||||
| 	if msg.ID != "" { | ||||
| 		ts := strings.Fields(msg.ID) | ||||
| 		_, _, _, err := b.sc.UpdateMessage(schannel.ID, ts[1], msg.Text) | ||||
| 		if err != nil { | ||||
| 			return msg.ID, err | ||||
| 		} | ||||
| 		return msg.ID, nil | ||||
| 	} | ||||
|  | ||||
| 	// create slack new post parameters | ||||
| 	np := slack.NewPostMessageParameters() | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		np.AsUser = true | ||||
| 	} | ||||
| 	np.Username = msg.Username | ||||
| 	np.LinkNames = 1 // replace mentions | ||||
| 	np.IconURL = config.GetIconURL(&msg, b.GetString("iconurl")) | ||||
| 	if msg.Avatar != "" { | ||||
| 		np.IconURL = msg.Avatar | ||||
| 	} | ||||
| 	// add a callback ID so we can see we created it | ||||
| 	np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge_" + b.uuid}) | ||||
| 	// add file attachments | ||||
| 	np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...) | ||||
| 	// add slack attachments (from another slack bridge) | ||||
| 	if len(msg.Extra["slack_attachment"]) > 0 { | ||||
| 		for _, attach := range msg.Extra["slack_attachment"] { | ||||
| 			np.Attachments = append(np.Attachments, attach.([]slack.Attachment)...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.sc.PostMessage(schannel.ID, rmsg.Username+rmsg.Text, np) | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			var err error | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				_, err = b.sc.UploadFile(slack.FileUploadParameters{ | ||||
| 					Reader:         bytes.NewReader(*fi.Data), | ||||
| 					Filename:       fi.Name, | ||||
| 					Channels:       []string{schannel.ID}, | ||||
| 					InitialComment: fi.Comment, | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					flog.Errorf("uploadfile %#v", err) | ||||
| 				} | ||||
| 			} | ||||
| 			b.handleUploadFile(&msg, schannel.ID) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	_, id, err := b.sc.PostMessage(schannel.ID, message, np) | ||||
| 	// Post normal message | ||||
| 	_, id, err := b.sc.PostMessage(schannel.ID, msg.Text, np) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "slack " + id, nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Reload(cfg *bridge.Config) (string, error) { | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) getAvatar(user string) string { | ||||
| 	var avatar string | ||||
| 	if b.Users != nil { | ||||
| @@ -265,163 +249,61 @@ func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) { | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleSlack() { | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.Config.WebhookBindAddress != "" { | ||||
| 		flog.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	messages := make(chan *config.Message) | ||||
| 	if b.GetString("WebhookBindAddress") != "" { | ||||
| 		b.Log.Debugf("Choosing webhooks based receiving") | ||||
| 		go b.handleMatterHook(messages) | ||||
| 	} else { | ||||
| 		flog.Debugf("Choosing token based receiving") | ||||
| 		go b.handleSlackClient(mchan) | ||||
| 		b.Log.Debugf("Choosing token based receiving") | ||||
| 		go b.handleSlackClient(messages) | ||||
| 	} | ||||
| 	time.Sleep(time.Second) | ||||
| 	flog.Debug("Start listening for Slack messages") | ||||
| 	for message := range mchan { | ||||
| 		// do not send messages from ourself | ||||
| 		if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name { | ||||
| 			continue | ||||
| 		} | ||||
| 		if (message.Text == "" || message.Username == "") && message.Raw.SubType != "message_deleted" { | ||||
| 			continue | ||||
| 		} | ||||
| 		text := message.Text | ||||
| 		text = b.replaceURL(text) | ||||
| 		text = html.UnescapeString(text) | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 		msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID, ID: "slack " + message.Raw.Timestamp, Extra: make(map[string][]interface{})} | ||||
| 		if message.Raw.SubType == "me_message" { | ||||
| 			msg.Event = config.EVENT_USER_ACTION | ||||
| 		} | ||||
| 		if message.Raw.SubType == "channel_leave" || message.Raw.SubType == "channel_join" { | ||||
| 			msg.Username = "system" | ||||
| 			msg.Event = config.EVENT_JOIN_LEAVE | ||||
| 		} | ||||
| 		// edited messages have a submessage, use this timestamp | ||||
| 		if message.Raw.SubMessage != nil { | ||||
| 			msg.ID = "slack " + message.Raw.SubMessage.Timestamp | ||||
| 		} | ||||
| 		if message.Raw.SubType == "message_deleted" { | ||||
| 			msg.Text = config.EVENT_MSG_DELETE | ||||
| 			msg.Event = config.EVENT_MSG_DELETE | ||||
| 			msg.ID = "slack " + message.Raw.DeletedTimestamp | ||||
| 		} | ||||
| 		if message.Raw.SubType == "channel_topic" || message.Raw.SubType == "channel_purpose" { | ||||
| 			msg.Event = config.EVENT_TOPIC_CHANGE | ||||
| 		} | ||||
| 	b.Log.Debug("Start listening for Slack messages") | ||||
| 	for message := range messages { | ||||
| 		b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
|  | ||||
| 		// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 		if message.Raw.File != nil { | ||||
| 			// limit to 1MB for now | ||||
| 			comment := "" | ||||
| 			results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(msg.Text, -1) | ||||
| 			if len(results) > 0 { | ||||
| 				comment = results[0][1] | ||||
| 			} | ||||
| 		// cleanup the message | ||||
| 		message.Text = b.replaceMention(message.Text) | ||||
| 		message.Text = b.replaceVariable(message.Text) | ||||
| 		message.Text = b.replaceChannel(message.Text) | ||||
| 		message.Text = b.replaceURL(message.Text) | ||||
| 		message.Text = html.UnescapeString(message.Text) | ||||
|  | ||||
| 			if message.Raw.File.Size > b.General.MediaDownloadSize { | ||||
| 				flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", message.Raw.File.Name, message.Raw.File.Size, b.General.MediaDownloadSize) | ||||
| 				msg.Event = config.EVENT_FILE_FAILURE_SIZE | ||||
| 				msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: message.Raw.File.Name, Comment: comment, Size: int64(message.Raw.File.Size)}) | ||||
| 			} else { | ||||
| 				data, err := b.downloadFile(message.Raw.File.URLPrivateDownload) | ||||
| 				if err != nil { | ||||
| 					flog.Errorf("download %s failed %#v", message.Raw.File.URLPrivateDownload, err) | ||||
| 				} else { | ||||
| 					msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: message.Raw.File.Name, Data: data, Comment: comment}) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		flog.Debugf("Message is %#v", msg) | ||||
| 		b.Remote <- msg | ||||
| 		// Add the avatar | ||||
| 		message.Avatar = b.getAvatar(strings.ToLower(message.Username)) | ||||
|  | ||||
| 		b.Log.Debugf("<= Message is %#v", message) | ||||
| 		b.Remote <- *message | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| func (b *Bslack) handleSlackClient(messages chan *config.Message) { | ||||
| 	for msg := range b.rtm.IncomingEvents { | ||||
| 		if msg.Type != "user_typing" && msg.Type != "latency_report" { | ||||
| 			flog.Debugf("Receiving from slackclient %#v", msg.Data) | ||||
| 			b.Log.Debugf("== Receiving event %#v", msg.Data) | ||||
| 		} | ||||
| 		switch ev := msg.Data.(type) { | ||||
| 		case *slack.MessageEvent: | ||||
| 			if ev.SubType == "pinned_item" || ev.SubType == "unpinned_item" { | ||||
| 			if b.skipMessageEvent(ev) { | ||||
| 				b.Log.Debugf("Skipped message: %#v", ev) | ||||
| 				continue | ||||
| 			} | ||||
| 			if len(ev.Attachments) > 0 { | ||||
| 				// skip messages we made ourselves | ||||
| 				if ev.Attachments[0].CallbackID == "matterbridge" { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp { | ||||
| 				flog.Debugf("SubMessage %#v", ev.SubMessage) | ||||
| 				ev.User = ev.SubMessage.User | ||||
| 				ev.Text = ev.SubMessage.Text + b.Config.EditSuffix | ||||
|  | ||||
| 				// it seems ev.SubMessage.Edited == nil when slack unfurls | ||||
| 				// do not forward these messages #266 | ||||
| 				if ev.SubMessage.Edited == nil { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			// use our own func because rtm.GetChannelInfo doesn't work for private channels | ||||
| 			channel, err := b.getChannelByID(ev.Channel) | ||||
| 			rmsg, err := b.handleMessageEvent(ev) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("%#v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			m := &MMMessage{} | ||||
| 			if ev.BotID == "" && ev.SubType != "message_deleted" && ev.SubType != "file_comment" { | ||||
| 				user, err := b.rtm.GetUserInfo(ev.User) | ||||
| 				if err != nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				m.UserID = user.ID | ||||
| 				m.Username = user.Name | ||||
| 				if user.Profile.DisplayName != "" { | ||||
| 					m.Username = user.Profile.DisplayName | ||||
| 				} | ||||
| 			} | ||||
| 			m.Channel = channel.Name | ||||
| 			m.Text = ev.Text | ||||
| 			if m.Text == "" { | ||||
| 				for _, attach := range ev.Attachments { | ||||
| 					if attach.Text != "" { | ||||
| 						m.Text = attach.Text | ||||
| 					} else { | ||||
| 						m.Text = attach.Fallback | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			m.Raw = ev | ||||
| 			m.Text = b.replaceMention(m.Text) | ||||
| 			m.Text = b.replaceVariable(m.Text) | ||||
| 			m.Text = b.replaceChannel(m.Text) | ||||
| 			// when using webhookURL we can't check if it's our webhook or not for now | ||||
| 			if ev.BotID != "" && b.Config.WebhookURL == "" { | ||||
| 				bot, err := b.rtm.GetBotInfo(ev.BotID) | ||||
| 				if err != nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				if bot.Name != "" { | ||||
| 					m.Username = bot.Name | ||||
| 					if ev.Username != "" { | ||||
| 						m.Username = ev.Username | ||||
| 					} | ||||
| 					m.UserID = bot.ID | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if ev.SubType == "file_comment" { | ||||
| 				m.Username = "system" | ||||
| 			} | ||||
|  | ||||
| 			mchan <- m | ||||
| 			messages <- rmsg | ||||
| 		case *slack.OutgoingErrorEvent: | ||||
| 			flog.Debugf("%#v", ev.Error()) | ||||
| 			b.Log.Debugf("%#v", ev.Error()) | ||||
| 		case *slack.ChannelJoinedEvent: | ||||
| 			b.Users, _ = b.sc.GetUsers() | ||||
| 			b.Usergroups, _ = b.sc.GetUserGroups() | ||||
| 		case *slack.ConnectedEvent: | ||||
| 			b.channels = ev.Info.Channels | ||||
| 			b.si = ev.Info | ||||
| 			b.Users, _ = b.sc.GetUsers() | ||||
| 			b.Usergroups, _ = b.sc.GetUserGroups() | ||||
| 			// add private channels | ||||
| 			groups, _ := b.sc.GetGroups(true) | ||||
| 			for _, g := range groups { | ||||
| @@ -431,29 +313,22 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| 				b.channels = append(b.channels, *channel) | ||||
| 			} | ||||
| 		case *slack.InvalidAuthEvent: | ||||
| 			flog.Fatalf("Invalid Token %#v", ev) | ||||
| 			b.Log.Fatalf("Invalid Token %#v", ev) | ||||
| 		case *slack.ConnectionErrorEvent: | ||||
| 			flog.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj) | ||||
| 			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj) | ||||
| 		default: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleMatterHook(mchan chan *MMMessage) { | ||||
| func (b *Bslack) handleMatterHook(messages chan *config.Message) { | ||||
| 	for { | ||||
| 		message := b.mh.Receive() | ||||
| 		flog.Debugf("receiving from matterhook (slack) %#v", message) | ||||
| 		m := &MMMessage{} | ||||
| 		m.Username = message.UserName | ||||
| 		m.Text = message.Text | ||||
| 		m.Text = b.replaceMention(m.Text) | ||||
| 		m.Text = b.replaceVariable(m.Text) | ||||
| 		m.Text = b.replaceChannel(m.Text) | ||||
| 		m.Channel = message.ChannelName | ||||
| 		if m.Username == "slackbot" { | ||||
| 		b.Log.Debugf("receiving from matterhook (slack) %#v", message) | ||||
| 		if message.UserName == "slackbot" { | ||||
| 			continue | ||||
| 		} | ||||
| 		mchan <- m | ||||
| 		messages <- &config.Message{Username: message.UserName, Text: message.Text, Channel: message.ChannelName} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -469,9 +344,20 @@ func (b *Bslack) userName(id string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| /* | ||||
| func (b *Bslack) userGroupName(id string) string { | ||||
| 	for _, u := range b.Usergroups { | ||||
| 		if u.ID == id { | ||||
| 			return u.Name | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| */ | ||||
|  | ||||
| // @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users | ||||
| func (b *Bslack) replaceMention(text string) string { | ||||
| 	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1) | ||||
| 	results := regexp.MustCompile(`<@([a-zA-Z0-9]+)>`).FindAllStringSubmatch(text, -1) | ||||
| 	for _, r := range results { | ||||
| 		text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1) | ||||
| 	} | ||||
| @@ -489,9 +375,13 @@ func (b *Bslack) replaceChannel(text string) string { | ||||
|  | ||||
| // @see https://api.slack.com/docs/message-formatting#variables | ||||
| func (b *Bslack) replaceVariable(text string) string { | ||||
| 	results := regexp.MustCompile(`<!([a-zA-Z0-9]+)(\|.+?)?>`).FindAllStringSubmatch(text, -1) | ||||
| 	results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1) | ||||
| 	for _, r := range results { | ||||
| 		text = strings.Replace(text, r[0], "@"+r[1], -1) | ||||
| 		if r[2] != "" { | ||||
| 			text = strings.Replace(text, r[0], "@"+r[2], -1) | ||||
| 		} else { | ||||
| 			text = strings.Replace(text, r[0], "@"+r[1], -1) | ||||
| 		} | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
| @@ -500,7 +390,11 @@ func (b *Bslack) replaceVariable(text string) string { | ||||
| func (b *Bslack) replaceURL(text string) string { | ||||
| 	results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1) | ||||
| 	for _, r := range results { | ||||
| 		text = strings.Replace(text, r[0], r[1], -1) | ||||
| 		if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank | ||||
| 			text = strings.Replace(text, r[0], "", -1) | ||||
| 		} else { | ||||
| 			text = strings.Replace(text, r[0], r[1], -1) | ||||
| 		} | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
| @@ -528,23 +422,263 @@ func (b *Bslack) createAttach(extra map[string][]interface{}) []slack.Attachment | ||||
| 	return attachs | ||||
| } | ||||
|  | ||||
| func (b *Bslack) downloadFile(url string) (*[]byte, error) { | ||||
| 	var buf bytes.Buffer | ||||
| 	client := &http.Client{ | ||||
| 		Timeout: time.Second * 5, | ||||
| // handleDownloadFile handles file download | ||||
| func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File) error { | ||||
| 	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 	// limit to 1MB for now | ||||
| 	comment := "" | ||||
| 	results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(rmsg.Text, -1) | ||||
| 	if len(results) > 0 { | ||||
| 		comment = results[0][1] | ||||
| 	} | ||||
| 	req, err := http.NewRequest("GET", url, nil) | ||||
|  | ||||
| 	err := helper.HandleDownloadSize(b.Log, rmsg, file.Name, int64(file.Size), b.General) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return err | ||||
| 	} | ||||
| 	req.Header.Add("Authorization", "Bearer "+b.Config.Token) | ||||
| 	resp, err := client.Do(req) | ||||
| 	// actually download the file | ||||
| 	data, err := helper.DownloadFileAuth(file.URLPrivateDownload, "Bearer "+b.GetString("Token")) | ||||
| 	if err != nil { | ||||
| 		resp.Body.Close() | ||||
| 		return nil, err | ||||
| 		return fmt.Errorf("download %s failed %#v", file.URLPrivateDownload, err) | ||||
| 	} | ||||
| 	io.Copy(&buf, resp.Body) | ||||
| 	data := buf.Bytes() | ||||
| 	resp.Body.Close() | ||||
| 	return &data, nil | ||||
| 	// add the downloaded data to the message | ||||
| 	helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bslack) handleUploadFile(msg *config.Message, channelID string) (string, error) { | ||||
| 	var err error | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		_, err = b.sc.UploadFile(slack.FileUploadParameters{ | ||||
| 			Reader:         bytes.NewReader(*fi.Data), | ||||
| 			Filename:       fi.Name, | ||||
| 			Channels:       []string{channelID}, | ||||
| 			InitialComment: fi.Comment, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("uploadfile %#v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // handleMessageEvent handles the message events | ||||
| func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, error) { | ||||
| 	// update the userlist on a channel_join | ||||
| 	if ev.SubType == "channel_join" { | ||||
| 		b.Users, _ = b.sc.GetUsers() | ||||
| 	} | ||||
|  | ||||
| 	// Edit message | ||||
| 	if !b.GetBool("EditDisable") && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp { | ||||
| 		b.Log.Debugf("SubMessage %#v", ev.SubMessage) | ||||
| 		ev.User = ev.SubMessage.User | ||||
| 		ev.Text = ev.SubMessage.Text + b.GetString("EditSuffix") | ||||
| 	} | ||||
|  | ||||
| 	// use our own func because rtm.GetChannelInfo doesn't work for private channels | ||||
| 	channel, err := b.getChannelByID(ev.Channel) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})} | ||||
|  | ||||
| 	// find the user id and name | ||||
| 	if ev.User != "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" { | ||||
| 		user, err := b.rtm.GetUserInfo(ev.User) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		rmsg.UserID = user.ID | ||||
| 		rmsg.Username = user.Name | ||||
| 		if user.Profile.DisplayName != "" { | ||||
| 			rmsg.Username = user.Profile.DisplayName | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// See if we have some text in the attachments | ||||
| 	if rmsg.Text == "" { | ||||
| 		for _, attach := range ev.Attachments { | ||||
| 			if attach.Text != "" { | ||||
| 				if attach.Title != "" { | ||||
| 					rmsg.Text = attach.Title + "\n" | ||||
| 				} | ||||
| 				rmsg.Text += attach.Text | ||||
| 			} else { | ||||
| 				rmsg.Text = attach.Fallback | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// when using webhookURL we can't check if it's our webhook or not for now | ||||
| 	if rmsg.Username == "" && ev.BotID != "" && b.GetString("WebhookURL") == "" { | ||||
| 		bot, err := b.rtm.GetBotInfo(ev.BotID) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if bot.Name != "" { | ||||
| 			rmsg.Username = bot.Name | ||||
| 			if ev.Username != "" { | ||||
| 				rmsg.Username = ev.Username | ||||
| 			} | ||||
| 			rmsg.UserID = bot.ID | ||||
| 		} | ||||
|  | ||||
| 		// fixes issues with matterircd users | ||||
| 		if bot.Name == "Slack API Tester" { | ||||
| 			user, err := b.rtm.GetUserInfo(ev.User) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			rmsg.UserID = user.ID | ||||
| 			rmsg.Username = user.Name | ||||
| 			if user.Profile.DisplayName != "" { | ||||
| 				rmsg.Username = user.Profile.DisplayName | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// file comments are set by the system (because there is no username given) | ||||
| 	if ev.SubType == "file_comment" { | ||||
| 		rmsg.Username = "system" | ||||
| 	} | ||||
|  | ||||
| 	// do we have a /me action | ||||
| 	if ev.SubType == "me_message" { | ||||
| 		rmsg.Event = config.EVENT_USER_ACTION | ||||
| 	} | ||||
|  | ||||
| 	// Handle join/leave | ||||
| 	if ev.SubType == "channel_leave" || ev.SubType == "channel_join" { | ||||
| 		rmsg.Username = "system" | ||||
| 		rmsg.Event = config.EVENT_JOIN_LEAVE | ||||
| 	} | ||||
|  | ||||
| 	// edited messages have a submessage, use this timestamp | ||||
| 	if ev.SubMessage != nil { | ||||
| 		rmsg.ID = "slack " + ev.SubMessage.Timestamp | ||||
| 	} | ||||
|  | ||||
| 	// deleted message event | ||||
| 	if ev.SubType == messageDeleted { | ||||
| 		rmsg.Text = config.EVENT_MSG_DELETE | ||||
| 		rmsg.Event = config.EVENT_MSG_DELETE | ||||
| 		rmsg.ID = "slack " + ev.DeletedTimestamp | ||||
| 	} | ||||
|  | ||||
| 	// topic change event | ||||
| 	if ev.SubType == "channel_topic" || ev.SubType == "channel_purpose" { | ||||
| 		rmsg.Event = config.EVENT_TOPIC_CHANGE | ||||
| 	} | ||||
|  | ||||
| 	// Only deleted messages can have a empty username and text | ||||
| 	if (rmsg.Text == "" || rmsg.Username == "") && ev.SubType != messageDeleted { | ||||
| 		// this is probably a webhook we couldn't resolve | ||||
| 		if ev.BotID != "" { | ||||
| 			return nil, fmt.Errorf("probably an incoming webhook we couldn't resolve (maybe ourselves)") | ||||
| 		} | ||||
| 		return nil, fmt.Errorf("empty message and not a deleted message") | ||||
| 	} | ||||
|  | ||||
| 	// save the attachments, so that we can send them to other slack (compatible) bridges | ||||
| 	if len(ev.Attachments) > 0 { | ||||
| 		rmsg.Extra["slack_attachment"] = append(rmsg.Extra["slack_attachment"], ev.Attachments) | ||||
| 	} | ||||
|  | ||||
| 	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 	if ev.File != nil { | ||||
| 		err := b.handleDownloadFile(&rmsg, ev.File) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("download failed: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &rmsg, nil | ||||
| } | ||||
|  | ||||
| // sendWebhook uses the configured WebhookURL to send the message | ||||
| func (b *Bslack) sendWebhook(msg config.Message) (string, error) { | ||||
| 	// skip events | ||||
| 	if msg.Event != "" { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	if b.GetBool("PrefixMessagesWithNick") { | ||||
| 		msg.Text = msg.Username + msg.Text | ||||
| 	} | ||||
|  | ||||
| 	if msg.Extra != nil { | ||||
| 		// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl")) | ||||
| 			matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: rmsg.Username, Text: rmsg.Text} | ||||
| 			b.mh.Send(matterMessage) | ||||
| 		} | ||||
|  | ||||
| 		// webhook doesn't support file uploads, so we add the url manually | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text += " " + fi.URL | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// if we have native slack_attachments add them | ||||
| 	var attachs []slack.Attachment | ||||
| 	if len(msg.Extra["slack_attachment"]) > 0 { | ||||
| 		for _, attach := range msg.Extra["slack_attachment"] { | ||||
| 			attachs = append(attachs, attach.([]slack.Attachment)...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	iconURL := config.GetIconURL(&msg, b.GetString("iconurl")) | ||||
| 	matterMessage := matterhook.OMessage{IconURL: iconURL, Attachments: attachs, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text} | ||||
| 	if msg.Avatar != "" { | ||||
| 		matterMessage.IconURL = msg.Avatar | ||||
| 	} | ||||
| 	err := b.mh.Send(matterMessage) | ||||
| 	if err != nil { | ||||
| 		b.Log.Error(err) | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // skipMessageEvent skips event that need to be skipped :-) | ||||
| func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool { | ||||
| 	if ev.SubType == "channel_leave" || ev.SubType == "channel_join" { | ||||
| 		return b.GetBool("nosendjoinpart") | ||||
| 	} | ||||
|  | ||||
| 	// ignore pinned items | ||||
| 	if ev.SubType == "pinned_item" || ev.SubType == "unpinned_item" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// do not send messages from ourself | ||||
| 	if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" && ev.Username == b.si.User.Name { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// skip messages we made ourselves | ||||
| 	if len(ev.Attachments) > 0 { | ||||
| 		if ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !b.GetBool("EditDisable") && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp { | ||||
| 		// it seems ev.SubMessage.Edited == nil when slack unfurls | ||||
| 		// do not forward these messages #266 | ||||
| 		if ev.SubMessage.Edited == nil { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -2,36 +2,31 @@ package bsshchat | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/shazow/ssh-chat/sshd" | ||||
| 	"io" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/shazow/ssh-chat/sshd" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type Bsshchat struct { | ||||
| 	r *bufio.Scanner | ||||
| 	w io.WriteCloser | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "sshchat" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bsshchat { | ||||
| 	return &Bsshchat{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bsshchat{Config: cfg} | ||||
| } | ||||
|  | ||||
| func (b *Bsshchat) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	go func() { | ||||
| 		err = sshd.ConnectShell(b.Config.Server, b.Config.Nick, func(r io.Reader, w io.WriteCloser) error { | ||||
| 		err = sshd.ConnectShell(b.GetString("Server"), b.GetString("Nick"), func(r io.Reader, w io.WriteCloser) error { | ||||
| 			b.r = bufio.NewScanner(r) | ||||
| 			b.w = w | ||||
| 			b.r.Scan() | ||||
| @@ -41,10 +36,10 @@ func (b *Bsshchat) Connect() error { | ||||
| 		}) | ||||
| 	}() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -61,7 +56,7 @@ func (b *Bsshchat) Send(msg config.Message) (string, error) { | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.w.Write([]byte(rmsg.Username + rmsg.Text + "\r\n")) | ||||
| @@ -74,6 +69,9 @@ func (b *Bsshchat) Send(msg config.Message) (string, error) { | ||||
| 				} | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text = fi.URL | ||||
| 					if fi.Comment != "" { | ||||
| 						msg.Text = fi.Comment + ": " + fi.URL | ||||
| 					} | ||||
| 				} | ||||
| 				b.w.Write([]byte(msg.Username + msg.Text)) | ||||
| 			} | ||||
| @@ -93,10 +91,10 @@ func (b *Bsshchat) sshchatKeepAlive() chan bool { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				flog.Debugf("PING") | ||||
| 				b.Log.Debugf("PING") | ||||
| 				err := b.xc.PingC2S("", "") | ||||
| 				if err != nil { | ||||
| 					flog.Debugf("PING failed %#v", err) | ||||
| 					b.Log.Debugf("PING failed %#v", err) | ||||
| 				} | ||||
| 			case <-done: | ||||
| 				return | ||||
| @@ -123,6 +121,10 @@ func (b *Bsshchat) handleSshChat() error { | ||||
| 	wait := true | ||||
| 	for { | ||||
| 		if b.r.Scan() { | ||||
| 			// ignore messages from ourselves | ||||
| 			if !strings.Contains(b.r.Text(), "\033[K") { | ||||
| 				continue | ||||
| 			} | ||||
| 			res := strings.Split(stripPrompt(b.r.Text()), ":") | ||||
| 			if res[0] == "-> Set theme" { | ||||
| 				wait = false | ||||
| @@ -130,7 +132,7 @@ func (b *Bsshchat) handleSshChat() error { | ||||
| 				continue | ||||
| 			} | ||||
| 			if !wait { | ||||
| 				flog.Debugf("message %#v", res) | ||||
| 				b.Log.Debugf("<= Message %#v", res) | ||||
| 				rmsg := config.Message{Username: res[0], Text: strings.Join(res[1:], ":"), Channel: "sshchat", Account: b.Account, UserID: "nick"} | ||||
| 				b.Remote <- rmsg | ||||
| 			} | ||||
|   | ||||
| @@ -2,11 +2,13 @@ package bsteam | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/Philipp15b/go-steam" | ||||
| 	"github.com/Philipp15b/go-steam/protocol/steamlang" | ||||
| 	"github.com/Philipp15b/go-steam/steamid" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	//"io/ioutil" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| @@ -18,31 +20,24 @@ type Bsteam struct { | ||||
| 	connected chan struct{} | ||||
| 	userMap   map[steamid.SteamId]string | ||||
| 	sync.RWMutex | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "steam" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bsteam { | ||||
| 	b := &Bsteam{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bsteam{Config: cfg} | ||||
| 	b.userMap = make(map[steamid.SteamId]string) | ||||
| 	b.connected = make(chan struct{}) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bsteam) Connect() error { | ||||
| 	flog.Info("Connecting") | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c = steam.NewClient() | ||||
| 	go b.handleEvents() | ||||
| 	go b.c.Connect() | ||||
| 	select { | ||||
| 	case <-b.connected: | ||||
| 		flog.Info("Connection succeeded") | ||||
| 		b.Log.Info("Connection succeeded") | ||||
| 	case <-time.After(time.Second * 30): | ||||
| 		return fmt.Errorf("connection timed out") | ||||
| 	} | ||||
| @@ -73,6 +68,30 @@ func (b *Bsteam) Send(msg config.Message) (string, error) { | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Handle files | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.Comment != "" { | ||||
| 					msg.Text += fi.Comment + ": " | ||||
| 				} | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text = fi.URL | ||||
| 					if fi.Comment != "" { | ||||
| 						msg.Text = fi.Comment + ": " + fi.URL | ||||
| 					} | ||||
| 				} | ||||
| 				b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) | ||||
| 			} | ||||
| 			return "", nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) | ||||
| 	return "", nil | ||||
| } | ||||
| @@ -88,18 +107,18 @@ func (b *Bsteam) getNick(id steamid.SteamId) string { | ||||
|  | ||||
| func (b *Bsteam) handleEvents() { | ||||
| 	myLoginInfo := new(steam.LogOnDetails) | ||||
| 	myLoginInfo.Username = b.Config.Login | ||||
| 	myLoginInfo.Password = b.Config.Password | ||||
| 	myLoginInfo.AuthCode = b.Config.AuthCode | ||||
| 	myLoginInfo.Username = b.GetString("Login") | ||||
| 	myLoginInfo.Password = b.GetString("Password") | ||||
| 	myLoginInfo.AuthCode = b.GetString("AuthCode") | ||||
| 	// Attempt to read existing auth hash to avoid steam guard. | ||||
| 	// Maybe works | ||||
| 	//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry") | ||||
| 	for event := range b.c.Events() { | ||||
| 		//flog.Info(event) | ||||
| 		//b.Log.Info(event) | ||||
| 		switch e := event.(type) { | ||||
| 		case *steam.ChatMsgEvent: | ||||
| 			flog.Debugf("Receiving ChatMsgEvent: %#v", e) | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account) | ||||
| 			b.Log.Debugf("Receiving ChatMsgEvent: %#v", e) | ||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account) | ||||
| 			var channel int64 | ||||
| 			if e.ChatRoomId == 0 { | ||||
| 				channel = int64(e.ChatterId) | ||||
| @@ -110,7 +129,7 @@ func (b *Bsteam) handleEvents() { | ||||
| 			msg := config.Message{Username: b.getNick(e.ChatterId), Text: e.Message, Channel: strconv.FormatInt(channel, 10), Account: b.Account, UserID: strconv.FormatInt(int64(e.ChatterId), 10)} | ||||
| 			b.Remote <- msg | ||||
| 		case *steam.PersonaStateEvent: | ||||
| 			flog.Debugf("PersonaStateEvent: %#v\n", e) | ||||
| 			b.Log.Debugf("PersonaStateEvent: %#v\n", e) | ||||
| 			b.Lock() | ||||
| 			b.userMap[e.FriendId] = e.Name | ||||
| 			b.Unlock() | ||||
| @@ -118,47 +137,47 @@ func (b *Bsteam) handleEvents() { | ||||
| 			b.c.Auth.LogOn(myLoginInfo) | ||||
| 		case *steam.MachineAuthUpdateEvent: | ||||
| 			/* | ||||
| 				flog.Info("authupdate", e) | ||||
| 				flog.Info("hash", e.Hash) | ||||
| 				b.Log.Info("authupdate", e) | ||||
| 				b.Log.Info("hash", e.Hash) | ||||
| 				ioutil.WriteFile("sentry", e.Hash, 0666) | ||||
| 			*/ | ||||
| 		case *steam.LogOnFailedEvent: | ||||
| 			flog.Info("Logon failed", e) | ||||
| 			b.Log.Info("Logon failed", e) | ||||
| 			switch e.Result { | ||||
| 			case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode: | ||||
| 				{ | ||||
| 					flog.Info("Steam guard isn't letting me in! Enter 2FA code:") | ||||
| 					b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:") | ||||
| 					var code string | ||||
| 					fmt.Scanf("%s", &code) | ||||
| 					myLoginInfo.TwoFactorCode = code | ||||
| 				} | ||||
| 			case steamlang.EResult_AccountLogonDenied: | ||||
| 				{ | ||||
| 					flog.Info("Steam guard isn't letting me in! Enter auth code:") | ||||
| 					b.Log.Info("Steam guard isn't letting me in! Enter auth code:") | ||||
| 					var code string | ||||
| 					fmt.Scanf("%s", &code) | ||||
| 					myLoginInfo.AuthCode = code | ||||
| 				} | ||||
| 			default: | ||||
| 				log.Errorf("LogOnFailedEvent: %#v ", e.Result) | ||||
| 				b.Log.Errorf("LogOnFailedEvent: %#v ", e.Result) | ||||
| 				// TODO: Handle EResult_InvalidLoginAuthCode | ||||
| 				return | ||||
| 			} | ||||
| 		case *steam.LoggedOnEvent: | ||||
| 			flog.Debugf("LoggedOnEvent: %#v", e) | ||||
| 			b.Log.Debugf("LoggedOnEvent: %#v", e) | ||||
| 			b.connected <- struct{}{} | ||||
| 			flog.Debugf("setting online") | ||||
| 			b.Log.Debugf("setting online") | ||||
| 			b.c.Social.SetPersonaState(steamlang.EPersonaState_Online) | ||||
| 		case *steam.DisconnectedEvent: | ||||
| 			flog.Info("Disconnected") | ||||
| 			flog.Info("Attempting to reconnect...") | ||||
| 			b.Log.Info("Disconnected") | ||||
| 			b.Log.Info("Attempting to reconnect...") | ||||
| 			b.c.Connect() | ||||
| 		case steam.FatalErrorEvent: | ||||
| 			flog.Error(e) | ||||
| 			b.Log.Error(e) | ||||
| 		case error: | ||||
| 			flog.Error(e) | ||||
| 			b.Log.Error(e) | ||||
| 		default: | ||||
| 			flog.Debugf("unknown event %#v", e) | ||||
| 			b.Log.Debugf("unknown event %#v", e) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -2,15 +2,16 @@ package btelegram | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"github.com/russross/blackfriday" | ||||
| 	"html" | ||||
|  | ||||
| 	"github.com/russross/blackfriday" | ||||
| ) | ||||
|  | ||||
| type customHtml struct { | ||||
| type customHTML struct { | ||||
| 	blackfriday.Renderer | ||||
| } | ||||
|  | ||||
| func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| func (options *customHTML) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| 	marker := out.Len() | ||||
|  | ||||
| 	if !text() { | ||||
| @@ -20,32 +21,32 @@ func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| 	out.WriteString("\n") | ||||
| } | ||||
|  | ||||
| func (options *customHtml) BlockCode(out *bytes.Buffer, text []byte, lang string) { | ||||
| func (options *customHTML) BlockCode(out *bytes.Buffer, text []byte, lang string) { | ||||
| 	out.WriteString("<pre>") | ||||
|  | ||||
| 	out.WriteString(html.EscapeString(string(text))) | ||||
| 	out.WriteString("</pre>\n") | ||||
| } | ||||
|  | ||||
| func (options *customHtml) Header(out *bytes.Buffer, text func() bool, level int, id string) { | ||||
| func (options *customHTML) Header(out *bytes.Buffer, text func() bool, level int, id string) { | ||||
| 	options.Paragraph(out, text) | ||||
| } | ||||
|  | ||||
| func (options *customHtml) HRule(out *bytes.Buffer) { | ||||
| func (options *customHTML) HRule(out *bytes.Buffer) { | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *customHtml) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| func (options *customHTML) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("> ") | ||||
| 	out.Write(text) | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *customHtml) List(out *bytes.Buffer, text func() bool, flags int) { | ||||
| func (options *customHTML) List(out *bytes.Buffer, text func() bool, flags int) { | ||||
| 	options.Paragraph(out, text) | ||||
| } | ||||
|  | ||||
| func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
| func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
| 	out.WriteString("- ") | ||||
| 	out.Write(text) | ||||
| 	out.WriteByte('\n') | ||||
| @@ -53,7 +54,7 @@ func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
|  | ||||
| func makeHTML(input string) string { | ||||
| 	return string(blackfriday.Markdown([]byte(input), | ||||
| 		&customHtml{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")}, | ||||
| 		&customHTML{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")}, | ||||
| 		blackfriday.EXTENSION_NO_INTRA_EMPHASIS| | ||||
| 			blackfriday.EXTENSION_FENCED_CODE| | ||||
| 			blackfriday.EXTENSION_AUTOLINK| | ||||
|   | ||||
| @@ -1,56 +1,49 @@ | ||||
| package btelegram | ||||
|  | ||||
| import ( | ||||
| 	"html" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/go-telegram-bot-api/telegram-bot-api" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type Btelegram struct { | ||||
| 	c *tgbotapi.BotAPI | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| 	avatarMap map[string]string // keep cache of userid and avatar sha | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "telegram" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Btelegram { | ||||
| 	return &Btelegram{BridgeConfig: cfg, avatarMap: make(map[string]string)} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Btelegram{Config: cfg, avatarMap: make(map[string]string)} | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	b.c, err = tgbotapi.NewBotAPI(b.Config.Token) | ||||
| 	b.Log.Info("Connecting") | ||||
| 	b.c, err = tgbotapi.NewBotAPI(b.GetString("Token")) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	u := tgbotapi.NewUpdate(0) | ||||
| 	u.Timeout = 60 | ||||
| 	updates, err := b.c.GetUpdatesChan(u) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go b.handleRecv(updates) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Disconnect() error { | ||||
| 	return nil | ||||
|  | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error { | ||||
| @@ -58,7 +51,9 @@ func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error { | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// get the chatid | ||||
| 	chatid, err := strconv.ParseInt(msg.Channel, 10, 64) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| @@ -66,20 +61,14 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
|  | ||||
| 	// map the file SHA to our user (caches the avatar) | ||||
| 	if msg.Event == config.EVENT_AVATAR_DOWNLOAD { | ||||
| 		fi := msg.Extra["file"][0].(config.FileInfo) | ||||
| 		/* if we have a sha we have successfully uploaded the file to the media server, | ||||
| 		so we can now cache the sha */ | ||||
| 		if fi.SHA != "" { | ||||
| 			flog.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID) | ||||
| 			b.avatarMap[msg.UserID] = fi.SHA | ||||
| 		} | ||||
| 		return "", nil | ||||
| 		return b.cacheAvatar(&msg) | ||||
| 	} | ||||
|  | ||||
| 	if b.Config.MessageFormat == "HTML" { | ||||
| 	if b.GetString("MessageFormat") == "HTML" { | ||||
| 		msg.Text = makeHTML(msg.Text) | ||||
| 	} | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| @@ -92,21 +81,40 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.sendMessage(chatid, rmsg.Username, rmsg.Text) | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			b.handleUploadFile(&msg, chatid) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// edit the message if we have a msg ID | ||||
| 	if msg.ID != "" { | ||||
| 		msgid, err := strconv.Atoi(msg.ID) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if strings.ToLower(b.GetString("MessageFormat")) == "htmlnick" { | ||||
| 			b.Log.Debug("Using mode HTML - nick only") | ||||
| 			msg.Text = html.EscapeString(msg.Text) | ||||
| 		} | ||||
| 		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text) | ||||
| 		if b.Config.MessageFormat == "HTML" { | ||||
| 			flog.Debug("Using mode HTML") | ||||
| 		if b.GetString("MessageFormat") == "HTML" { | ||||
| 			b.Log.Debug("Using mode HTML") | ||||
| 			m.ParseMode = tgbotapi.ModeHTML | ||||
| 		} | ||||
| 		if b.Config.MessageFormat == "Markdown" { | ||||
| 			flog.Debug("Using mode markdown") | ||||
| 		if b.GetString("MessageFormat") == "Markdown" { | ||||
| 			b.Log.Debug("Using mode markdown") | ||||
| 			m.ParseMode = tgbotapi.ModeMarkdown | ||||
| 		} | ||||
| 		if strings.ToLower(b.GetString("MessageFormat")) == "htmlnick" { | ||||
| 			b.Log.Debug("Using mode HTML - nick only") | ||||
| 			m.ParseMode = tgbotapi.ModeHTML | ||||
| 		} | ||||
| 		_, err = b.c.Send(m) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| @@ -114,114 +122,84 @@ func (b *Btelegram) Send(msg config.Message) (string, error) { | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.sendMessage(chatid, rmsg.Username+rmsg.Text) | ||||
| 		} | ||||
| 		// check if we have files to upload (from slack, telegram or mattermost) | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			var c tgbotapi.Chattable | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				file := tgbotapi.FileBytes{fi.Name, *fi.Data} | ||||
| 				re := regexp.MustCompile(".(jpg|png)$") | ||||
| 				if re.MatchString(fi.Name) { | ||||
| 					c = tgbotapi.NewPhotoUpload(chatid, file) | ||||
| 				} else { | ||||
| 					c = tgbotapi.NewDocumentUpload(chatid, file) | ||||
| 				} | ||||
| 				_, err := b.c.Send(c) | ||||
| 				if err != nil { | ||||
| 					log.Errorf("file upload failed: %#v", err) | ||||
| 				} | ||||
| 				if fi.Comment != "" { | ||||
| 					b.sendMessage(chatid, msg.Username+fi.Comment) | ||||
| 				} | ||||
| 			} | ||||
| 			return "", nil | ||||
| 		} | ||||
| 	} | ||||
| 	return b.sendMessage(chatid, msg.Username+msg.Text) | ||||
| 	// Post normal message | ||||
| 	return b.sendMessage(chatid, msg.Username, msg.Text) | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 	for update := range updates { | ||||
| 		flog.Debugf("Receiving from telegram: %#v", update.Message) | ||||
| 		if update.Message == nil { | ||||
| 			flog.Error("Getting nil messages, this shouldn't happen.") | ||||
| 		b.Log.Debugf("== Receiving event: %#v", update.Message) | ||||
|  | ||||
| 		if update.Message == nil && update.ChannelPost == nil && update.EditedMessage == nil && update.EditedChannelPost == nil { | ||||
| 			b.Log.Error("Getting nil messages, this shouldn't happen.") | ||||
| 			continue | ||||
| 		} | ||||
| 		var message *tgbotapi.Message | ||||
| 		username := "" | ||||
| 		channel := "" | ||||
| 		text := "" | ||||
|  | ||||
| 		fmsg := config.Message{Extra: make(map[string][]interface{})} | ||||
| 		var message *tgbotapi.Message | ||||
|  | ||||
| 		rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})} | ||||
|  | ||||
| 		// handle channels | ||||
| 		if update.ChannelPost != nil { | ||||
| 			message = update.ChannelPost | ||||
| 			rmsg.Text = message.Text | ||||
| 		} | ||||
| 		if update.EditedChannelPost != nil && !b.Config.EditDisable { | ||||
|  | ||||
| 		// edited channel message | ||||
| 		if update.EditedChannelPost != nil && !b.GetBool("EditDisable") { | ||||
| 			message = update.EditedChannelPost | ||||
| 			message.Text = message.Text + b.Config.EditSuffix | ||||
| 			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix") | ||||
| 		} | ||||
|  | ||||
| 		// handle groups | ||||
| 		if update.Message != nil { | ||||
| 			message = update.Message | ||||
| 			rmsg.Text = message.Text | ||||
| 		} | ||||
| 		if update.EditedMessage != nil && !b.Config.EditDisable { | ||||
|  | ||||
| 		// edited group message | ||||
| 		if update.EditedMessage != nil && !b.GetBool("EditDisable") { | ||||
| 			message = update.EditedMessage | ||||
| 			message.Text = message.Text + b.Config.EditSuffix | ||||
| 			rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix") | ||||
| 		} | ||||
|  | ||||
| 		// set the ID's from the channel or group message | ||||
| 		rmsg.ID = strconv.Itoa(message.MessageID) | ||||
| 		rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10) | ||||
|  | ||||
| 		// handle username | ||||
| 		if message.From != nil { | ||||
| 			if b.Config.UseFirstName { | ||||
| 				username = message.From.FirstName | ||||
| 			rmsg.UserID = strconv.Itoa(message.From.ID) | ||||
| 			if b.GetBool("UseFirstName") { | ||||
| 				rmsg.Username = message.From.FirstName | ||||
| 			} | ||||
| 			if username == "" { | ||||
| 				username = message.From.UserName | ||||
| 				if username == "" { | ||||
| 					username = message.From.FirstName | ||||
| 			if rmsg.Username == "" { | ||||
| 				rmsg.Username = message.From.UserName | ||||
| 				if rmsg.Username == "" { | ||||
| 					rmsg.Username = message.From.FirstName | ||||
| 				} | ||||
| 			} | ||||
| 			text = message.Text | ||||
| 			channel = strconv.FormatInt(message.Chat.ID, 10) | ||||
| 			// only download avatars if we have a place to upload them (configured mediaserver) | ||||
| 			if b.General.MediaServerUpload != "" { | ||||
| 				b.handleDownloadAvatar(message.From.ID, channel) | ||||
| 				b.handleDownloadAvatar(message.From.ID, rmsg.Channel) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if username == "" { | ||||
| 			username = "unknown" | ||||
| 		} | ||||
| 		if message.Sticker != nil { | ||||
| 			b.handleDownload(message.Sticker, message.Caption, &fmsg) | ||||
| 		} | ||||
| 		if message.Video != nil { | ||||
| 			b.handleDownload(message.Video, message.Caption, &fmsg) | ||||
| 		} | ||||
| 		if message.Photo != nil { | ||||
| 			b.handleDownload(message.Photo, message.Caption, &fmsg) | ||||
| 		} | ||||
| 		if message.Document != nil { | ||||
| 			b.handleDownload(message.Document, message.Caption, &fmsg) | ||||
| 		} | ||||
| 		if message.Voice != nil { | ||||
| 			b.handleDownload(message.Voice, message.Caption, &fmsg) | ||||
| 		} | ||||
| 		if message.Audio != nil { | ||||
| 			b.handleDownload(message.Audio, message.Caption, &fmsg) | ||||
| 		// if we really didn't find a username, set it to unknown | ||||
| 		if rmsg.Username == "" { | ||||
| 			rmsg.Username = "unknown" | ||||
| 		} | ||||
|  | ||||
| 		// If UseInsecureURL is used we'll have a text in fmsg.Text | ||||
| 		if fmsg.Text != "" { | ||||
| 			text = text + fmsg.Text | ||||
| 		// handle any downloads | ||||
| 		err := b.handleDownload(message, &rmsg) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("download failed: %s", err) | ||||
| 		} | ||||
|  | ||||
| 		// handle forwarded messages | ||||
| 		if message.ForwardFrom != nil { | ||||
| 			usernameForward := "" | ||||
| 			if b.Config.UseFirstName { | ||||
| 			if b.GetBool("UseFirstName") { | ||||
| 				usernameForward = message.ForwardFrom.FirstName | ||||
| 			} | ||||
| 			if usernameForward == "" { | ||||
| @@ -233,14 +211,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 			if usernameForward == "" { | ||||
| 				usernameForward = "unknown" | ||||
| 			} | ||||
| 			text = "Forwarded from " + usernameForward + ": " + text | ||||
| 			rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text | ||||
| 		} | ||||
|  | ||||
| 		// quote the previous message | ||||
| 		if message.ReplyToMessage != nil { | ||||
| 			usernameReply := "" | ||||
| 			if message.ReplyToMessage.From != nil { | ||||
| 				if b.Config.UseFirstName { | ||||
| 				if b.GetBool("UseFirstName") { | ||||
| 					usernameReply = message.ReplyToMessage.From.FirstName | ||||
| 				} | ||||
| 				if usernameReply == "" { | ||||
| @@ -253,15 +231,21 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 			if usernameReply == "" { | ||||
| 				usernameReply = "unknown" | ||||
| 			} | ||||
| 			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")" | ||||
| 			if !b.GetBool("QuoteDisable") { | ||||
| 				rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if text != "" || len(fmsg.Extra) > 0 { | ||||
| 			avatar := helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General) | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account) | ||||
| 			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra, Avatar: avatar} | ||||
| 			flog.Debugf("Message is %#v", msg) | ||||
| 			b.Remote <- msg | ||||
| 		if rmsg.Text != "" || len(rmsg.Extra) > 0 { | ||||
| 			rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text) | ||||
| 			// channels don't have (always?) user information. see #410 | ||||
| 			if message.From != nil { | ||||
| 				rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General) | ||||
| 			} | ||||
|  | ||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account) | ||||
| 			b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 			b.Remote <- rmsg | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -278,60 +262,42 @@ func (b *Btelegram) getFileDirectURL(id string) string { | ||||
| // sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful. | ||||
| // logs an error message if it fails | ||||
| func (b *Btelegram) handleDownloadAvatar(userid int, channel string) { | ||||
| 	msg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})} | ||||
| 	rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})} | ||||
| 	if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok { | ||||
| 		photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1}) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("Userprofile download failed for %#v %s", userid, err) | ||||
| 			b.Log.Errorf("Userprofile download failed for %#v %s", userid, err) | ||||
| 		} | ||||
|  | ||||
| 		if len(photos.Photos) > 0 { | ||||
| 			photo := photos.Photos[0][0] | ||||
| 			url := b.getFileDirectURL(photo.FileID) | ||||
| 			name := strconv.Itoa(userid) + ".png" | ||||
| 			flog.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize) | ||||
| 			if photo.FileSize <= b.General.MediaDownloadSize { | ||||
| 				data, err := helper.DownloadFile(url) | ||||
| 				if err != nil { | ||||
| 					flog.Errorf("download %s failed %#v", url, err) | ||||
| 				} else { | ||||
| 					flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url)) | ||||
| 					msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, Avatar: true}) | ||||
| 					flog.Debugf("Sending avatar download message from %#v on %s to gateway", userid, b.Account) | ||||
| 					flog.Debugf("Message is %#v", msg) | ||||
| 					b.Remote <- msg | ||||
| 				} | ||||
| 			} else { | ||||
| 				flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, photo.FileSize, b.General.MediaDownloadSize) | ||||
| 			b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize) | ||||
|  | ||||
| 			err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General) | ||||
| 			if err != nil { | ||||
| 				b.Log.Error(err) | ||||
| 				return | ||||
| 			} | ||||
| 			data, err := helper.DownloadFile(url) | ||||
| 			if err != nil { | ||||
| 				b.Log.Errorf("download %s failed %#v", url, err) | ||||
| 				return | ||||
| 			} | ||||
| 			helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General) | ||||
| 			b.Remote <- rmsg | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleDownload(file interface{}, comment string, msg *config.Message) { | ||||
| // handleDownloadFile handles file download | ||||
| func (b *Btelegram) handleDownload(message *tgbotapi.Message, rmsg *config.Message) error { | ||||
| 	size := 0 | ||||
| 	url := "" | ||||
| 	name := "" | ||||
| 	text := "" | ||||
| 	fileid := "" | ||||
| 	switch v := file.(type) { | ||||
| 	case *tgbotapi.Audio: | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		fileid = v.FileID | ||||
| 	case *tgbotapi.Voice: | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		if !strings.HasSuffix(name, ".ogg") { | ||||
| 			name = name + ".ogg" | ||||
| 		} | ||||
| 		fileid = v.FileID | ||||
| 	case *tgbotapi.Sticker: | ||||
| 	var url, name, text string | ||||
|  | ||||
| 	if message.Sticker != nil { | ||||
| 		v := message.Sticker | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| @@ -340,63 +306,136 @@ func (b *Btelegram) handleDownload(file interface{}, comment string, msg *config | ||||
| 			name = name + ".webp" | ||||
| 		} | ||||
| 		text = " " + url | ||||
| 		fileid = v.FileID | ||||
| 	case *tgbotapi.Video: | ||||
| 	} | ||||
| 	if message.Video != nil { | ||||
| 		v := message.Video | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		fileid = v.FileID | ||||
| 	case *[]tgbotapi.PhotoSize: | ||||
| 		photos := *v | ||||
| 	} | ||||
| 	if message.Photo != nil { | ||||
| 		photos := *message.Photo | ||||
| 		size = photos[len(photos)-1].FileSize | ||||
| 		url = b.getFileDirectURL(photos[len(photos)-1].FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 	case *tgbotapi.Document: | ||||
| 	} | ||||
| 	if message.Document != nil { | ||||
| 		v := message.Document | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		name = v.FileName | ||||
| 		text = " " + v.FileName + " : " + url | ||||
| 		fileid = v.FileID | ||||
| 	} | ||||
| 	if b.Config.UseInsecureURL { | ||||
| 		flog.Debugf("Setting message text to :%s", text) | ||||
| 		msg.Text = text | ||||
| 		return | ||||
| 	if message.Voice != nil { | ||||
| 		v := message.Voice | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 		if !strings.HasSuffix(name, ".ogg") { | ||||
| 			name = name + ".ogg" | ||||
| 		} | ||||
| 	} | ||||
| 	if message.Audio != nil { | ||||
| 		v := message.Audio | ||||
| 		size = v.FileSize | ||||
| 		url = b.getFileDirectURL(v.FileID) | ||||
| 		urlPart := strings.Split(url, "/") | ||||
| 		name = urlPart[len(urlPart)-1] | ||||
| 		text = " " + url | ||||
| 	} | ||||
| 	// if name is empty we didn't match a thing to download | ||||
| 	if name == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	// use the URL instead of native upload | ||||
| 	if b.GetBool("UseInsecureURL") { | ||||
| 		b.Log.Debugf("Setting message text to :%s", text) | ||||
| 		rmsg.Text = rmsg.Text + text | ||||
| 		return nil | ||||
| 	} | ||||
| 	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra | ||||
| 	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size) | ||||
| 	if size <= b.General.MediaDownloadSize { | ||||
| 		data, err := helper.DownloadFile(url) | ||||
| 		if err != nil { | ||||
| 			flog.Errorf("download %s failed %#v", url, err) | ||||
| 		} else { | ||||
| 			flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url)) | ||||
| 			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, Comment: comment}) | ||||
| 		} | ||||
| 	} else { | ||||
| 		flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, b.General.MediaDownloadSize) | ||||
| 		msg.Event = config.EVENT_FILE_FAILURE_SIZE | ||||
| 		msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: name, Comment: comment, Size: int64(size)}) | ||||
| 	err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	data, err := helper.DownloadFile(url) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) { | ||||
| 	m := tgbotapi.NewMessage(chatid, text) | ||||
| 	if b.Config.MessageFormat == "HTML" { | ||||
| 		flog.Debug("Using mode HTML") | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) (string, error) { | ||||
| 	var c tgbotapi.Chattable | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		file := tgbotapi.FileBytes{fi.Name, *fi.Data} | ||||
| 		re := regexp.MustCompile(".(jpg|png)$") | ||||
| 		if re.MatchString(fi.Name) { | ||||
| 			c = tgbotapi.NewPhotoUpload(chatid, file) | ||||
| 		} else { | ||||
| 			c = tgbotapi.NewDocumentUpload(chatid, file) | ||||
| 		} | ||||
| 		_, err := b.c.Send(c) | ||||
| 		if err != nil { | ||||
| 			b.Log.Errorf("file upload failed: %#v", err) | ||||
| 		} | ||||
| 		if fi.Comment != "" { | ||||
| 			b.sendMessage(chatid, msg.Username, fi.Comment) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) { | ||||
| 	m := tgbotapi.NewMessage(chatid, "") | ||||
| 	m.Text = username + text | ||||
| 	if b.GetString("MessageFormat") == "HTML" { | ||||
| 		b.Log.Debug("Using mode HTML") | ||||
| 		m.ParseMode = tgbotapi.ModeHTML | ||||
| 	} | ||||
| 	if b.Config.MessageFormat == "Markdown" { | ||||
| 		flog.Debug("Using mode markdown") | ||||
| 	if b.GetString("MessageFormat") == "Markdown" { | ||||
| 		b.Log.Debug("Using mode markdown") | ||||
| 		m.ParseMode = tgbotapi.ModeMarkdown | ||||
| 	} | ||||
| 	if strings.ToLower(b.GetString("MessageFormat")) == "htmlnick" { | ||||
| 		b.Log.Debug("Using mode HTML - nick only") | ||||
| 		m.Text = username + html.EscapeString(text) | ||||
| 		m.ParseMode = tgbotapi.ModeHTML | ||||
| 	} | ||||
| 	res, err := b.c.Send(m) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return strconv.Itoa(res.MessageID), nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) { | ||||
| 	fi := msg.Extra["file"][0].(config.FileInfo) | ||||
| 	/* if we have a sha we have successfully uploaded the file to the media server, | ||||
| 	so we can now cache the sha */ | ||||
| 	if fi.SHA != "" { | ||||
| 		b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID) | ||||
| 		b.avatarMap[msg.UserID] = fi.SHA | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string { | ||||
| 	format := b.GetString("quoteformat") | ||||
| 	if format == "" { | ||||
| 		format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})" | ||||
| 	} | ||||
| 	format = strings.Replace(format, "{MESSAGE}", message, -1) | ||||
| 	format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1) | ||||
| 	format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1) | ||||
| 	return format | ||||
| } | ||||
|   | ||||
| @@ -2,44 +2,38 @@ package bxmpp | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/jpillora/backoff" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
|  | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	"github.com/jpillora/backoff" | ||||
| 	"github.com/matterbridge/go-xmpp" | ||||
| 	"github.com/rs/xid" | ||||
| ) | ||||
|  | ||||
| type Bxmpp struct { | ||||
| 	xc      *xmpp.Client | ||||
| 	xmppMap map[string]string | ||||
| 	*config.BridgeConfig | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "xmpp" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.BridgeConfig) *Bxmpp { | ||||
| 	b := &Bxmpp{BridgeConfig: cfg} | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	b := &Bxmpp{Config: cfg} | ||||
| 	b.xmppMap = make(map[string]string) | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Infof("Connecting %s", b.Config.Server) | ||||
| 	b.Log.Infof("Connecting %s", b.GetString("Server")) | ||||
| 	b.xc, err = b.createXMPP() | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		b.Log.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go func() { | ||||
| 		initial := true | ||||
| 		bf := &backoff.Backoff{ | ||||
| @@ -49,16 +43,16 @@ func (b *Bxmpp) Connect() error { | ||||
| 		} | ||||
| 		for { | ||||
| 			if initial { | ||||
| 				b.handleXmpp() | ||||
| 				b.handleXMPP() | ||||
| 				initial = false | ||||
| 			} | ||||
| 			d := bf.Duration() | ||||
| 			flog.Infof("Disconnected. Reconnecting in %s", d) | ||||
| 			b.Log.Infof("Disconnected. Reconnecting in %s", d) | ||||
| 			time.Sleep(d) | ||||
| 			b.xc, err = b.createXMPP() | ||||
| 			if err == nil { | ||||
| 				b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||
| 				b.handleXmpp() | ||||
| 				b.handleXMPP() | ||||
| 				bf.Reset() | ||||
| 			} | ||||
| 		} | ||||
| @@ -71,59 +65,65 @@ func (b *Bxmpp) Disconnect() error { | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick) | ||||
| 	if channel.Options.Key != "" { | ||||
| 		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) | ||||
| 		b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil) | ||||
| 	} else { | ||||
| 		b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick")) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Send(msg config.Message) (string, error) { | ||||
| 	var msgid = "" | ||||
| 	var msgreplaceid = "" | ||||
| 	// ignore delete messages | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Upload a file (in xmpp case send the upload URL because xmpp has no native upload support) | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.Config.Muc, Text: rmsg.Username + rmsg.Text}) | ||||
| 			b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.GetString("Muc"), Text: rmsg.Username + rmsg.Text}) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			for _, f := range msg.Extra["file"] { | ||||
| 				fi := f.(config.FileInfo) | ||||
| 				if fi.Comment != "" { | ||||
| 					msg.Text += fi.Comment + ": " | ||||
| 				} | ||||
| 				if fi.URL != "" { | ||||
| 					msg.Text += fi.URL | ||||
| 				} | ||||
| 				b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) | ||||
| 			} | ||||
| 			return "", nil | ||||
| 			return b.handleUploadFile(&msg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) | ||||
| 	return "", nil | ||||
| 	msgid = xid.New().String() | ||||
| 	if msg.ID != "" { | ||||
| 		msgid = msg.ID | ||||
| 		msgreplaceid = msg.ID | ||||
| 	} | ||||
| 	// Post normal message | ||||
| 	_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text, ID: msgid, ReplaceID: msgreplaceid}) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return msgid, nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||
| 	tc := new(tls.Config) | ||||
| 	tc.InsecureSkipVerify = b.Config.SkipTLSVerify | ||||
| 	tc.ServerName = strings.Split(b.Config.Server, ":")[0] | ||||
| 	tc.InsecureSkipVerify = b.GetBool("SkipTLSVerify") | ||||
| 	tc.ServerName = strings.Split(b.GetString("Server"), ":")[0] | ||||
| 	options := xmpp.Options{ | ||||
| 		Host:      b.Config.Server, | ||||
| 		User:      b.Config.Jid, | ||||
| 		Password:  b.Config.Password, | ||||
| 		NoTLS:     true, | ||||
| 		StartTLS:  true, | ||||
| 		TLSConfig: tc, | ||||
|  | ||||
| 		//StartTLS:      false, | ||||
| 		Debug:                        b.General.Debug, | ||||
| 		Host:                         b.GetString("Server"), | ||||
| 		User:                         b.GetString("Jid"), | ||||
| 		Password:                     b.GetString("Password"), | ||||
| 		NoTLS:                        true, | ||||
| 		StartTLS:                     true, | ||||
| 		TLSConfig:                    tc, | ||||
| 		Debug:                        b.GetBool("debug"), | ||||
| 		Logger:                       b.Log.Writer(), | ||||
| 		Session:                      true, | ||||
| 		Status:                       "", | ||||
| 		StatusMessage:                "", | ||||
| 		Resource:                     "", | ||||
| 		InsecureAllowUnencryptedAuth: false, | ||||
| 		//InsecureAllowUnencryptedAuth: true, | ||||
| 	} | ||||
| 	var err error | ||||
| 	b.xc, err = options.NewClient() | ||||
| @@ -138,10 +138,10 @@ func (b *Bxmpp) xmppKeepAlive() chan bool { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				flog.Debugf("PING") | ||||
| 				b.Log.Debugf("PING") | ||||
| 				err := b.xc.PingC2S("", "") | ||||
| 				if err != nil { | ||||
| 					flog.Debugf("PING failed %#v", err) | ||||
| 					b.Log.Debugf("PING failed %#v", err) | ||||
| 				} | ||||
| 			case <-done: | ||||
| 				return | ||||
| @@ -151,11 +151,11 @@ func (b *Bxmpp) xmppKeepAlive() chan bool { | ||||
| 	return done | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) handleXmpp() error { | ||||
| func (b *Bxmpp) handleXMPP() error { | ||||
| 	var ok bool | ||||
| 	var msgid string | ||||
| 	done := b.xmppKeepAlive() | ||||
| 	defer close(done) | ||||
| 	nodelay := time.Time{} | ||||
| 	for { | ||||
| 		m, err := b.xc.Recv() | ||||
| 		if err != nil { | ||||
| @@ -163,25 +163,26 @@ func (b *Bxmpp) handleXmpp() error { | ||||
| 		} | ||||
| 		switch v := m.(type) { | ||||
| 		case xmpp.Chat: | ||||
| 			var channel, nick string | ||||
| 			if v.Type == "groupchat" { | ||||
| 				s := strings.Split(v.Remote, "@") | ||||
| 				if len(s) >= 2 { | ||||
| 					channel = s[0] | ||||
| 				b.Log.Debugf("== Receiving %#v", v) | ||||
| 				// skip invalid messages | ||||
| 				if b.skipMessage(v) { | ||||
| 					continue | ||||
| 				} | ||||
| 				s = strings.Split(s[1], "/") | ||||
| 				if len(s) == 2 { | ||||
| 					nick = s[1] | ||||
| 				msgid = v.ID | ||||
| 				if v.ReplaceID != "" { | ||||
| 					msgid = v.ReplaceID | ||||
| 				} | ||||
| 				if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" && !strings.Contains(v.Text, "</subject>") { | ||||
| 					rmsg := config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account, UserID: v.Remote} | ||||
| 					rmsg.Text, ok = b.replaceAction(rmsg.Text) | ||||
| 					if ok { | ||||
| 						rmsg.Event = config.EVENT_USER_ACTION | ||||
| 					} | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", nick, b.Account) | ||||
| 					b.Remote <- rmsg | ||||
| 				rmsg := config.Message{Username: b.parseNick(v.Remote), Text: v.Text, Channel: b.parseChannel(v.Remote), Account: b.Account, UserID: v.Remote, ID: msgid} | ||||
|  | ||||
| 				// check if we have an action event | ||||
| 				rmsg.Text, ok = b.replaceAction(rmsg.Text) | ||||
| 				if ok { | ||||
| 					rmsg.Event = config.EVENT_USER_ACTION | ||||
| 				} | ||||
| 				b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account) | ||||
| 				b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 				b.Remote <- rmsg | ||||
| 			} | ||||
| 		case xmpp.Presence: | ||||
| 			// do nothing | ||||
| @@ -195,3 +196,71 @@ func (b *Bxmpp) replaceAction(text string) (string, bool) { | ||||
| 	} | ||||
| 	return text, false | ||||
| } | ||||
|  | ||||
| // handleUploadFile handles native upload of files | ||||
| func (b *Bxmpp) handleUploadFile(msg *config.Message) (string, error) { | ||||
| 	var urldesc = "" | ||||
|  | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		if fi.Comment != "" { | ||||
| 			msg.Text += fi.Comment + ": " | ||||
| 		} | ||||
| 		if fi.URL != "" { | ||||
| 			msg.Text = fi.URL | ||||
| 			if fi.Comment != "" { | ||||
| 				msg.Text = fi.Comment + ": " + fi.URL | ||||
| 				urldesc = fi.Comment | ||||
| 			} | ||||
| 		} | ||||
| 		_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text}) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if fi.URL != "" { | ||||
| 			b.xc.SendOOB(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Ooburl: fi.URL, Oobdesc: urldesc}) | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) parseNick(remote string) string { | ||||
| 	s := strings.Split(remote, "@") | ||||
| 	if len(s) > 0 { | ||||
| 		s = strings.Split(s[1], "/") | ||||
| 		if len(s) == 2 { | ||||
| 			return s[1] // nick | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) parseChannel(remote string) string { | ||||
| 	s := strings.Split(remote, "@") | ||||
| 	if len(s) >= 2 { | ||||
| 		return s[0] // channel | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // skipMessage skips messages that need to be skipped | ||||
| func (b *Bxmpp) skipMessage(message xmpp.Chat) bool { | ||||
| 	// skip messages from ourselves | ||||
| 	if b.parseNick(message.Remote) == b.GetString("Nick") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// skip empty messages | ||||
| 	if message.Text == "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// skip subject messages | ||||
| 	if strings.Contains(message.Text, "</subject>") { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// skip delayed messages | ||||
| 	t := time.Time{} | ||||
| 	return message.Stamp != t | ||||
| } | ||||
|   | ||||
							
								
								
									
										170
									
								
								bridge/zulip/zulip.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								bridge/zulip/zulip.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| package bzulip | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/bridge/helper" | ||||
| 	gzb "github.com/matterbridge/gozulipbot" | ||||
| ) | ||||
|  | ||||
| type Bzulip struct { | ||||
| 	q       *gzb.Queue | ||||
| 	bot     *gzb.Bot | ||||
| 	streams map[int]string | ||||
| 	*bridge.Config | ||||
| } | ||||
|  | ||||
| func New(cfg *bridge.Config) bridge.Bridger { | ||||
| 	return &Bzulip{Config: cfg, streams: make(map[int]string)} | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) Connect() error { | ||||
| 	bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")} | ||||
| 	bot.Init() | ||||
| 	q, err := bot.RegisterAll() | ||||
| 	b.q = q | ||||
| 	b.bot = &bot | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("Connect() %#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	// init stream | ||||
| 	b.getChannel(0) | ||||
| 	b.Log.Info("Connection succeeded") | ||||
| 	go b.handleQueue() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) Disconnect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) Send(msg config.Message) (string, error) { | ||||
| 	b.Log.Debugf("=> Receiving %#v", msg) | ||||
|  | ||||
| 	// Delete message | ||||
| 	if msg.Event == config.EVENT_MSG_DELETE { | ||||
| 		if msg.ID == "" { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		_, err := b.bot.UpdateMessage(msg.ID, "") | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Upload a file if it exists | ||||
| 	if msg.Extra != nil { | ||||
| 		for _, rmsg := range helper.HandleExtra(&msg, b.General) { | ||||
| 			b.sendMessage(rmsg) | ||||
| 		} | ||||
| 		if len(msg.Extra["file"]) > 0 { | ||||
| 			return b.handleUploadFile(&msg) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// edit the message if we have a msg ID | ||||
| 	if msg.ID != "" { | ||||
| 		_, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text) | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Post normal message | ||||
| 	return b.sendMessage(msg) | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) getChannel(id int) string { | ||||
| 	if name, ok := b.streams[id]; ok { | ||||
| 		return name | ||||
| 	} | ||||
| 	streams, err := b.bot.GetRawStreams() | ||||
| 	if err != nil { | ||||
| 		b.Log.Errorf("getChannel: %#v", err) | ||||
| 		return "" | ||||
| 	} | ||||
| 	for _, stream := range streams.Streams { | ||||
| 		b.streams[stream.StreamID] = stream.Name | ||||
| 	} | ||||
| 	if name, ok := b.streams[id]; ok { | ||||
| 		return name | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) handleQueue() error { | ||||
| 	for { | ||||
| 		messages, _ := b.q.GetEvents() | ||||
| 		for _, m := range messages { | ||||
| 			b.Log.Debugf("== Receiving %#v", m) | ||||
| 			// ignore our own messages | ||||
| 			if m.SenderEmail == b.GetString("login") { | ||||
| 				continue | ||||
| 			} | ||||
| 			rmsg := config.Message{Username: m.SenderFullName, Text: m.Content, Channel: b.getChannel(m.StreamID), Account: b.Account, UserID: strconv.Itoa(m.SenderID), Avatar: m.AvatarURL} | ||||
| 			b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account) | ||||
| 			b.Log.Debugf("<= Message is %#v", rmsg) | ||||
| 			b.Remote <- rmsg | ||||
| 			b.q.LastEventID = m.ID | ||||
| 		} | ||||
| 		time.Sleep(time.Second * 3) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) sendMessage(msg config.Message) (string, error) { | ||||
| 	topic := "matterbridge" | ||||
| 	if b.GetString("topic") != "" { | ||||
| 		topic = b.GetString("topic") | ||||
| 	} | ||||
| 	m := gzb.Message{ | ||||
| 		Stream:  msg.Channel, | ||||
| 		Topic:   topic, | ||||
| 		Content: msg.Username + msg.Text, | ||||
| 	} | ||||
| 	resp, err := b.bot.Message(m) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if resp != nil { | ||||
| 		defer resp.Body.Close() | ||||
| 		res, err := ioutil.ReadAll(resp.Body) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		var jr struct { | ||||
| 			ID int `json:"id"` | ||||
| 		} | ||||
| 		err = json.Unmarshal(res, &jr) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		return strconv.Itoa(jr.ID), nil | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) { | ||||
| 	for _, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		if fi.Comment != "" { | ||||
| 			msg.Text += fi.Comment + ": " | ||||
| 		} | ||||
| 		if fi.URL != "" { | ||||
| 			msg.Text = fi.URL | ||||
| 			if fi.Comment != "" { | ||||
| 				msg.Text = fi.Comment + ": " + fi.URL | ||||
| 			} | ||||
| 		} | ||||
| 		_, err := b.sendMessage(*msg) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
							
								
								
									
										79
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,82 @@ | ||||
| # v1.11.0 | ||||
|  | ||||
| ## New features | ||||
| * general: Add config option MediaDownloadPath (#443). See `MediaDownloadPath` in matterbridge.toml.sample | ||||
| * general: Add MediaDownloadBlacklist option. Closes #442. See `MediaDownloadBlacklist` in matterbridge.toml.sample | ||||
| * xmpp: Add channel password support for XMPP (#451) | ||||
| * xmpp: Add message correction support for XMPP (#437) | ||||
| * telegram: Add support for MessageFormat=htmlnick (telegram). #444 | ||||
| * mattermost: Add support for mattermost 5.x | ||||
|  | ||||
| ## Enhancements | ||||
| * slack: Add Title from attachment slack message (#446) | ||||
| * irc: Prevent white or black color codes (irc) (#434) | ||||
|  | ||||
| ## Bugfix | ||||
| * slack: Fix regexp in replaceMention (slack). (#435) | ||||
| * irc: Reconnect on quit. (irc) See #431 (#445) | ||||
| * sshchat: Ignore messages from ourself. (sshchat) Closes #439 | ||||
|  | ||||
| # v1.10.1 | ||||
| ## New features | ||||
| * irc: Colorize username sent to IRC using its crc32 IEEE checksum (#423). See `ColorNicks` in matterbridge.toml.sample | ||||
| * irc: Add support for CJK to/from utf-8 (irc). #400 | ||||
| * telegram: Add QuoteFormat option (telegram). Closes #413. See `QuoteFormat` in matterbridge.toml.sample | ||||
| * xmpp: Send attached files to XMPP in different message with OOB data and without body (#421) | ||||
|  | ||||
| ## Bugfix | ||||
| * general: updated irc/xmpp/telegram libraries | ||||
| * mattermost/slack/rocketchat: Fix iconurl regression. Closes #430 | ||||
| * mattermost/slack: Use uuid instead of userid. Fixes #429 | ||||
| * slack: Avatar spoofing from Slack to Discord with uppercase in nick doesn't work (#433) | ||||
| * irc: Fix format string bug (irc) (#428) | ||||
|  | ||||
| # v1.10.0 | ||||
| ## New features | ||||
| * general: Add support for reloading all settings automatically after changing config except connection and gateway configuration. Closes #373 | ||||
| * zulip: New protocol support added (https://zulipchat.com) | ||||
|  | ||||
| ## Enhancements | ||||
| * general: Handle file comment better | ||||
| * steam: Handle file uploads to mediaserver (steam) | ||||
| * slack: Properly set Slack user who initiated slash command (#394) | ||||
|  | ||||
| ## Bugfix | ||||
| * general: Use only alphanumeric for file uploads to mediaserver. Closes #416 | ||||
| * general: Fix crash on invalid filenames | ||||
| * general: Fix regression in ReplaceMessages and ReplaceNicks. Closes #407 | ||||
| * telegram: Fix possible nil when using channels (telegram). #410 | ||||
| * telegram: Fix panic (telegram). Closes #410 | ||||
| * telegram: Handle channel posts correctly | ||||
| * mattermost: Update GetFileLinks to API_V4 | ||||
|  | ||||
| # v1.9.1 | ||||
| ## New features | ||||
| * telegram: Add QuoteDisable option (telegram). Closes #399. See QuoteDisable in matterbridge.toml.sample | ||||
| ## Enhancements | ||||
| * discord: Send mediaserver link to Discord in Webhook mode (discord) (#405) | ||||
| * mattermost: Print list of valid team names when team not found (#390) | ||||
| * slack: Strip markdown URLs with blank text (slack) (#392) | ||||
| ## Bugfix | ||||
| * slack/mattermost: Make our callbackid more unique. Fixes issue with running multiple matterbridge on the same channel (slack,mattermost) | ||||
| * telegram: fix newlines in multiline messages #399 | ||||
| * telegram: Revert #378 | ||||
|  | ||||
| # v1.9.0 (the refactor release) | ||||
| ## New features | ||||
| * general: better debug messages | ||||
| * general: better support for environment variables override | ||||
| * general: Ability to disable sending join/leave messages to other gateways. #382 | ||||
| * slack: Allow Slack @usergroups to be parsed as human-friendly names #379 | ||||
| * slack: Provide better context for shared posts from Slack<=>Slack enhancement #369 | ||||
| * telegram: Convert nicks automatically into HTML when MessageFormat is set to HTML #378 | ||||
| * irc: Add DebugLevel option  | ||||
|  | ||||
| ## Bugfix | ||||
| * slack: Ignore restricted_action on channel join (slack). Closes #387 | ||||
| * slack: Add slack attachment support to matterhook | ||||
| * slack: Update userlist on join (slack). Closes #372 | ||||
|  | ||||
| # v1.8.0 | ||||
| ## New features | ||||
| * general: Send chat notification if media is too big to be re-uploaded to MediaServer. See #359 | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #!/bin/bash | ||||
| go version |grep go1.9 || exit | ||||
| go version |grep go1.10 || exit | ||||
| VERSION=$(git describe --tags) | ||||
| mkdir ci/binaries | ||||
| GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe | ||||
|   | ||||
| @@ -3,17 +3,35 @@ package gateway | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/api" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	bdiscord "github.com/42wim/matterbridge/bridge/discord" | ||||
| 	bgitter "github.com/42wim/matterbridge/bridge/gitter" | ||||
| 	birc "github.com/42wim/matterbridge/bridge/irc" | ||||
| 	bmatrix "github.com/42wim/matterbridge/bridge/matrix" | ||||
| 	bmattermost "github.com/42wim/matterbridge/bridge/mattermost" | ||||
| 	brocketchat "github.com/42wim/matterbridge/bridge/rocketchat" | ||||
| 	bslack "github.com/42wim/matterbridge/bridge/slack" | ||||
| 	bsshchat "github.com/42wim/matterbridge/bridge/sshchat" | ||||
| 	bsteam "github.com/42wim/matterbridge/bridge/steam" | ||||
| 	btelegram "github.com/42wim/matterbridge/bridge/telegram" | ||||
| 	bxmpp "github.com/42wim/matterbridge/bridge/xmpp" | ||||
| 	bzulip "github.com/42wim/matterbridge/bridge/zulip" | ||||
| 	"github.com/hashicorp/golang-lru" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	//	"github.com/davecgh/go-spew/spew" | ||||
| 	"crypto/sha1" | ||||
| 	"github.com/hashicorp/golang-lru" | ||||
| 	"github.com/peterhellberg/emojilib" | ||||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/peterhellberg/emojilib" | ||||
| ) | ||||
|  | ||||
| type Gateway struct { | ||||
| @@ -36,6 +54,22 @@ type BrMsgID struct { | ||||
|  | ||||
| var flog *log.Entry | ||||
|  | ||||
| var bridgeMap = map[string]bridge.Factory{ | ||||
| 	"api":        api.New, | ||||
| 	"discord":    bdiscord.New, | ||||
| 	"gitter":     bgitter.New, | ||||
| 	"irc":        birc.New, | ||||
| 	"mattermost": bmattermost.New, | ||||
| 	"matrix":     bmatrix.New, | ||||
| 	"rocketchat": brocketchat.New, | ||||
| 	"slack":      bslack.New, | ||||
| 	"sshchat":    bsshchat.New, | ||||
| 	"steam":      bsteam.New, | ||||
| 	"telegram":   btelegram.New, | ||||
| 	"xmpp":       bxmpp.New, | ||||
| 	"zulip":      bzulip.New, | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"prefix": "gateway"}) | ||||
| } | ||||
| @@ -52,7 +86,14 @@ func New(cfg config.Gateway, r *Router) *Gateway { | ||||
| func (gw *Gateway) AddBridge(cfg *config.Bridge) error { | ||||
| 	br := gw.Router.getBridge(cfg.Account) | ||||
| 	if br == nil { | ||||
| 		br = bridge.New(gw.Config, cfg, gw.Message) | ||||
| 		br = bridge.New(cfg) | ||||
| 		br.Config = gw.Router.Config | ||||
| 		br.General = &gw.General | ||||
| 		// set logging | ||||
| 		br.Log = log.WithFields(log.Fields{"prefix": "bridge"}) | ||||
| 		brconfig := &bridge.Config{Remote: gw.Message, Log: log.WithFields(log.Fields{"prefix": br.Protocol}), Bridge: br} | ||||
| 		// add the actual bridger for this protocol to this bridge using the bridgeMap | ||||
| 		br.Bridger = bridgeMap[br.Protocol](brconfig) | ||||
| 	} | ||||
| 	gw.mapChannelsToBridge(br) | ||||
| 	gw.Bridges[cfg.Account] = br | ||||
| @@ -168,17 +209,9 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con | ||||
| func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID { | ||||
| 	var brMsgIDs []*BrMsgID | ||||
|  | ||||
| 	// TODO refactor | ||||
| 	// only slack now, check will have to be done in the different bridges. | ||||
| 	// we need to check if we can't use fallback or text in other bridges | ||||
| 	// if we have an attached file, or other info | ||||
| 	if msg.Extra != nil { | ||||
| 		if dest.Protocol != "discord" && | ||||
| 			dest.Protocol != "slack" && | ||||
| 			dest.Protocol != "mattermost" && | ||||
| 			dest.Protocol != "telegram" && | ||||
| 			dest.Protocol != "matrix" && | ||||
| 			dest.Protocol != "xmpp" && | ||||
| 			len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) == 0 { | ||||
| 		if len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) != 0 { | ||||
| 			if msg.Text == "" { | ||||
| 				return brMsgIDs | ||||
| 			} | ||||
| @@ -192,11 +225,14 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM | ||||
| 			return brMsgIDs | ||||
| 		} | ||||
| 	} | ||||
| 	// only relay join/part when configged | ||||
| 	if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { | ||||
|  | ||||
| 	// only relay join/part when configured | ||||
| 	if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].GetBool("ShowJoinPart") { | ||||
| 		return brMsgIDs | ||||
| 	} | ||||
| 	if msg.Event == config.EVENT_TOPIC_CHANGE && !gw.Bridges[dest.Account].Config.ShowTopicChange { | ||||
|  | ||||
| 	// only relay topic change when configured | ||||
| 	if msg.Event == config.EVENT_TOPIC_CHANGE && !gw.Bridges[dest.Account].GetBool("ShowTopicChange") { | ||||
| 		return brMsgIDs | ||||
| 	} | ||||
|  | ||||
| @@ -205,6 +241,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM | ||||
| 		flog.Debug("empty channel") | ||||
| 		return brMsgIDs | ||||
| 	} | ||||
|  | ||||
| 	originchannel := msg.Channel | ||||
| 	origmsg := msg | ||||
| 	channels := gw.getDestChannel(&msg, *dest) | ||||
| @@ -220,7 +257,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		flog.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name) | ||||
| 		flog.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name) | ||||
| 		msg.Channel = channel.Name | ||||
| 		msg.Avatar = gw.modifyAvatar(origmsg, dest) | ||||
| 		msg.Username = gw.modifyUsername(origmsg, dest) | ||||
| @@ -241,10 +278,11 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM | ||||
| 		} | ||||
| 		mID, err := dest.Send(msg) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			flog.Error(err) | ||||
| 		} | ||||
| 		// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice | ||||
| 		if mID != "" { | ||||
| 			flog.Debugf("mID %s: %s", dest.Account, mID) | ||||
| 			brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID, channel.ID}) | ||||
| 		} | ||||
| 	} | ||||
| @@ -256,8 +294,10 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| 	if _, ok := gw.Bridges[msg.Account]; !ok { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// check if we need to ignore a empty message | ||||
| 	if msg.Text == "" { | ||||
| 		// we have an attachment or actual bytes | ||||
| 		// we have an attachment or actual bytes, do not ignore | ||||
| 		if msg.Extra != nil && | ||||
| 			(msg.Extra["attachments"] != nil || | ||||
| 				len(msg.Extra["file"]) > 0 || | ||||
| @@ -267,14 +307,18 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| 		flog.Debugf("ignoring empty message %#v from %s", msg, msg.Account) | ||||
| 		return true | ||||
| 	} | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) { | ||||
|  | ||||
| 	// is the username in IgnoreNicks field | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks")) { | ||||
| 		if msg.Username == entry { | ||||
| 			flog.Debugf("ignoring %s from %s", msg.Username, msg.Account) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// does the message match regex in IgnoreMessages field | ||||
| 	// TODO do not compile regexps everytime | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreMessages) { | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) { | ||||
| 		if entry != "" { | ||||
| 			re, err := regexp.Compile(entry) | ||||
| 			if err != nil { | ||||
| @@ -293,17 +337,17 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string { | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	msg.Protocol = br.Protocol | ||||
| 	if gw.Config.General.StripNick || dest.Config.StripNick { | ||||
| 	if gw.Config.General.StripNick || dest.GetBool("StripNick") { | ||||
| 		re := regexp.MustCompile("[^a-zA-Z0-9]+") | ||||
| 		msg.Username = re.ReplaceAllString(msg.Username, "") | ||||
| 	} | ||||
| 	nick := dest.Config.RemoteNickFormat | ||||
| 	nick := dest.GetString("RemoteNickFormat") | ||||
| 	if nick == "" { | ||||
| 		nick = gw.Config.General.RemoteNickFormat | ||||
| 	} | ||||
|  | ||||
| 	// loop to replace nicks | ||||
| 	for _, outer := range br.Config.ReplaceNicks { | ||||
| 	for _, outer := range br.GetStringSlice2D("ReplaceNicks") { | ||||
| 		search := outer[0] | ||||
| 		replace := outer[1] | ||||
| 		// TODO move compile to bridge init somewhere | ||||
| @@ -327,9 +371,10 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin | ||||
| 		} | ||||
| 		nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1) | ||||
| 	} | ||||
|  | ||||
| 	nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1) | ||||
| 	nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1) | ||||
| 	nick = strings.Replace(nick, "{LABEL}", br.Config.Label, -1) | ||||
| 	nick = strings.Replace(nick, "{LABEL}", br.GetString("Label"), -1) | ||||
| 	nick = strings.Replace(nick, "{NICK}", msg.Username, -1) | ||||
| 	return nick | ||||
| } | ||||
| @@ -337,7 +382,7 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin | ||||
| func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string { | ||||
| 	iconurl := gw.Config.General.IconURL | ||||
| 	if iconurl == "" { | ||||
| 		iconurl = dest.Config.IconURL | ||||
| 		iconurl = dest.GetString("IconURL") | ||||
| 	} | ||||
| 	iconurl = strings.Replace(iconurl, "{NICK}", msg.Username, -1) | ||||
| 	if msg.Avatar == "" { | ||||
| @@ -349,9 +394,10 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string | ||||
| func (gw *Gateway) modifyMessage(msg *config.Message) { | ||||
| 	// replace :emoji: to unicode | ||||
| 	msg.Text = emojilib.Replace(msg.Text) | ||||
|  | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	// loop to replace messages | ||||
| 	for _, outer := range br.Config.ReplaceMessages { | ||||
| 	for _, outer := range br.GetStringSlice2D("ReplaceMessages") { | ||||
| 		search := outer[0] | ||||
| 		replace := outer[1] | ||||
| 		// TODO move compile to bridge init somewhere | ||||
| @@ -369,45 +415,94 @@ func (gw *Gateway) modifyMessage(msg *config.Message) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handleFiles uploads or places all files on the given msg to the MediaServer and | ||||
| // adds the new URL of the file on the MediaServer onto the given msg. | ||||
| func (gw *Gateway) handleFiles(msg *config.Message) { | ||||
| 	if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" { | ||||
| 	reg := regexp.MustCompile("[^a-zA-Z0-9]+") | ||||
|  | ||||
| 	// If we don't have a attachfield or we don't have a mediaserver configured return | ||||
| 	if msg.Extra == nil || (gw.Config.General.MediaServerUpload == "" && gw.Config.General.MediaDownloadPath == "") { | ||||
| 		return | ||||
| 	} | ||||
| 	if len(msg.Extra["file"]) > 0 { | ||||
| 		client := &http.Client{ | ||||
| 			Timeout: time.Second * 5, | ||||
| 		} | ||||
| 		for i, f := range msg.Extra["file"] { | ||||
| 			fi := f.(config.FileInfo) | ||||
| 			sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data)) | ||||
| 			reader := bytes.NewReader(*fi.Data) | ||||
|  | ||||
| 	// If we don't have files, nothing to upload. | ||||
| 	if len(msg.Extra["file"]) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	client := &http.Client{ | ||||
| 		Timeout: time.Second * 5, | ||||
| 	} | ||||
|  | ||||
| 	for i, f := range msg.Extra["file"] { | ||||
| 		fi := f.(config.FileInfo) | ||||
| 		ext := filepath.Ext(fi.Name) | ||||
| 		fi.Name = fi.Name[0 : len(fi.Name)-len(ext)] | ||||
| 		fi.Name = reg.ReplaceAllString(fi.Name, "_") | ||||
| 		fi.Name = fi.Name + ext | ||||
|  | ||||
| 		sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))[:8] | ||||
|  | ||||
| 		if gw.Config.General.MediaServerUpload != "" { | ||||
| 			// Use MediaServerUpload. Upload using a PUT HTTP request and basicauth. | ||||
|  | ||||
| 			url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name | ||||
| 			durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name | ||||
| 			extra := msg.Extra["file"][i].(config.FileInfo) | ||||
| 			extra.URL = durl | ||||
| 			req, _ := http.NewRequest("PUT", url, reader) | ||||
| 			req.Header.Set("Content-Type", "binary/octet-stream") | ||||
| 			_, err := client.Do(req) | ||||
|  | ||||
| 			req, err := http.NewRequest("PUT", url, bytes.NewReader(*fi.Data)) | ||||
| 			if err != nil { | ||||
| 				flog.Errorf("mediaserver upload failed: %#v", err) | ||||
| 				flog.Errorf("mediaserver upload failed, could not create request: %#v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			flog.Debugf("mediaserver download URL = %s", durl) | ||||
| 			// we uploaded the file successfully. Add the SHA | ||||
| 			extra.SHA = sha1sum | ||||
| 			msg.Extra["file"][i] = extra | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getChannelID(msg config.Message) string { | ||||
| 	return msg.Channel + msg.Account | ||||
| 			flog.Debugf("mediaserver upload url: %s", url) | ||||
|  | ||||
| 			req.Header.Set("Content-Type", "binary/octet-stream") | ||||
| 			_, err = client.Do(req) | ||||
| 			if err != nil { | ||||
| 				flog.Errorf("mediaserver upload failed, could not Do request: %#v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 		} else { | ||||
| 			// Use MediaServerPath. Place the file on the current filesystem. | ||||
|  | ||||
| 			dir := gw.Config.General.MediaDownloadPath + "/" + sha1sum | ||||
| 			err := os.Mkdir(dir, os.ModePerm) | ||||
| 			if err != nil && !os.IsExist(err) { | ||||
| 				flog.Errorf("mediaserver path failed, could not mkdir: %s %#v", err, err) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			path := dir + "/" + fi.Name | ||||
| 			flog.Debugf("mediaserver path placing file: %s", path) | ||||
|  | ||||
| 			err = ioutil.WriteFile(path, *fi.Data, os.ModePerm) | ||||
| 			if err != nil { | ||||
| 				flog.Errorf("mediaserver path failed, could not writefile: %s %#v", err, err) | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Download URL. | ||||
| 		durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name | ||||
|  | ||||
| 		flog.Debugf("mediaserver download URL = %s", durl) | ||||
|  | ||||
| 		// We uploaded/placed the file successfully. Add the SHA and URL. | ||||
| 		extra := msg.Extra["file"][i].(config.FileInfo) | ||||
| 		extra.URL = durl | ||||
| 		extra.SHA = sha1sum | ||||
| 		msg.Extra["file"][i] = extra | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) validGatewayDest(msg *config.Message, channel *config.ChannelInfo) bool { | ||||
| 	return msg.Gateway == gw.Name | ||||
| } | ||||
|  | ||||
| func getChannelID(msg config.Message) string { | ||||
| 	return msg.Channel + msg.Account | ||||
| } | ||||
|  | ||||
| func isApi(account string) bool { | ||||
| 	return strings.HasPrefix(account, "api.") | ||||
| } | ||||
|   | ||||
| @@ -2,15 +2,15 @@ package gateway | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/BurntSushi/toml" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| var testconfig = ` | ||||
| var testconfig = []byte(` | ||||
| [irc.freenode] | ||||
| [mattermost.test] | ||||
| [gitter.42wim] | ||||
| @@ -37,9 +37,9 @@ var testconfig = ` | ||||
|     [[gateway.inout]] | ||||
|     account="slack.test" | ||||
|     channel="testing" | ||||
| 	` | ||||
| 	`) | ||||
|  | ||||
| var testconfig2 = ` | ||||
| var testconfig2 = []byte(` | ||||
| [irc.freenode] | ||||
| [mattermost.test] | ||||
| [gitter.42wim] | ||||
| @@ -80,8 +80,9 @@ var testconfig2 = ` | ||||
|     [[gateway.out]] | ||||
|     account = "discord.test" | ||||
|     channel = "general2" | ||||
| 	` | ||||
| var testconfig3 = ` | ||||
| 	`) | ||||
|  | ||||
| var testconfig3 = []byte(` | ||||
| [irc.zzz] | ||||
| [telegram.zzz] | ||||
| [slack.zzz] | ||||
| @@ -149,13 +150,10 @@ enable=true | ||||
|     [[gateway.inout]] | ||||
|     account="telegram.zzz" | ||||
|     channel="--333333333333" | ||||
| ` | ||||
| `) | ||||
|  | ||||
| func maketestRouter(input string) *Router { | ||||
| 	var cfg *config.Config | ||||
| 	if _, err := toml.Decode(input, &cfg); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 	} | ||||
| func maketestRouter(input []byte) *Router { | ||||
| 	cfg := config.NewConfigFromString(input) | ||||
| 	r, err := NewRouter(cfg) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| @@ -163,14 +161,7 @@ func maketestRouter(input string) *Router { | ||||
| 	return r | ||||
| } | ||||
| func TestNewRouter(t *testing.T) { | ||||
| 	var cfg *config.Config | ||||
| 	if _, err := toml.Decode(testconfig, &cfg); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 	} | ||||
| 	r, err := NewRouter(cfg) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 	} | ||||
| 	r := maketestRouter(testconfig) | ||||
| 	assert.Equal(t, 1, len(r.Gateways)) | ||||
| 	assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges)) | ||||
| 	assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels)) | ||||
|   | ||||
| @@ -2,10 +2,10 @@ package gateway | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/gateway/samechannel" | ||||
| 	//log "github.com/sirupsen/logrus" | ||||
| 	samechannelgateway "github.com/42wim/matterbridge/gateway/samechannel" | ||||
| 	//	"github.com/davecgh/go-spew/spew" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -17,10 +17,7 @@ type Router struct { | ||||
| } | ||||
|  | ||||
| func NewRouter(cfg *config.Config) (*Router, error) { | ||||
| 	r := &Router{} | ||||
| 	r.Config = cfg | ||||
| 	r.Message = make(chan config.Message) | ||||
| 	r.Gateways = make(map[string]*Gateway) | ||||
| 	r := &Router{Message: make(chan config.Message), Gateways: make(map[string]*Gateway), Config: cfg} | ||||
| 	sgw := samechannelgateway.New(cfg) | ||||
| 	gwconfigs := sgw.GetConfig() | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package samechannelgateway | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/BurntSushi/toml" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								img/matterbridge.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								img/matterbridge.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 77 KiB | 
| @@ -3,17 +3,18 @@ package main | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/gateway" | ||||
| 	"github.com/google/gops/agent" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	prefixed "github.com/x-cray/logrus-prefixed-formatter" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	version = "1.8.0" | ||||
| 	version = "1.11.0" | ||||
| 	githash string | ||||
| ) | ||||
|  | ||||
| @@ -34,7 +35,7 @@ func main() { | ||||
| 		return | ||||
| 	} | ||||
| 	if *flagDebug || os.Getenv("DEBUG") == "1" { | ||||
| 		log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false}) | ||||
| 		log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true}) | ||||
| 		flog.Info("Enabling debug") | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
|   | ||||
| @@ -64,6 +64,9 @@ NickServPassword="secret" | ||||
| #OPTIONAL only used for quakenet auth | ||||
| NickServUsername="username" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Flood control | ||||
| #Delay in milliseconds between each message send to the IRC server | ||||
| #OPTIONAL (default 1300) | ||||
| @@ -89,6 +92,10 @@ MessageSplit=false | ||||
| #OPTIONAL (default 0) | ||||
| RejoinDelay=0 | ||||
|  | ||||
| #ColorNicks will show each nickname in a different color. | ||||
| #Only works in IRC right now. | ||||
| ColorNicks=false | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| @@ -135,6 +142,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Do not send joins/parts to other bridges | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| NoSendJoinPart=false | ||||
|  | ||||
| #StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285 | ||||
| #It will strip other characters from the nick | ||||
| #OPTIONAL (default false) | ||||
| @@ -179,6 +191,9 @@ Nick="xmppbot" | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| @@ -260,6 +275,9 @@ Muc="conf.hipchat.com" | ||||
| #REQUIRED | ||||
| Nick="yourlogin" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| @@ -377,6 +395,9 @@ IconURL="http://youricon.png" | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in mattermost.  | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| @@ -446,6 +467,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Do not send joins/parts to other bridges | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| NoSendJoinPart=false | ||||
|  | ||||
| #StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285 | ||||
| #It will strip other characters from the nick | ||||
| #OPTIONAL (default false) | ||||
| @@ -472,6 +498,9 @@ ShowTopicChange=false | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| @@ -567,6 +596,9 @@ WebhookBindAddress="0.0.0.0:9999" | ||||
| #OPTIONAL | ||||
| IconURL="https://robohash.org/{NICK}.png?size=48x48" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in slack | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| @@ -636,6 +668,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Do not send joins/parts to other bridges | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| NoSendJoinPart=false | ||||
|  | ||||
| #StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285 | ||||
| #It will strip other characters from the nick | ||||
| #OPTIONAL (default false) | ||||
| @@ -665,6 +702,9 @@ Token="Yourtokenhere" | ||||
| #REQUIRED | ||||
| Server="yourservername" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Shows title, description and URL of embedded messages (sent by other bots) | ||||
| #OPTIONAL (default false) | ||||
| ShowEmbeds=false | ||||
| @@ -755,9 +795,14 @@ ShowTopicChange=false | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #OPTIONAL (default empty) | ||||
| #Only supported format is "HTML", messages will be sent in html parsemode. | ||||
| #Supported formats are "HTML", "Markdown" and "HTMLNick" | ||||
| #See https://core.telegram.org/bots/api#html-style | ||||
| #See https://core.telegram.org/bots/api#markdown-style | ||||
| #HTMLNick only allows HTML for the nick, the message itself will be html-escaped | ||||
| MessageFormat="" | ||||
|  | ||||
| #If enabled use the "First Name" as username. If this is empty use the Username | ||||
| @@ -772,6 +817,14 @@ UseFirstName=false | ||||
| #OPTIONAL (default false) | ||||
| UseInsecureURL=false | ||||
|  | ||||
| #Disable quoted/reply messages | ||||
| #OPTIONAL (default false) | ||||
| QuoteDisable=false | ||||
|  | ||||
| #Format quoted/reply messages | ||||
| #OPTIONAL (default "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})") | ||||
| QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})" | ||||
|  | ||||
| #Disable sending of edits to other bridges | ||||
| #OPTIONAL (default false) | ||||
| EditDisable=false | ||||
| @@ -817,6 +870,11 @@ Label="" | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| # | ||||
| #WARNING: if you have set MessageFormat="HTML" be sure that this format matches the guidelines | ||||
| #on https://core.telegram.org/bots/api#html-style otherwise the message will not go through to | ||||
| #telegram! eg <{NICK}> should be <{NICK}> | ||||
| # | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| @@ -868,6 +926,9 @@ NoTLS=false | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Whether to prefix messages from other bridges to rocketchat with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #rocketchat server. If you set PrefixMessagesWithNick to true, each message  | ||||
| @@ -955,6 +1016,9 @@ Password="yourpass" | ||||
| #OPTIONAL (default false) | ||||
| NoHomeServerSuffix=false | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Whether to prefix messages from other bridges to matrix with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #matrix server. If you set PrefixMessagesWithNick to true, each message  | ||||
| @@ -1036,6 +1100,9 @@ Password="yourpass" | ||||
| #OPTIONAL  | ||||
| Authcode="ABCE12" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Whether to prefix messages from other bridges to matrix with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #matrix server. If you set PrefixMessagesWithNick to true, each message  | ||||
| @@ -1098,6 +1165,90 @@ StripNick=false | ||||
| #OPTIONAL (default false) | ||||
| ShowTopicChange=false | ||||
|  | ||||
| ################################################################### | ||||
| #zulip section | ||||
| ################################################################### | ||||
| [zulip] | ||||
| #You can configure multiple servers "[zulip.name]" or "[zulip.name2]" | ||||
| #In this example we use [zulip.streamchat] | ||||
| #REQUIRED | ||||
|  | ||||
| [zulip.streamchat] | ||||
| #Token to connect with zulip API (called bot API key in Settings - Your bots) | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| #Username of the bot, normally called yourbot-bot@yourserver.zulipchat.com  | ||||
| #See username in Settings - Your bots  | ||||
| #REQUIRED | ||||
| Login="yourbot-bot@yourserver.zulipchat.com" | ||||
|  | ||||
| #Servername of your zulip instance | ||||
| #REQUIRED  | ||||
| Server="https://yourserver.zulipchat.com" | ||||
|  | ||||
| #Topic of the messages matterbridge will use | ||||
| #OPTIONAL (default "matterbridge") | ||||
| Topic="matterbridge" | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="spammer1 spammer2" | ||||
|  | ||||
| #Messages you want to ignore.  | ||||
| #Messages matching these regexp will be ignored and not sent to other bridges | ||||
| #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||
| #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||
| IgnoreMessages="^~~ badword" | ||||
|  | ||||
| #messages you want to replace. | ||||
| #it replaces outgoing messages from the bridge. | ||||
| #so you need to place it by the sending bridge definition. | ||||
| #regular expressions supported | ||||
| #some examples: | ||||
| #this replaces cat => dog and sleep => awake | ||||
| #replacemessages=[ ["cat","dog"], ["sleep","awake"] ] | ||||
| #this replaces every number with number.  123 => numbernumbernumber | ||||
| #replacemessages=[ ["[0-9]","number"] ] | ||||
| #optional (default empty) | ||||
| ReplaceMessages=[ ["cat","dog"] ] | ||||
|  | ||||
| #nicks you want to replace. | ||||
| #see replacemessages for syntaxa | ||||
| #optional (default empty) | ||||
| ReplaceNicks=[ ["user--","user"] ] | ||||
|  | ||||
| #extra label that can be used in the RemoteNickFormat | ||||
| #optional (default empty) | ||||
| Label="" | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges  | ||||
| #Currently works for messages from the following bridges: irc, mattermost, slack | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285 | ||||
| #It will strip other characters from the nick | ||||
| #OPTIONAL (default false) | ||||
| StripNick=false | ||||
|  | ||||
| #Enable to show topic changes from other bridges  | ||||
| #Only works hiding/show topic changes from slack bridge for now | ||||
| #OPTIONAL (default false) | ||||
| ShowTopicChange=false | ||||
|  | ||||
| ################################################################### | ||||
| #API | ||||
| ################################################################### | ||||
| @@ -1138,6 +1289,10 @@ RemoteNickFormat="{NICK}" | ||||
| ################################################################### | ||||
| # Settings here are defaults that each protocol can override | ||||
| [general] | ||||
|  | ||||
| ## RELOADABLE SETTINGS | ||||
| ## Settings below can be reloaded by editing the file | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| @@ -1152,10 +1307,13 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| StripNick=false | ||||
|  | ||||
|  | ||||
| #MediaServerUpload and MediaServerDownload are used for uploading images/files/video to | ||||
| #a remote "mediaserver" (a webserver like caddy for example). | ||||
| #When configured images/files uploaded on bridges like mattermost,slack, telegram will be downloaded | ||||
| #and uploaded again to MediaServerUpload URL | ||||
| #MediaServerUpload (or MediaDownloadPath) and MediaServerDownload are used for uploading | ||||
| #images/files/video to a remote "mediaserver" (a webserver like caddy for example). | ||||
| #When configured images/files uploaded on bridges like mattermost, slack, telegram will be | ||||
| #downloaded and uploaded again to MediaServerUpload URL | ||||
| #MediaDownloadPath is the filesystem path where the media file will be placed, instead of uploaded, | ||||
| #for if Matterbridge has write access to the directory your webserver is serving. | ||||
| #It is an alternative to MediaServerUpload. | ||||
| #The MediaServerDownload will be used so that bridges without native uploading support: | ||||
| #gitter, irc and xmpp will be shown links to the files on MediaServerDownload | ||||
| # | ||||
| @@ -1163,6 +1321,8 @@ StripNick=false | ||||
| #OPTIONAL (default empty) | ||||
| MediaServerUpload="https://user:pass@yourserver.com/upload" | ||||
| #OPTIONAL (default empty) | ||||
| MediaDownloadPath="/srv/http/yourserver.com/public/download" | ||||
| #OPTIONAL (default empty) | ||||
| MediaServerDownload="https://youserver.com/download" | ||||
|  | ||||
| #MediaDownloadSize is the maximum size of attachments, videos, images | ||||
| @@ -1172,9 +1332,15 @@ MediaServerDownload="https://youserver.com/download" | ||||
| #It will only download from bridges that don't have public links available, which are for the moment | ||||
| #slack, telegram, matrix and mattermost | ||||
| # | ||||
| #Optional (default 1000000 (1 megabyte)) | ||||
| #OPTIONAL (default 1000000 (1 megabyte)) | ||||
| MediaDownloadSize=1000000 | ||||
|  | ||||
| #MediaDownloadBlacklist allows you to blacklist specific files from being downloaded. | ||||
| #Filenames matching these regexp will not be download/uploaded to the mediaserver | ||||
| #You can use regex for this, see https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||
| #OPTIONAL (default empty) | ||||
| MediaDownloadBlacklist=[".html$",".htm$"] | ||||
|  | ||||
| ################################################################### | ||||
| #Gateway configuration | ||||
| ################################################################### | ||||
| @@ -1222,13 +1388,14 @@ enable=true | ||||
|     #           - encrypted rooms are not supported in matrix | ||||
|     #steam      - chatid (a large number).  | ||||
|     #             The number in the URL when you click "enter chat room" in the browser | ||||
|     #zulip      - stream (without the #) | ||||
|     #                   | ||||
|     #REQUIRED | ||||
|     channel="#testing" | ||||
|  | ||||
|         #OPTIONAL - only used for IRC protocol at the moment | ||||
|         #OPTIONAL - only used for IRC and XMPP protocols at the moment | ||||
|         [gateway.in.options] | ||||
|         #OPTIONAL - your irc channel key | ||||
|         #OPTIONAL - your irc / xmpp channel key | ||||
|         key="yourkey" | ||||
|  | ||||
|  | ||||
| @@ -1237,9 +1404,9 @@ enable=true | ||||
|     account="irc.freenode" | ||||
|     channel="#testing" | ||||
|  | ||||
|         #OPTIONAL - only used for IRC protocol at the moment | ||||
|         #OPTIONAL - only used for IRC and XMPP protocols at the moment | ||||
|         [gateway.out.options] | ||||
|         #OPTIONAL - your irc channel key | ||||
|         #OPTIONAL - your irc / xmpp channel key | ||||
|         key="yourkey" | ||||
|  | ||||
|     #[[gateway.inout]] can be used when then channel will be used to receive from  | ||||
| @@ -1248,9 +1415,9 @@ enable=true | ||||
|     account="mattermost.work" | ||||
|     channel="off-topic" | ||||
|  | ||||
|         #OPTIONAL - only used for IRC protocol at the moment | ||||
|         #OPTIONAL - only used for IRC and XMPP protocols at the moment | ||||
|         [gateway.inout.options] | ||||
|         #OPTIONAL - your irc channel key | ||||
|         #OPTIONAL - your irc / xmpp channel key | ||||
|         key="yourkey" | ||||
|  | ||||
|     [[gateway.inout]] | ||||
|   | ||||
| @@ -81,7 +81,7 @@ func New(login, pass, team, server string) *MMClient { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SetDebugLog() { | ||||
| 	log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false}) | ||||
| 	log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true}) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SetLogLevel(level string) { | ||||
| @@ -190,7 +190,11 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
|  | ||||
| 	if m.Team == nil { | ||||
| 		return errors.New("team not found") | ||||
| 		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) | ||||
| 	} | ||||
|  | ||||
| 	m.wsConnect() | ||||
| @@ -570,7 +574,7 @@ func (m *MMClient) GetFileLinks(filenames []string) []string { | ||||
| 		res, resp := m.Client.GetFileLink(f) | ||||
| 		if resp.Error != nil { | ||||
| 			// public links is probably disabled, create the link ourselves | ||||
| 			output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get") | ||||
| 			output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V4+"/files/"+f) | ||||
| 			continue | ||||
| 		} | ||||
| 		output = append(output, res) | ||||
| @@ -768,6 +772,14 @@ func (m *MMClient) GetStatus(userId string) string { | ||||
| 	return "offline" | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateStatus(userId string, status string) error { | ||||
| 	_, resp := m.Client.UpdateUserStatus(userId, &model.Status{Status: status}) | ||||
| 	if resp.Error != nil { | ||||
| 		return resp.Error | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetStatuses() map[string]string { | ||||
| 	var ids []string | ||||
| 	statuses := make(map[string]string) | ||||
| @@ -905,7 +917,8 @@ func supportedVersion(version string) bool { | ||||
| 	if strings.HasPrefix(version, "3.8.0") || | ||||
| 		strings.HasPrefix(version, "3.9.0") || | ||||
| 		strings.HasPrefix(version, "3.10.0") || | ||||
| 		strings.HasPrefix(version, "4.") { | ||||
| 		strings.HasPrefix(version, "4.") || | ||||
| 		strings.HasPrefix(version, "5.") { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
|   | ||||
| @@ -6,13 +6,15 @@ import ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/gorilla/schema" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/schema" | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| // OMessage for mattermost incoming webhook. (send to mattermost) | ||||
| @@ -22,7 +24,7 @@ type OMessage struct { | ||||
| 	IconEmoji   string                 `json:"icon_emoji,omitempty"` | ||||
| 	UserName    string                 `json:"username,omitempty"` | ||||
| 	Text        string                 `json:"text"` | ||||
| 	Attachments interface{}            `json:"attachments,omitempty"` | ||||
| 	Attachments []slack.Attachment     `json:"attachments,omitempty"` | ||||
| 	Type        string                 `json:"type,omitempty"` | ||||
| 	Props       map[string]interface{} `json:"props"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										362
									
								
								vendor/github.com/armon/consul-api/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										362
									
								
								vendor/github.com/armon/consul-api/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,362 @@ | ||||
| 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. | ||||
							
								
								
									
										140
									
								
								vendor/github.com/armon/consul-api/acl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/armon/consul-api/acl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| package consulapi | ||||
|  | ||||
| const ( | ||||
| 	// ACLCLientType is the client type token | ||||
| 	ACLClientType = "client" | ||||
|  | ||||
| 	// ACLManagementType is the management type token | ||||
| 	ACLManagementType = "management" | ||||
| ) | ||||
|  | ||||
| // ACLEntry is used to represent an ACL entry | ||||
| type ACLEntry struct { | ||||
| 	CreateIndex uint64 | ||||
| 	ModifyIndex uint64 | ||||
| 	ID          string | ||||
| 	Name        string | ||||
| 	Type        string | ||||
| 	Rules       string | ||||
| } | ||||
|  | ||||
| // ACL can be used to query the ACL endpoints | ||||
| type ACL struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // ACL returns a handle to the ACL endpoints | ||||
| func (c *Client) ACL() *ACL { | ||||
| 	return &ACL{c} | ||||
| } | ||||
|  | ||||
| // Create is used to generate a new token with the given parameters | ||||
| func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) { | ||||
| 	r := a.c.newRequest("PUT", "/v1/acl/create") | ||||
| 	r.setWriteOptions(q) | ||||
| 	r.obj = acl | ||||
| 	rtt, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	var out struct{ ID string } | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return out.ID, wm, nil | ||||
| } | ||||
|  | ||||
| // Update is used to update the rules of an existing token | ||||
| func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	r := a.c.newRequest("PUT", "/v1/acl/update") | ||||
| 	r.setWriteOptions(q) | ||||
| 	r.obj = acl | ||||
| 	rtt, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	return wm, nil | ||||
| } | ||||
|  | ||||
| // Destroy is used to destroy a given ACL token ID | ||||
| func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id) | ||||
| 	r.setWriteOptions(q) | ||||
| 	rtt, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	return wm, nil | ||||
| } | ||||
|  | ||||
| // Clone is used to return a new token cloned from an existing one | ||||
| func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) { | ||||
| 	r := a.c.newRequest("PUT", "/v1/acl/clone/"+id) | ||||
| 	r.setWriteOptions(q) | ||||
| 	rtt, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	var out struct{ ID string } | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return out.ID, wm, nil | ||||
| } | ||||
|  | ||||
| // Info is used to query for information about an ACL token | ||||
| func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) { | ||||
| 	r := a.c.newRequest("GET", "/v1/acl/info/"+id) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var entries []*ACLEntry | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	if len(entries) > 0 { | ||||
| 		return entries[0], qm, nil | ||||
| 	} | ||||
| 	return nil, qm, nil | ||||
| } | ||||
|  | ||||
| // List is used to get all the ACL tokens | ||||
| func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) { | ||||
| 	r := a.c.newRequest("GET", "/v1/acl/list") | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var entries []*ACLEntry | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return entries, qm, nil | ||||
| } | ||||
							
								
								
									
										272
									
								
								vendor/github.com/armon/consul-api/agent.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								vendor/github.com/armon/consul-api/agent.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| package consulapi | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // AgentCheck represents a check known to the agent | ||||
| type AgentCheck struct { | ||||
| 	Node        string | ||||
| 	CheckID     string | ||||
| 	Name        string | ||||
| 	Status      string | ||||
| 	Notes       string | ||||
| 	Output      string | ||||
| 	ServiceID   string | ||||
| 	ServiceName string | ||||
| } | ||||
|  | ||||
| // AgentService represents a service known to the agent | ||||
| type AgentService struct { | ||||
| 	ID      string | ||||
| 	Service string | ||||
| 	Tags    []string | ||||
| 	Port    int | ||||
| } | ||||
|  | ||||
| // AgentMember represents a cluster member known to the agent | ||||
| type AgentMember struct { | ||||
| 	Name        string | ||||
| 	Addr        string | ||||
| 	Port        uint16 | ||||
| 	Tags        map[string]string | ||||
| 	Status      int | ||||
| 	ProtocolMin uint8 | ||||
| 	ProtocolMax uint8 | ||||
| 	ProtocolCur uint8 | ||||
| 	DelegateMin uint8 | ||||
| 	DelegateMax uint8 | ||||
| 	DelegateCur uint8 | ||||
| } | ||||
|  | ||||
| // AgentServiceRegistration is used to register a new service | ||||
| type AgentServiceRegistration struct { | ||||
| 	ID    string   `json:",omitempty"` | ||||
| 	Name  string   `json:",omitempty"` | ||||
| 	Tags  []string `json:",omitempty"` | ||||
| 	Port  int      `json:",omitempty"` | ||||
| 	Check *AgentServiceCheck | ||||
| } | ||||
|  | ||||
| // AgentCheckRegistration is used to register a new check | ||||
| type AgentCheckRegistration struct { | ||||
| 	ID    string `json:",omitempty"` | ||||
| 	Name  string `json:",omitempty"` | ||||
| 	Notes string `json:",omitempty"` | ||||
| 	AgentServiceCheck | ||||
| } | ||||
|  | ||||
| // AgentServiceCheck is used to create an associated | ||||
| // check for a service | ||||
| type AgentServiceCheck struct { | ||||
| 	Script   string `json:",omitempty"` | ||||
| 	Interval string `json:",omitempty"` | ||||
| 	TTL      string `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| // Agent can be used to query the Agent endpoints | ||||
| type Agent struct { | ||||
| 	c *Client | ||||
|  | ||||
| 	// cache the node name | ||||
| 	nodeName string | ||||
| } | ||||
|  | ||||
| // Agent returns a handle to the agent endpoints | ||||
| func (c *Client) Agent() *Agent { | ||||
| 	return &Agent{c: c} | ||||
| } | ||||
|  | ||||
| // Self is used to query the agent we are speaking to for | ||||
| // information about itself | ||||
| func (a *Agent) Self() (map[string]map[string]interface{}, error) { | ||||
| 	r := a.c.newRequest("GET", "/v1/agent/self") | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var out map[string]map[string]interface{} | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // NodeName is used to get the node name of the agent | ||||
| func (a *Agent) NodeName() (string, error) { | ||||
| 	if a.nodeName != "" { | ||||
| 		return a.nodeName, nil | ||||
| 	} | ||||
| 	info, err := a.Self() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	name := info["Config"]["NodeName"].(string) | ||||
| 	a.nodeName = name | ||||
| 	return name, nil | ||||
| } | ||||
|  | ||||
| // Checks returns the locally registered checks | ||||
| func (a *Agent) Checks() (map[string]*AgentCheck, error) { | ||||
| 	r := a.c.newRequest("GET", "/v1/agent/checks") | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var out map[string]*AgentCheck | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // Services returns the locally registered services | ||||
| func (a *Agent) Services() (map[string]*AgentService, error) { | ||||
| 	r := a.c.newRequest("GET", "/v1/agent/services") | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var out map[string]*AgentService | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // Members returns the known gossip members. The WAN | ||||
| // flag can be used to query a server for WAN members. | ||||
| func (a *Agent) Members(wan bool) ([]*AgentMember, error) { | ||||
| 	r := a.c.newRequest("GET", "/v1/agent/members") | ||||
| 	if wan { | ||||
| 		r.params.Set("wan", "1") | ||||
| 	} | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var out []*AgentMember | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // ServiceRegister is used to register a new service with | ||||
| // the local agent | ||||
| func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error { | ||||
| 	r := a.c.newRequest("PUT", "/v1/agent/service/register") | ||||
| 	r.obj = service | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ServiceDeregister is used to deregister a service with | ||||
| // the local agent | ||||
| func (a *Agent) ServiceDeregister(serviceID string) error { | ||||
| 	r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID) | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // PassTTL is used to set a TTL check to the passing state | ||||
| func (a *Agent) PassTTL(checkID, note string) error { | ||||
| 	return a.UpdateTTL(checkID, note, "pass") | ||||
| } | ||||
|  | ||||
| // WarnTTL is used to set a TTL check to the warning state | ||||
| func (a *Agent) WarnTTL(checkID, note string) error { | ||||
| 	return a.UpdateTTL(checkID, note, "warn") | ||||
| } | ||||
|  | ||||
| // FailTTL is used to set a TTL check to the failing state | ||||
| func (a *Agent) FailTTL(checkID, note string) error { | ||||
| 	return a.UpdateTTL(checkID, note, "fail") | ||||
| } | ||||
|  | ||||
| // UpdateTTL is used to update the TTL of a check | ||||
| func (a *Agent) UpdateTTL(checkID, note, status string) error { | ||||
| 	switch status { | ||||
| 	case "pass": | ||||
| 	case "warn": | ||||
| 	case "fail": | ||||
| 	default: | ||||
| 		return fmt.Errorf("Invalid status: %s", status) | ||||
| 	} | ||||
| 	endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID) | ||||
| 	r := a.c.newRequest("PUT", endpoint) | ||||
| 	r.params.Set("note", note) | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CheckRegister is used to register a new check with | ||||
| // the local agent | ||||
| func (a *Agent) CheckRegister(check *AgentCheckRegistration) error { | ||||
| 	r := a.c.newRequest("PUT", "/v1/agent/check/register") | ||||
| 	r.obj = check | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CheckDeregister is used to deregister a check with | ||||
| // the local agent | ||||
| func (a *Agent) CheckDeregister(checkID string) error { | ||||
| 	r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID) | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Join is used to instruct the agent to attempt a join to | ||||
| // another cluster member | ||||
| func (a *Agent) Join(addr string, wan bool) error { | ||||
| 	r := a.c.newRequest("PUT", "/v1/agent/join/"+addr) | ||||
| 	if wan { | ||||
| 		r.params.Set("wan", "1") | ||||
| 	} | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ForceLeave is used to have the agent eject a failed node | ||||
| func (a *Agent) ForceLeave(node string) error { | ||||
| 	r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node) | ||||
| 	_, resp, err := requireOK(a.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										323
									
								
								vendor/github.com/armon/consul-api/api.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								vendor/github.com/armon/consul-api/api.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | ||||
| package consulapi | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // QueryOptions are used to parameterize a query | ||||
| type QueryOptions struct { | ||||
| 	// Providing a datacenter overwrites the DC provided | ||||
| 	// by the Config | ||||
| 	Datacenter string | ||||
|  | ||||
| 	// AllowStale allows any Consul server (non-leader) to service | ||||
| 	// a read. This allows for lower latency and higher throughput | ||||
| 	AllowStale bool | ||||
|  | ||||
| 	// RequireConsistent forces the read to be fully consistent. | ||||
| 	// This is more expensive but prevents ever performing a stale | ||||
| 	// read. | ||||
| 	RequireConsistent bool | ||||
|  | ||||
| 	// WaitIndex is used to enable a blocking query. Waits | ||||
| 	// until the timeout or the next index is reached | ||||
| 	WaitIndex uint64 | ||||
|  | ||||
| 	// WaitTime is used to bound the duration of a wait. | ||||
| 	// Defaults to that of the Config, but can be overriden. | ||||
| 	WaitTime time.Duration | ||||
|  | ||||
| 	// Token is used to provide a per-request ACL token | ||||
| 	// which overrides the agent's default token. | ||||
| 	Token string | ||||
| } | ||||
|  | ||||
| // WriteOptions are used to parameterize a write | ||||
| type WriteOptions struct { | ||||
| 	// Providing a datacenter overwrites the DC provided | ||||
| 	// by the Config | ||||
| 	Datacenter string | ||||
|  | ||||
| 	// Token is used to provide a per-request ACL token | ||||
| 	// which overrides the agent's default token. | ||||
| 	Token string | ||||
| } | ||||
|  | ||||
| // QueryMeta is used to return meta data about a query | ||||
| type QueryMeta struct { | ||||
| 	// LastIndex. This can be used as a WaitIndex to perform | ||||
| 	// a blocking query | ||||
| 	LastIndex uint64 | ||||
|  | ||||
| 	// Time of last contact from the leader for the | ||||
| 	// server servicing the request | ||||
| 	LastContact time.Duration | ||||
|  | ||||
| 	// Is there a known leader | ||||
| 	KnownLeader bool | ||||
|  | ||||
| 	// How long did the request take | ||||
| 	RequestTime time.Duration | ||||
| } | ||||
|  | ||||
| // WriteMeta is used to return meta data about a write | ||||
| type WriteMeta struct { | ||||
| 	// How long did the request take | ||||
| 	RequestTime time.Duration | ||||
| } | ||||
|  | ||||
| // HttpBasicAuth is used to authenticate http client with HTTP Basic Authentication | ||||
| type HttpBasicAuth struct { | ||||
| 	// Username to use for HTTP Basic Authentication | ||||
| 	Username string | ||||
|  | ||||
| 	// Password to use for HTTP Basic Authentication | ||||
| 	Password string | ||||
| } | ||||
|  | ||||
| // Config is used to configure the creation of a client | ||||
| type Config struct { | ||||
| 	// Address is the address of the Consul server | ||||
| 	Address string | ||||
|  | ||||
| 	// Scheme is the URI scheme for the Consul server | ||||
| 	Scheme string | ||||
|  | ||||
| 	// Datacenter to use. If not provided, the default agent datacenter is used. | ||||
| 	Datacenter string | ||||
|  | ||||
| 	// HttpClient is the client to use. Default will be | ||||
| 	// used if not provided. | ||||
| 	HttpClient *http.Client | ||||
|  | ||||
| 	// HttpAuth is the auth info to use for http access. | ||||
| 	HttpAuth *HttpBasicAuth | ||||
|  | ||||
| 	// WaitTime limits how long a Watch will block. If not provided, | ||||
| 	// the agent default values will be used. | ||||
| 	WaitTime time.Duration | ||||
|  | ||||
| 	// Token is used to provide a per-request ACL token | ||||
| 	// which overrides the agent's default token. | ||||
| 	Token string | ||||
| } | ||||
|  | ||||
| // DefaultConfig returns a default configuration for the client | ||||
| func DefaultConfig() *Config { | ||||
| 	return &Config{ | ||||
| 		Address:    "127.0.0.1:8500", | ||||
| 		Scheme:     "http", | ||||
| 		HttpClient: http.DefaultClient, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Client provides a client to the Consul API | ||||
| type Client struct { | ||||
| 	config Config | ||||
| } | ||||
|  | ||||
| // NewClient returns a new client | ||||
| func NewClient(config *Config) (*Client, error) { | ||||
| 	// bootstrap the config | ||||
| 	defConfig := DefaultConfig() | ||||
|  | ||||
| 	if len(config.Address) == 0 { | ||||
| 		config.Address = defConfig.Address | ||||
| 	} | ||||
|  | ||||
| 	if len(config.Scheme) == 0 { | ||||
| 		config.Scheme = defConfig.Scheme | ||||
| 	} | ||||
|  | ||||
| 	if config.HttpClient == nil { | ||||
| 		config.HttpClient = defConfig.HttpClient | ||||
| 	} | ||||
|  | ||||
| 	client := &Client{ | ||||
| 		config: *config, | ||||
| 	} | ||||
| 	return client, nil | ||||
| } | ||||
|  | ||||
| // request is used to help build up a request | ||||
| type request struct { | ||||
| 	config *Config | ||||
| 	method string | ||||
| 	url    *url.URL | ||||
| 	params url.Values | ||||
| 	body   io.Reader | ||||
| 	obj    interface{} | ||||
| } | ||||
|  | ||||
| // setQueryOptions is used to annotate the request with | ||||
| // additional query options | ||||
| func (r *request) setQueryOptions(q *QueryOptions) { | ||||
| 	if q == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if q.Datacenter != "" { | ||||
| 		r.params.Set("dc", q.Datacenter) | ||||
| 	} | ||||
| 	if q.AllowStale { | ||||
| 		r.params.Set("stale", "") | ||||
| 	} | ||||
| 	if q.RequireConsistent { | ||||
| 		r.params.Set("consistent", "") | ||||
| 	} | ||||
| 	if q.WaitIndex != 0 { | ||||
| 		r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10)) | ||||
| 	} | ||||
| 	if q.WaitTime != 0 { | ||||
| 		r.params.Set("wait", durToMsec(q.WaitTime)) | ||||
| 	} | ||||
| 	if q.Token != "" { | ||||
| 		r.params.Set("token", q.Token) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // durToMsec converts a duration to a millisecond specified string | ||||
| func durToMsec(dur time.Duration) string { | ||||
| 	return fmt.Sprintf("%dms", dur/time.Millisecond) | ||||
| } | ||||
|  | ||||
| // setWriteOptions is used to annotate the request with | ||||
| // additional write options | ||||
| func (r *request) setWriteOptions(q *WriteOptions) { | ||||
| 	if q == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if q.Datacenter != "" { | ||||
| 		r.params.Set("dc", q.Datacenter) | ||||
| 	} | ||||
| 	if q.Token != "" { | ||||
| 		r.params.Set("token", q.Token) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // toHTTP converts the request to an HTTP request | ||||
| func (r *request) toHTTP() (*http.Request, error) { | ||||
| 	// Encode the query parameters | ||||
| 	r.url.RawQuery = r.params.Encode() | ||||
|  | ||||
| 	// Get the url sring | ||||
| 	urlRaw := r.url.String() | ||||
|  | ||||
| 	// Check if we should encode the body | ||||
| 	if r.body == nil && r.obj != nil { | ||||
| 		if b, err := encodeBody(r.obj); err != nil { | ||||
| 			return nil, err | ||||
| 		} else { | ||||
| 			r.body = b | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Create the HTTP request | ||||
| 	req, err := http.NewRequest(r.method, urlRaw, r.body) | ||||
|  | ||||
| 	// Setup auth | ||||
| 	if err == nil && r.config.HttpAuth != nil { | ||||
| 		req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password) | ||||
| 	} | ||||
|  | ||||
| 	return req, err | ||||
| } | ||||
|  | ||||
| // newRequest is used to create a new request | ||||
| func (c *Client) newRequest(method, path string) *request { | ||||
| 	r := &request{ | ||||
| 		config: &c.config, | ||||
| 		method: method, | ||||
| 		url: &url.URL{ | ||||
| 			Scheme: c.config.Scheme, | ||||
| 			Host:   c.config.Address, | ||||
| 			Path:   path, | ||||
| 		}, | ||||
| 		params: make(map[string][]string), | ||||
| 	} | ||||
| 	if c.config.Datacenter != "" { | ||||
| 		r.params.Set("dc", c.config.Datacenter) | ||||
| 	} | ||||
| 	if c.config.WaitTime != 0 { | ||||
| 		r.params.Set("wait", durToMsec(r.config.WaitTime)) | ||||
| 	} | ||||
| 	if c.config.Token != "" { | ||||
| 		r.params.Set("token", r.config.Token) | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // doRequest runs a request with our client | ||||
| func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) { | ||||
| 	req, err := r.toHTTP() | ||||
| 	if err != nil { | ||||
| 		return 0, nil, err | ||||
| 	} | ||||
| 	start := time.Now() | ||||
| 	resp, err := c.config.HttpClient.Do(req) | ||||
| 	diff := time.Now().Sub(start) | ||||
| 	return diff, resp, err | ||||
| } | ||||
|  | ||||
| // parseQueryMeta is used to help parse query meta-data | ||||
| func parseQueryMeta(resp *http.Response, q *QueryMeta) error { | ||||
| 	header := resp.Header | ||||
|  | ||||
| 	// Parse the X-Consul-Index | ||||
| 	index, err := strconv.ParseUint(header.Get("X-Consul-Index"), 10, 64) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to parse X-Consul-Index: %v", err) | ||||
| 	} | ||||
| 	q.LastIndex = index | ||||
|  | ||||
| 	// Parse the X-Consul-LastContact | ||||
| 	last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err) | ||||
| 	} | ||||
| 	q.LastContact = time.Duration(last) * time.Millisecond | ||||
|  | ||||
| 	// Parse the X-Consul-KnownLeader | ||||
| 	switch header.Get("X-Consul-KnownLeader") { | ||||
| 	case "true": | ||||
| 		q.KnownLeader = true | ||||
| 	default: | ||||
| 		q.KnownLeader = false | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // decodeBody is used to JSON decode a body | ||||
| func decodeBody(resp *http.Response, out interface{}) error { | ||||
| 	dec := json.NewDecoder(resp.Body) | ||||
| 	return dec.Decode(out) | ||||
| } | ||||
|  | ||||
| // encodeBody is used to encode a request body | ||||
| func encodeBody(obj interface{}) (io.Reader, error) { | ||||
| 	buf := bytes.NewBuffer(nil) | ||||
| 	enc := json.NewEncoder(buf) | ||||
| 	if err := enc.Encode(obj); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return buf, nil | ||||
| } | ||||
|  | ||||
| // requireOK is used to wrap doRequest and check for a 200 | ||||
| func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) { | ||||
| 	if e != nil { | ||||
| 		return d, resp, e | ||||
| 	} | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		var buf bytes.Buffer | ||||
| 		io.Copy(&buf, resp.Body) | ||||
| 		return d, resp, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes()) | ||||
| 	} | ||||
| 	return d, resp, e | ||||
| } | ||||
							
								
								
									
										181
									
								
								vendor/github.com/armon/consul-api/catalog.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								vendor/github.com/armon/consul-api/catalog.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| package consulapi | ||||
|  | ||||
| type Node struct { | ||||
| 	Node    string | ||||
| 	Address string | ||||
| } | ||||
|  | ||||
| type CatalogService struct { | ||||
| 	Node        string | ||||
| 	Address     string | ||||
| 	ServiceID   string | ||||
| 	ServiceName string | ||||
| 	ServiceTags []string | ||||
| 	ServicePort int | ||||
| } | ||||
|  | ||||
| type CatalogNode struct { | ||||
| 	Node     *Node | ||||
| 	Services map[string]*AgentService | ||||
| } | ||||
|  | ||||
| type CatalogRegistration struct { | ||||
| 	Node       string | ||||
| 	Address    string | ||||
| 	Datacenter string | ||||
| 	Service    *AgentService | ||||
| 	Check      *AgentCheck | ||||
| } | ||||
|  | ||||
| type CatalogDeregistration struct { | ||||
| 	Node       string | ||||
| 	Address    string | ||||
| 	Datacenter string | ||||
| 	ServiceID  string | ||||
| 	CheckID    string | ||||
| } | ||||
|  | ||||
| // Catalog can be used to query the Catalog endpoints | ||||
| type Catalog struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // Catalog returns a handle to the catalog endpoints | ||||
| func (c *Client) Catalog() *Catalog { | ||||
| 	return &Catalog{c} | ||||
| } | ||||
|  | ||||
| func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	r := c.c.newRequest("PUT", "/v1/catalog/register") | ||||
| 	r.setWriteOptions(q) | ||||
| 	r.obj = reg | ||||
| 	rtt, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{} | ||||
| 	wm.RequestTime = rtt | ||||
|  | ||||
| 	return wm, nil | ||||
| } | ||||
|  | ||||
| func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	r := c.c.newRequest("PUT", "/v1/catalog/deregister") | ||||
| 	r.setWriteOptions(q) | ||||
| 	r.obj = dereg | ||||
| 	rtt, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{} | ||||
| 	wm.RequestTime = rtt | ||||
|  | ||||
| 	return wm, nil | ||||
| } | ||||
|  | ||||
| // Datacenters is used to query for all the known datacenters | ||||
| func (c *Catalog) Datacenters() ([]string, error) { | ||||
| 	r := c.c.newRequest("GET", "/v1/catalog/datacenters") | ||||
| 	_, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var out []string | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return out, nil | ||||
| } | ||||
|  | ||||
| // Nodes is used to query all the known nodes | ||||
| func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) { | ||||
| 	r := c.c.newRequest("GET", "/v1/catalog/nodes") | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out []*Node | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
|  | ||||
| // Services is used to query for all known services | ||||
| func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) { | ||||
| 	r := c.c.newRequest("GET", "/v1/catalog/services") | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out map[string][]string | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
|  | ||||
| // Service is used to query catalog entries for a given service | ||||
| func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) { | ||||
| 	r := c.c.newRequest("GET", "/v1/catalog/service/"+service) | ||||
| 	r.setQueryOptions(q) | ||||
| 	if tag != "" { | ||||
| 		r.params.Set("tag", tag) | ||||
| 	} | ||||
| 	rtt, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out []*CatalogService | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
|  | ||||
| // Node is used to query for service information about a single node | ||||
| func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) { | ||||
| 	r := c.c.newRequest("GET", "/v1/catalog/node/"+node) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(c.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out *CatalogNode | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
							
								
								
									
										104
									
								
								vendor/github.com/armon/consul-api/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								vendor/github.com/armon/consul-api/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| package consulapi | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // Event can be used to query the Event endpoints | ||||
| type Event struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // UserEvent represents an event that was fired by the user | ||||
| type UserEvent struct { | ||||
| 	ID            string | ||||
| 	Name          string | ||||
| 	Payload       []byte | ||||
| 	NodeFilter    string | ||||
| 	ServiceFilter string | ||||
| 	TagFilter     string | ||||
| 	Version       int | ||||
| 	LTime         uint64 | ||||
| } | ||||
|  | ||||
| // Event returns a handle to the event endpoints | ||||
| func (c *Client) Event() *Event { | ||||
| 	return &Event{c} | ||||
| } | ||||
|  | ||||
| // Fire is used to fire a new user event. Only the Name, Payload and Filters | ||||
| // are respected. This returns the ID or an associated error. Cross DC requests | ||||
| // are supported. | ||||
| func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) { | ||||
| 	r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name) | ||||
| 	r.setWriteOptions(q) | ||||
| 	if params.NodeFilter != "" { | ||||
| 		r.params.Set("node", params.NodeFilter) | ||||
| 	} | ||||
| 	if params.ServiceFilter != "" { | ||||
| 		r.params.Set("service", params.ServiceFilter) | ||||
| 	} | ||||
| 	if params.TagFilter != "" { | ||||
| 		r.params.Set("tag", params.TagFilter) | ||||
| 	} | ||||
| 	if params.Payload != nil { | ||||
| 		r.body = bytes.NewReader(params.Payload) | ||||
| 	} | ||||
|  | ||||
| 	rtt, resp, err := requireOK(e.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	var out UserEvent | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return out.ID, wm, nil | ||||
| } | ||||
|  | ||||
| // List is used to get the most recent events an agent has received. | ||||
| // This list can be optionally filtered by the name. This endpoint supports | ||||
| // quasi-blocking queries. The index is not monotonic, nor does it provide provide | ||||
| // LastContact or KnownLeader. | ||||
| func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) { | ||||
| 	r := e.c.newRequest("GET", "/v1/event/list") | ||||
| 	r.setQueryOptions(q) | ||||
| 	if name != "" { | ||||
| 		r.params.Set("name", name) | ||||
| 	} | ||||
| 	rtt, resp, err := requireOK(e.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var entries []*UserEvent | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return entries, qm, nil | ||||
| } | ||||
|  | ||||
| // IDToIndex is a bit of a hack. This simulates the index generation to | ||||
| // convert an event ID into a WaitIndex. | ||||
| func (e *Event) IDToIndex(uuid string) uint64 { | ||||
| 	lower := uuid[0:8] + uuid[9:13] + uuid[14:18] | ||||
| 	upper := uuid[19:23] + uuid[24:36] | ||||
| 	lowVal, err := strconv.ParseUint(lower, 16, 64) | ||||
| 	if err != nil { | ||||
| 		panic("Failed to convert " + lower) | ||||
| 	} | ||||
| 	highVal, err := strconv.ParseUint(upper, 16, 64) | ||||
| 	if err != nil { | ||||
| 		panic("Failed to convert " + upper) | ||||
| 	} | ||||
| 	return lowVal ^ highVal | ||||
| } | ||||
							
								
								
									
										136
									
								
								vendor/github.com/armon/consul-api/health.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								vendor/github.com/armon/consul-api/health.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| package consulapi | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // HealthCheck is used to represent a single check | ||||
| type HealthCheck struct { | ||||
| 	Node        string | ||||
| 	CheckID     string | ||||
| 	Name        string | ||||
| 	Status      string | ||||
| 	Notes       string | ||||
| 	Output      string | ||||
| 	ServiceID   string | ||||
| 	ServiceName string | ||||
| } | ||||
|  | ||||
| // ServiceEntry is used for the health service endpoint | ||||
| type ServiceEntry struct { | ||||
| 	Node    *Node | ||||
| 	Service *AgentService | ||||
| 	Checks  []*HealthCheck | ||||
| } | ||||
|  | ||||
| // Health can be used to query the Health endpoints | ||||
| type Health struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // Health returns a handle to the health endpoints | ||||
| func (c *Client) Health() *Health { | ||||
| 	return &Health{c} | ||||
| } | ||||
|  | ||||
| // Node is used to query for checks belonging to a given node | ||||
| func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) { | ||||
| 	r := h.c.newRequest("GET", "/v1/health/node/"+node) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(h.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out []*HealthCheck | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
|  | ||||
| // Checks is used to return the checks associated with a service | ||||
| func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) { | ||||
| 	r := h.c.newRequest("GET", "/v1/health/checks/"+service) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(h.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out []*HealthCheck | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
|  | ||||
| // Service is used to query health information along with service info | ||||
| // for a given service. It can optionally do server-side filtering on a tag | ||||
| // or nodes with passing health checks only. | ||||
| func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) { | ||||
| 	r := h.c.newRequest("GET", "/v1/health/service/"+service) | ||||
| 	r.setQueryOptions(q) | ||||
| 	if tag != "" { | ||||
| 		r.params.Set("tag", tag) | ||||
| 	} | ||||
| 	if passingOnly { | ||||
| 		r.params.Set("passing", "1") | ||||
| 	} | ||||
| 	rtt, resp, err := requireOK(h.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out []*ServiceEntry | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
|  | ||||
| // State is used to retrieve all the checks in a given state. | ||||
| // The wildcard "any" state can also be used for all checks. | ||||
| func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) { | ||||
| 	switch state { | ||||
| 	case "any": | ||||
| 	case "warning": | ||||
| 	case "critical": | ||||
| 	case "passing": | ||||
| 	case "unknown": | ||||
| 	default: | ||||
| 		return nil, nil, fmt.Errorf("Unsupported state: %v", state) | ||||
| 	} | ||||
| 	r := h.c.newRequest("GET", "/v1/health/state/"+state) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(h.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var out []*HealthCheck | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return out, qm, nil | ||||
| } | ||||
							
								
								
									
										219
									
								
								vendor/github.com/armon/consul-api/kv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								vendor/github.com/armon/consul-api/kv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| package consulapi | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // KVPair is used to represent a single K/V entry | ||||
| type KVPair struct { | ||||
| 	Key         string | ||||
| 	CreateIndex uint64 | ||||
| 	ModifyIndex uint64 | ||||
| 	LockIndex   uint64 | ||||
| 	Flags       uint64 | ||||
| 	Value       []byte | ||||
| 	Session     string | ||||
| } | ||||
|  | ||||
| // KVPairs is a list of KVPair objects | ||||
| type KVPairs []*KVPair | ||||
|  | ||||
| // KV is used to manipulate the K/V API | ||||
| type KV struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // KV is used to return a handle to the K/V apis | ||||
| func (c *Client) KV() *KV { | ||||
| 	return &KV{c} | ||||
| } | ||||
|  | ||||
| // Get is used to lookup a single key | ||||
| func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) { | ||||
| 	resp, qm, err := k.getInternal(key, nil, q) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		return nil, qm, nil | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var entries []*KVPair | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	if len(entries) > 0 { | ||||
| 		return entries[0], qm, nil | ||||
| 	} | ||||
| 	return nil, qm, nil | ||||
| } | ||||
|  | ||||
| // List is used to lookup all keys under a prefix | ||||
| func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) { | ||||
| 	resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		return nil, qm, nil | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var entries []*KVPair | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return entries, qm, nil | ||||
| } | ||||
|  | ||||
| // Keys is used to list all the keys under a prefix. Optionally, | ||||
| // a separator can be used to limit the responses. | ||||
| func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) { | ||||
| 	params := map[string]string{"keys": ""} | ||||
| 	if separator != "" { | ||||
| 		params["separator"] = separator | ||||
| 	} | ||||
| 	resp, qm, err := k.getInternal(prefix, params, q) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	if resp == nil { | ||||
| 		return nil, qm, nil | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var entries []string | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return entries, qm, nil | ||||
| } | ||||
|  | ||||
| func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) { | ||||
| 	r := k.c.newRequest("GET", "/v1/kv/"+key) | ||||
| 	r.setQueryOptions(q) | ||||
| 	for param, val := range params { | ||||
| 		r.params.Set(param, val) | ||||
| 	} | ||||
| 	rtt, resp, err := k.c.doRequest(r) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	if resp.StatusCode == 404 { | ||||
| 		resp.Body.Close() | ||||
| 		return nil, qm, nil | ||||
| 	} else if resp.StatusCode != 200 { | ||||
| 		resp.Body.Close() | ||||
| 		return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode) | ||||
| 	} | ||||
| 	return resp, qm, nil | ||||
| } | ||||
|  | ||||
| // Put is used to write a new value. Only the | ||||
| // Key, Flags and Value is respected. | ||||
| func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	params := make(map[string]string, 1) | ||||
| 	if p.Flags != 0 { | ||||
| 		params["flags"] = strconv.FormatUint(p.Flags, 10) | ||||
| 	} | ||||
| 	_, wm, err := k.put(p.Key, params, p.Value, q) | ||||
| 	return wm, err | ||||
| } | ||||
|  | ||||
| // CAS is used for a Check-And-Set operation. The Key, | ||||
| // ModifyIndex, Flags and Value are respected. Returns true | ||||
| // on success or false on failures. | ||||
| func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) { | ||||
| 	params := make(map[string]string, 2) | ||||
| 	if p.Flags != 0 { | ||||
| 		params["flags"] = strconv.FormatUint(p.Flags, 10) | ||||
| 	} | ||||
| 	params["cas"] = strconv.FormatUint(p.ModifyIndex, 10) | ||||
| 	return k.put(p.Key, params, p.Value, q) | ||||
| } | ||||
|  | ||||
| // Acquire is used for a lock acquisiiton operation. The Key, | ||||
| // Flags, Value and Session are respected. Returns true | ||||
| // on success or false on failures. | ||||
| func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) { | ||||
| 	params := make(map[string]string, 2) | ||||
| 	if p.Flags != 0 { | ||||
| 		params["flags"] = strconv.FormatUint(p.Flags, 10) | ||||
| 	} | ||||
| 	params["acquire"] = p.Session | ||||
| 	return k.put(p.Key, params, p.Value, q) | ||||
| } | ||||
|  | ||||
| // Release is used for a lock release operation. The Key, | ||||
| // Flags, Value and Session are respected. Returns true | ||||
| // on success or false on failures. | ||||
| func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) { | ||||
| 	params := make(map[string]string, 2) | ||||
| 	if p.Flags != 0 { | ||||
| 		params["flags"] = strconv.FormatUint(p.Flags, 10) | ||||
| 	} | ||||
| 	params["release"] = p.Session | ||||
| 	return k.put(p.Key, params, p.Value, q) | ||||
| } | ||||
|  | ||||
| func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) { | ||||
| 	r := k.c.newRequest("PUT", "/v1/kv/"+key) | ||||
| 	r.setWriteOptions(q) | ||||
| 	for param, val := range params { | ||||
| 		r.params.Set(param, val) | ||||
| 	} | ||||
| 	r.body = bytes.NewReader(body) | ||||
| 	rtt, resp, err := requireOK(k.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return false, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &WriteMeta{} | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var buf bytes.Buffer | ||||
| 	if _, err := io.Copy(&buf, resp.Body); err != nil { | ||||
| 		return false, nil, fmt.Errorf("Failed to read response: %v", err) | ||||
| 	} | ||||
| 	res := strings.Contains(string(buf.Bytes()), "true") | ||||
| 	return res, qm, nil | ||||
| } | ||||
|  | ||||
| // Delete is used to delete a single key | ||||
| func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) { | ||||
| 	return k.deleteInternal(key, nil, w) | ||||
| } | ||||
|  | ||||
| // DeleteTree is used to delete all keys under a prefix | ||||
| func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) { | ||||
| 	return k.deleteInternal(prefix, []string{"recurse"}, w) | ||||
| } | ||||
|  | ||||
| func (k *KV) deleteInternal(key string, params []string, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	r := k.c.newRequest("DELETE", "/v1/kv/"+key) | ||||
| 	r.setWriteOptions(q) | ||||
| 	for _, param := range params { | ||||
| 		r.params.Set(param, "") | ||||
| 	} | ||||
| 	rtt, resp, err := requireOK(k.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
|  | ||||
| 	qm := &WriteMeta{} | ||||
| 	qm.RequestTime = rtt | ||||
| 	return qm, nil | ||||
| } | ||||
							
								
								
									
										204
									
								
								vendor/github.com/armon/consul-api/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								vendor/github.com/armon/consul-api/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| package consulapi | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // SessionEntry represents a session in consul | ||||
| type SessionEntry struct { | ||||
| 	CreateIndex uint64 | ||||
| 	ID          string | ||||
| 	Name        string | ||||
| 	Node        string | ||||
| 	Checks      []string | ||||
| 	LockDelay   time.Duration | ||||
| 	Behavior    string | ||||
| 	TTL         string | ||||
| } | ||||
|  | ||||
| // Session can be used to query the Session endpoints | ||||
| type Session struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // Session returns a handle to the session endpoints | ||||
| func (c *Client) Session() *Session { | ||||
| 	return &Session{c} | ||||
| } | ||||
|  | ||||
| // CreateNoChecks is like Create but is used specifically to create | ||||
| // a session with no associated health checks. | ||||
| func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) { | ||||
| 	body := make(map[string]interface{}) | ||||
| 	body["Checks"] = []string{} | ||||
| 	if se != nil { | ||||
| 		if se.Name != "" { | ||||
| 			body["Name"] = se.Name | ||||
| 		} | ||||
| 		if se.Node != "" { | ||||
| 			body["Node"] = se.Node | ||||
| 		} | ||||
| 		if se.LockDelay != 0 { | ||||
| 			body["LockDelay"] = durToMsec(se.LockDelay) | ||||
| 		} | ||||
| 		if se.Behavior != "" { | ||||
| 			body["Behavior"] = se.Behavior | ||||
| 		} | ||||
| 		if se.TTL != "" { | ||||
| 			body["TTL"] = se.TTL | ||||
| 		} | ||||
| 	} | ||||
| 	return s.create(body, q) | ||||
|  | ||||
| } | ||||
|  | ||||
| // Create makes a new session. Providing a session entry can | ||||
| // customize the session. It can also be nil to use defaults. | ||||
| func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) { | ||||
| 	var obj interface{} | ||||
| 	if se != nil { | ||||
| 		body := make(map[string]interface{}) | ||||
| 		obj = body | ||||
| 		if se.Name != "" { | ||||
| 			body["Name"] = se.Name | ||||
| 		} | ||||
| 		if se.Node != "" { | ||||
| 			body["Node"] = se.Node | ||||
| 		} | ||||
| 		if se.LockDelay != 0 { | ||||
| 			body["LockDelay"] = durToMsec(se.LockDelay) | ||||
| 		} | ||||
| 		if len(se.Checks) > 0 { | ||||
| 			body["Checks"] = se.Checks | ||||
| 		} | ||||
| 		if se.Behavior != "" { | ||||
| 			body["Behavior"] = se.Behavior | ||||
| 		} | ||||
| 		if se.TTL != "" { | ||||
| 			body["TTL"] = se.TTL | ||||
| 		} | ||||
| 	} | ||||
| 	return s.create(obj, q) | ||||
| } | ||||
|  | ||||
| func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) { | ||||
| 	r := s.c.newRequest("PUT", "/v1/session/create") | ||||
| 	r.setWriteOptions(q) | ||||
| 	r.obj = obj | ||||
| 	rtt, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	var out struct{ ID string } | ||||
| 	if err := decodeBody(resp, &out); err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return out.ID, wm, nil | ||||
| } | ||||
|  | ||||
| // Destroy invalides a given session | ||||
| func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) { | ||||
| 	r := s.c.newRequest("PUT", "/v1/session/destroy/"+id) | ||||
| 	r.setWriteOptions(q) | ||||
| 	rtt, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
| 	return wm, nil | ||||
| } | ||||
|  | ||||
| // Renew renews the TTL on a given session | ||||
| func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) { | ||||
| 	r := s.c.newRequest("PUT", "/v1/session/renew/"+id) | ||||
| 	r.setWriteOptions(q) | ||||
| 	rtt, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	wm := &WriteMeta{RequestTime: rtt} | ||||
|  | ||||
| 	var entries []*SessionEntry | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, wm, err | ||||
| 	} | ||||
|  | ||||
| 	if len(entries) > 0 { | ||||
| 		return entries[0], wm, nil | ||||
| 	} | ||||
| 	return nil, wm, nil | ||||
| } | ||||
|  | ||||
| // Info looks up a single session | ||||
| func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) { | ||||
| 	r := s.c.newRequest("GET", "/v1/session/info/"+id) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var entries []*SessionEntry | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	if len(entries) > 0 { | ||||
| 		return entries[0], qm, nil | ||||
| 	} | ||||
| 	return nil, qm, nil | ||||
| } | ||||
|  | ||||
| // List gets sessions for a node | ||||
| func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) { | ||||
| 	r := s.c.newRequest("GET", "/v1/session/node/"+node) | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var entries []*SessionEntry | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return entries, qm, nil | ||||
| } | ||||
|  | ||||
| // List gets all active sessions | ||||
| func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) { | ||||
| 	r := s.c.newRequest("GET", "/v1/session/list") | ||||
| 	r.setQueryOptions(q) | ||||
| 	rtt, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	qm := &QueryMeta{} | ||||
| 	parseQueryMeta(resp, qm) | ||||
| 	qm.RequestTime = rtt | ||||
|  | ||||
| 	var entries []*SessionEntry | ||||
| 	if err := decodeBody(resp, &entries); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return entries, qm, nil | ||||
| } | ||||
							
								
								
									
										43
									
								
								vendor/github.com/armon/consul-api/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/armon/consul-api/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| package consulapi | ||||
|  | ||||
| // Status can be used to query the Status endpoints | ||||
| type Status struct { | ||||
| 	c *Client | ||||
| } | ||||
|  | ||||
| // Status returns a handle to the status endpoints | ||||
| func (c *Client) Status() *Status { | ||||
| 	return &Status{c} | ||||
| } | ||||
|  | ||||
| // Leader is used to query for a known leader | ||||
| func (s *Status) Leader() (string, error) { | ||||
| 	r := s.c.newRequest("GET", "/v1/status/leader") | ||||
| 	_, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var leader string | ||||
| 	if err := decodeBody(resp, &leader); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return leader, nil | ||||
| } | ||||
|  | ||||
| // Peers is used to query for a known raft peers | ||||
| func (s *Status) Peers() ([]string, error) { | ||||
| 	r := s.c.newRequest("GET", "/v1/status/peers") | ||||
| 	_, resp, err := requireOK(s.c.doRequest(r)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	var peers []string | ||||
| 	if err := decodeBody(resp, &peers); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return peers, nil | ||||
| } | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/client/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/client/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										236
									
								
								vendor/github.com/coreos/etcd/client/auth_role.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								vendor/github.com/coreos/etcd/client/auth_role.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| type Role struct { | ||||
| 	Role        string       `json:"role"` | ||||
| 	Permissions Permissions  `json:"permissions"` | ||||
| 	Grant       *Permissions `json:"grant,omitempty"` | ||||
| 	Revoke      *Permissions `json:"revoke,omitempty"` | ||||
| } | ||||
|  | ||||
| type Permissions struct { | ||||
| 	KV rwPermission `json:"kv"` | ||||
| } | ||||
|  | ||||
| type rwPermission struct { | ||||
| 	Read  []string `json:"read"` | ||||
| 	Write []string `json:"write"` | ||||
| } | ||||
|  | ||||
| type PermissionType int | ||||
|  | ||||
| const ( | ||||
| 	ReadPermission PermissionType = iota | ||||
| 	WritePermission | ||||
| 	ReadWritePermission | ||||
| ) | ||||
|  | ||||
| // NewAuthRoleAPI constructs a new AuthRoleAPI that uses HTTP to | ||||
| // interact with etcd's role creation and modification features. | ||||
| func NewAuthRoleAPI(c Client) AuthRoleAPI { | ||||
| 	return &httpAuthRoleAPI{ | ||||
| 		client: c, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type AuthRoleAPI interface { | ||||
| 	// AddRole adds a role. | ||||
| 	AddRole(ctx context.Context, role string) error | ||||
|  | ||||
| 	// RemoveRole removes a role. | ||||
| 	RemoveRole(ctx context.Context, role string) error | ||||
|  | ||||
| 	// GetRole retrieves role details. | ||||
| 	GetRole(ctx context.Context, role string) (*Role, error) | ||||
|  | ||||
| 	// GrantRoleKV grants a role some permission prefixes for the KV store. | ||||
| 	GrantRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error) | ||||
|  | ||||
| 	// RevokeRoleKV revokes some permission prefixes for a role on the KV store. | ||||
| 	RevokeRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error) | ||||
|  | ||||
| 	// ListRoles lists roles. | ||||
| 	ListRoles(ctx context.Context) ([]string, error) | ||||
| } | ||||
|  | ||||
| type httpAuthRoleAPI struct { | ||||
| 	client httpClient | ||||
| } | ||||
|  | ||||
| type authRoleAPIAction struct { | ||||
| 	verb string | ||||
| 	name string | ||||
| 	role *Role | ||||
| } | ||||
|  | ||||
| type authRoleAPIList struct{} | ||||
|  | ||||
| func (list *authRoleAPIList) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2AuthURL(ep, "roles", "") | ||||
| 	req, _ := http.NewRequest("GET", u.String(), nil) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| func (l *authRoleAPIAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2AuthURL(ep, "roles", l.name) | ||||
| 	if l.role == nil { | ||||
| 		req, _ := http.NewRequest(l.verb, u.String(), nil) | ||||
| 		return req | ||||
| 	} | ||||
| 	b, err := json.Marshal(l.role) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	body := bytes.NewReader(b) | ||||
| 	req, _ := http.NewRequest(l.verb, u.String(), body) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) ListRoles(ctx context.Context) ([]string, error) { | ||||
| 	resp, body, err := r.client.Do(ctx, &authRoleAPIList{}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var roleList struct { | ||||
| 		Roles []Role `json:"roles"` | ||||
| 	} | ||||
| 	if err = json.Unmarshal(body, &roleList); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	ret := make([]string, 0, len(roleList.Roles)) | ||||
| 	for _, r := range roleList.Roles { | ||||
| 		ret = append(ret, r.Role) | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) AddRole(ctx context.Context, rolename string) error { | ||||
| 	role := &Role{ | ||||
| 		Role: rolename, | ||||
| 	} | ||||
| 	return r.addRemoveRole(ctx, &authRoleAPIAction{ | ||||
| 		verb: "PUT", | ||||
| 		name: rolename, | ||||
| 		role: role, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) RemoveRole(ctx context.Context, rolename string) error { | ||||
| 	return r.addRemoveRole(ctx, &authRoleAPIAction{ | ||||
| 		verb: "DELETE", | ||||
| 		name: rolename, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) addRemoveRole(ctx context.Context, req *authRoleAPIAction) error { | ||||
| 	resp, body, err := r.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil { | ||||
| 		var sec authError | ||||
| 		err := json.Unmarshal(body, &sec) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return sec | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) GetRole(ctx context.Context, rolename string) (*Role, error) { | ||||
| 	return r.modRole(ctx, &authRoleAPIAction{ | ||||
| 		verb: "GET", | ||||
| 		name: rolename, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func buildRWPermission(prefixes []string, permType PermissionType) rwPermission { | ||||
| 	var out rwPermission | ||||
| 	switch permType { | ||||
| 	case ReadPermission: | ||||
| 		out.Read = prefixes | ||||
| 	case WritePermission: | ||||
| 		out.Write = prefixes | ||||
| 	case ReadWritePermission: | ||||
| 		out.Read = prefixes | ||||
| 		out.Write = prefixes | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) GrantRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) { | ||||
| 	rwp := buildRWPermission(prefixes, permType) | ||||
| 	role := &Role{ | ||||
| 		Role: rolename, | ||||
| 		Grant: &Permissions{ | ||||
| 			KV: rwp, | ||||
| 		}, | ||||
| 	} | ||||
| 	return r.modRole(ctx, &authRoleAPIAction{ | ||||
| 		verb: "PUT", | ||||
| 		name: rolename, | ||||
| 		role: role, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) RevokeRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) { | ||||
| 	rwp := buildRWPermission(prefixes, permType) | ||||
| 	role := &Role{ | ||||
| 		Role: rolename, | ||||
| 		Revoke: &Permissions{ | ||||
| 			KV: rwp, | ||||
| 		}, | ||||
| 	} | ||||
| 	return r.modRole(ctx, &authRoleAPIAction{ | ||||
| 		verb: "PUT", | ||||
| 		name: rolename, | ||||
| 		role: role, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (r *httpAuthRoleAPI) modRole(ctx context.Context, req *authRoleAPIAction) (*Role, error) { | ||||
| 	resp, body, err := r.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { | ||||
| 		var sec authError | ||||
| 		err = json.Unmarshal(body, &sec) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return nil, sec | ||||
| 	} | ||||
| 	var role Role | ||||
| 	if err = json.Unmarshal(body, &role); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &role, nil | ||||
| } | ||||
							
								
								
									
										319
									
								
								vendor/github.com/coreos/etcd/client/auth_user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								vendor/github.com/coreos/etcd/client/auth_user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,319 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	defaultV2AuthPrefix = "/v2/auth" | ||||
| ) | ||||
|  | ||||
| type User struct { | ||||
| 	User     string   `json:"user"` | ||||
| 	Password string   `json:"password,omitempty"` | ||||
| 	Roles    []string `json:"roles"` | ||||
| 	Grant    []string `json:"grant,omitempty"` | ||||
| 	Revoke   []string `json:"revoke,omitempty"` | ||||
| } | ||||
|  | ||||
| // userListEntry is the user representation given by the server for ListUsers | ||||
| type userListEntry struct { | ||||
| 	User  string `json:"user"` | ||||
| 	Roles []Role `json:"roles"` | ||||
| } | ||||
|  | ||||
| type UserRoles struct { | ||||
| 	User  string `json:"user"` | ||||
| 	Roles []Role `json:"roles"` | ||||
| } | ||||
|  | ||||
| func v2AuthURL(ep url.URL, action string, name string) *url.URL { | ||||
| 	if name != "" { | ||||
| 		ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name) | ||||
| 		return &ep | ||||
| 	} | ||||
| 	ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action) | ||||
| 	return &ep | ||||
| } | ||||
|  | ||||
| // NewAuthAPI constructs a new AuthAPI that uses HTTP to | ||||
| // interact with etcd's general auth features. | ||||
| func NewAuthAPI(c Client) AuthAPI { | ||||
| 	return &httpAuthAPI{ | ||||
| 		client: c, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type AuthAPI interface { | ||||
| 	// Enable auth. | ||||
| 	Enable(ctx context.Context) error | ||||
|  | ||||
| 	// Disable auth. | ||||
| 	Disable(ctx context.Context) error | ||||
| } | ||||
|  | ||||
| type httpAuthAPI struct { | ||||
| 	client httpClient | ||||
| } | ||||
|  | ||||
| func (s *httpAuthAPI) Enable(ctx context.Context) error { | ||||
| 	return s.enableDisable(ctx, &authAPIAction{"PUT"}) | ||||
| } | ||||
|  | ||||
| func (s *httpAuthAPI) Disable(ctx context.Context) error { | ||||
| 	return s.enableDisable(ctx, &authAPIAction{"DELETE"}) | ||||
| } | ||||
|  | ||||
| func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error { | ||||
| 	resp, body, err := s.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil { | ||||
| 		var sec authError | ||||
| 		err = json.Unmarshal(body, &sec) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return sec | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type authAPIAction struct { | ||||
| 	verb string | ||||
| } | ||||
|  | ||||
| func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2AuthURL(ep, "enable", "") | ||||
| 	req, _ := http.NewRequest(l.verb, u.String(), nil) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type authError struct { | ||||
| 	Message string `json:"message"` | ||||
| 	Code    int    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (e authError) Error() string { | ||||
| 	return e.Message | ||||
| } | ||||
|  | ||||
| // NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to | ||||
| // interact with etcd's user creation and modification features. | ||||
| func NewAuthUserAPI(c Client) AuthUserAPI { | ||||
| 	return &httpAuthUserAPI{ | ||||
| 		client: c, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type AuthUserAPI interface { | ||||
| 	// AddUser adds a user. | ||||
| 	AddUser(ctx context.Context, username string, password string) error | ||||
|  | ||||
| 	// RemoveUser removes a user. | ||||
| 	RemoveUser(ctx context.Context, username string) error | ||||
|  | ||||
| 	// GetUser retrieves user details. | ||||
| 	GetUser(ctx context.Context, username string) (*User, error) | ||||
|  | ||||
| 	// GrantUser grants a user some permission roles. | ||||
| 	GrantUser(ctx context.Context, username string, roles []string) (*User, error) | ||||
|  | ||||
| 	// RevokeUser revokes some permission roles from a user. | ||||
| 	RevokeUser(ctx context.Context, username string, roles []string) (*User, error) | ||||
|  | ||||
| 	// ChangePassword changes the user's password. | ||||
| 	ChangePassword(ctx context.Context, username string, password string) (*User, error) | ||||
|  | ||||
| 	// ListUsers lists the users. | ||||
| 	ListUsers(ctx context.Context) ([]string, error) | ||||
| } | ||||
|  | ||||
| type httpAuthUserAPI struct { | ||||
| 	client httpClient | ||||
| } | ||||
|  | ||||
| type authUserAPIAction struct { | ||||
| 	verb     string | ||||
| 	username string | ||||
| 	user     *User | ||||
| } | ||||
|  | ||||
| type authUserAPIList struct{} | ||||
|  | ||||
| func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2AuthURL(ep, "users", "") | ||||
| 	req, _ := http.NewRequest("GET", u.String(), nil) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2AuthURL(ep, "users", l.username) | ||||
| 	if l.user == nil { | ||||
| 		req, _ := http.NewRequest(l.verb, u.String(), nil) | ||||
| 		return req | ||||
| 	} | ||||
| 	b, err := json.Marshal(l.user) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	body := bytes.NewReader(b) | ||||
| 	req, _ := http.NewRequest(l.verb, u.String(), body) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) { | ||||
| 	resp, body, err := u.client.Do(ctx, &authUserAPIList{}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { | ||||
| 		var sec authError | ||||
| 		err = json.Unmarshal(body, &sec) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return nil, sec | ||||
| 	} | ||||
|  | ||||
| 	var userList struct { | ||||
| 		Users []userListEntry `json:"users"` | ||||
| 	} | ||||
|  | ||||
| 	if err = json.Unmarshal(body, &userList); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	ret := make([]string, 0, len(userList.Users)) | ||||
| 	for _, u := range userList.Users { | ||||
| 		ret = append(ret, u.User) | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error { | ||||
| 	user := &User{ | ||||
| 		User:     username, | ||||
| 		Password: password, | ||||
| 	} | ||||
| 	return u.addRemoveUser(ctx, &authUserAPIAction{ | ||||
| 		verb:     "PUT", | ||||
| 		username: username, | ||||
| 		user:     user, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error { | ||||
| 	return u.addRemoveUser(ctx, &authUserAPIAction{ | ||||
| 		verb:     "DELETE", | ||||
| 		username: username, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error { | ||||
| 	resp, body, err := u.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil { | ||||
| 		var sec authError | ||||
| 		err = json.Unmarshal(body, &sec) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return sec | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) { | ||||
| 	return u.modUser(ctx, &authUserAPIAction{ | ||||
| 		verb:     "GET", | ||||
| 		username: username, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) { | ||||
| 	user := &User{ | ||||
| 		User:  username, | ||||
| 		Grant: roles, | ||||
| 	} | ||||
| 	return u.modUser(ctx, &authUserAPIAction{ | ||||
| 		verb:     "PUT", | ||||
| 		username: username, | ||||
| 		user:     user, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) { | ||||
| 	user := &User{ | ||||
| 		User:   username, | ||||
| 		Revoke: roles, | ||||
| 	} | ||||
| 	return u.modUser(ctx, &authUserAPIAction{ | ||||
| 		verb:     "PUT", | ||||
| 		username: username, | ||||
| 		user:     user, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) { | ||||
| 	user := &User{ | ||||
| 		User:     username, | ||||
| 		Password: password, | ||||
| 	} | ||||
| 	return u.modUser(ctx, &authUserAPIAction{ | ||||
| 		verb:     "PUT", | ||||
| 		username: username, | ||||
| 		user:     user, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) { | ||||
| 	resp, body, err := u.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { | ||||
| 		var sec authError | ||||
| 		err = json.Unmarshal(body, &sec) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return nil, sec | ||||
| 	} | ||||
| 	var user User | ||||
| 	if err = json.Unmarshal(body, &user); err != nil { | ||||
| 		var userR UserRoles | ||||
| 		if urerr := json.Unmarshal(body, &userR); urerr != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		user.User = userR.User | ||||
| 		for _, r := range userR.Roles { | ||||
| 			user.Roles = append(user.Roles, r.Role) | ||||
| 		} | ||||
| 	} | ||||
| 	return &user, nil | ||||
| } | ||||
							
								
								
									
										18
									
								
								vendor/github.com/coreos/etcd/client/cancelreq.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/coreos/etcd/client/cancelreq.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| // Copyright 2015 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. | ||||
|  | ||||
| // borrowed from golang/net/context/ctxhttp/cancelreq.go | ||||
|  | ||||
| package client | ||||
|  | ||||
| import "net/http" | ||||
|  | ||||
| func requestCanceler(tr CancelableTransport, req *http.Request) func() { | ||||
| 	ch := make(chan struct{}) | ||||
| 	req.Cancel = ch | ||||
|  | ||||
| 	return func() { | ||||
| 		close(ch) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										710
									
								
								vendor/github.com/coreos/etcd/client/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										710
									
								
								vendor/github.com/coreos/etcd/client/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,710 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/etcd/version" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrNoEndpoints           = errors.New("client: no endpoints available") | ||||
| 	ErrTooManyRedirects      = errors.New("client: too many redirects") | ||||
| 	ErrClusterUnavailable    = errors.New("client: etcd cluster is unavailable or misconfigured") | ||||
| 	ErrNoLeaderEndpoint      = errors.New("client: no leader endpoint available") | ||||
| 	errTooManyRedirectChecks = errors.New("client: too many redirect checks") | ||||
|  | ||||
| 	// oneShotCtxValue is set on a context using WithValue(&oneShotValue) so | ||||
| 	// that Do() will not retry a request | ||||
| 	oneShotCtxValue interface{} | ||||
| ) | ||||
|  | ||||
| var DefaultRequestTimeout = 5 * time.Second | ||||
|  | ||||
| var DefaultTransport CancelableTransport = &http.Transport{ | ||||
| 	Proxy: http.ProxyFromEnvironment, | ||||
| 	Dial: (&net.Dialer{ | ||||
| 		Timeout:   30 * time.Second, | ||||
| 		KeepAlive: 30 * time.Second, | ||||
| 	}).Dial, | ||||
| 	TLSHandshakeTimeout: 10 * time.Second, | ||||
| } | ||||
|  | ||||
| type EndpointSelectionMode int | ||||
|  | ||||
| const ( | ||||
| 	// EndpointSelectionRandom is the default value of the 'SelectionMode'. | ||||
| 	// As the name implies, the client object will pick a node from the members | ||||
| 	// of the cluster in a random fashion. If the cluster has three members, A, B, | ||||
| 	// and C, the client picks any node from its three members as its request | ||||
| 	// destination. | ||||
| 	EndpointSelectionRandom EndpointSelectionMode = iota | ||||
|  | ||||
| 	// If 'SelectionMode' is set to 'EndpointSelectionPrioritizeLeader', | ||||
| 	// requests are sent directly to the cluster leader. This reduces | ||||
| 	// forwarding roundtrips compared to making requests to etcd followers | ||||
| 	// who then forward them to the cluster leader. In the event of a leader | ||||
| 	// failure, however, clients configured this way cannot prioritize among | ||||
| 	// the remaining etcd followers. Therefore, when a client sets 'SelectionMode' | ||||
| 	// to 'EndpointSelectionPrioritizeLeader', it must use 'client.AutoSync()' to | ||||
| 	// maintain its knowledge of current cluster state. | ||||
| 	// | ||||
| 	// This mode should be used with Client.AutoSync(). | ||||
| 	EndpointSelectionPrioritizeLeader | ||||
| ) | ||||
|  | ||||
| type Config struct { | ||||
| 	// Endpoints defines a set of URLs (schemes, hosts and ports only) | ||||
| 	// that can be used to communicate with a logical etcd cluster. For | ||||
| 	// example, a three-node cluster could be provided like so: | ||||
| 	// | ||||
| 	// 	Endpoints: []string{ | ||||
| 	//		"http://node1.example.com:2379", | ||||
| 	//		"http://node2.example.com:2379", | ||||
| 	//		"http://node3.example.com:2379", | ||||
| 	//	} | ||||
| 	// | ||||
| 	// If multiple endpoints are provided, the Client will attempt to | ||||
| 	// use them all in the event that one or more of them are unusable. | ||||
| 	// | ||||
| 	// If Client.Sync is ever called, the Client may cache an alternate | ||||
| 	// set of endpoints to continue operation. | ||||
| 	Endpoints []string | ||||
|  | ||||
| 	// Transport is used by the Client to drive HTTP requests. If not | ||||
| 	// provided, DefaultTransport will be used. | ||||
| 	Transport CancelableTransport | ||||
|  | ||||
| 	// CheckRedirect specifies the policy for handling HTTP redirects. | ||||
| 	// If CheckRedirect is not nil, the Client calls it before | ||||
| 	// following an HTTP redirect. The sole argument is the number of | ||||
| 	// requests that have already been made. If CheckRedirect returns | ||||
| 	// an error, Client.Do will not make any further requests and return | ||||
| 	// the error back it to the caller. | ||||
| 	// | ||||
| 	// If CheckRedirect is nil, the Client uses its default policy, | ||||
| 	// which is to stop after 10 consecutive requests. | ||||
| 	CheckRedirect CheckRedirectFunc | ||||
|  | ||||
| 	// Username specifies the user credential to add as an authorization header | ||||
| 	Username string | ||||
|  | ||||
| 	// Password is the password for the specified user to add as an authorization header | ||||
| 	// to the request. | ||||
| 	Password string | ||||
|  | ||||
| 	// HeaderTimeoutPerRequest specifies the time limit to wait for response | ||||
| 	// header in a single request made by the Client. The timeout includes | ||||
| 	// connection time, any redirects, and header wait time. | ||||
| 	// | ||||
| 	// For non-watch GET request, server returns the response body immediately. | ||||
| 	// For PUT/POST/DELETE request, server will attempt to commit request | ||||
| 	// before responding, which is expected to take `100ms + 2 * RTT`. | ||||
| 	// For watch request, server returns the header immediately to notify Client | ||||
| 	// watch start. But if server is behind some kind of proxy, the response | ||||
| 	// header may be cached at proxy, and Client cannot rely on this behavior. | ||||
| 	// | ||||
| 	// Especially, wait request will ignore this timeout. | ||||
| 	// | ||||
| 	// One API call may send multiple requests to different etcd servers until it | ||||
| 	// succeeds. Use context of the API to specify the overall timeout. | ||||
| 	// | ||||
| 	// A HeaderTimeoutPerRequest of zero means no timeout. | ||||
| 	HeaderTimeoutPerRequest time.Duration | ||||
|  | ||||
| 	// SelectionMode is an EndpointSelectionMode enum that specifies the | ||||
| 	// policy for choosing the etcd cluster node to which requests are sent. | ||||
| 	SelectionMode EndpointSelectionMode | ||||
| } | ||||
|  | ||||
| func (cfg *Config) transport() CancelableTransport { | ||||
| 	if cfg.Transport == nil { | ||||
| 		return DefaultTransport | ||||
| 	} | ||||
| 	return cfg.Transport | ||||
| } | ||||
|  | ||||
| func (cfg *Config) checkRedirect() CheckRedirectFunc { | ||||
| 	if cfg.CheckRedirect == nil { | ||||
| 		return DefaultCheckRedirect | ||||
| 	} | ||||
| 	return cfg.CheckRedirect | ||||
| } | ||||
|  | ||||
| // CancelableTransport mimics net/http.Transport, but requires that | ||||
| // the object also support request cancellation. | ||||
| type CancelableTransport interface { | ||||
| 	http.RoundTripper | ||||
| 	CancelRequest(req *http.Request) | ||||
| } | ||||
|  | ||||
| type CheckRedirectFunc func(via int) error | ||||
|  | ||||
| // DefaultCheckRedirect follows up to 10 redirects, but no more. | ||||
| var DefaultCheckRedirect CheckRedirectFunc = func(via int) error { | ||||
| 	if via > 10 { | ||||
| 		return ErrTooManyRedirects | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type Client interface { | ||||
| 	// Sync updates the internal cache of the etcd cluster's membership. | ||||
| 	Sync(context.Context) error | ||||
|  | ||||
| 	// AutoSync periodically calls Sync() every given interval. | ||||
| 	// The recommended sync interval is 10 seconds to 1 minute, which does | ||||
| 	// not bring too much overhead to server and makes client catch up the | ||||
| 	// cluster change in time. | ||||
| 	// | ||||
| 	// The example to use it: | ||||
| 	// | ||||
| 	//  for { | ||||
| 	//      err := client.AutoSync(ctx, 10*time.Second) | ||||
| 	//      if err == context.DeadlineExceeded || err == context.Canceled { | ||||
| 	//          break | ||||
| 	//      } | ||||
| 	//      log.Print(err) | ||||
| 	//  } | ||||
| 	AutoSync(context.Context, time.Duration) error | ||||
|  | ||||
| 	// Endpoints returns a copy of the current set of API endpoints used | ||||
| 	// by Client to resolve HTTP requests. If Sync has ever been called, | ||||
| 	// this may differ from the initial Endpoints provided in the Config. | ||||
| 	Endpoints() []string | ||||
|  | ||||
| 	// SetEndpoints sets the set of API endpoints used by Client to resolve | ||||
| 	// HTTP requests. If the given endpoints are not valid, an error will be | ||||
| 	// returned | ||||
| 	SetEndpoints(eps []string) error | ||||
|  | ||||
| 	// GetVersion retrieves the current etcd server and cluster version | ||||
| 	GetVersion(ctx context.Context) (*version.Versions, error) | ||||
|  | ||||
| 	httpClient | ||||
| } | ||||
|  | ||||
| func New(cfg Config) (Client, error) { | ||||
| 	c := &httpClusterClient{ | ||||
| 		clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest), | ||||
| 		rand:          rand.New(rand.NewSource(int64(time.Now().Nanosecond()))), | ||||
| 		selectionMode: cfg.SelectionMode, | ||||
| 	} | ||||
| 	if cfg.Username != "" { | ||||
| 		c.credentials = &credentials{ | ||||
| 			username: cfg.Username, | ||||
| 			password: cfg.Password, | ||||
| 		} | ||||
| 	} | ||||
| 	if err := c.SetEndpoints(cfg.Endpoints); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return c, nil | ||||
| } | ||||
|  | ||||
| type httpClient interface { | ||||
| 	Do(context.Context, httpAction) (*http.Response, []byte, error) | ||||
| } | ||||
|  | ||||
| func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory { | ||||
| 	return func(ep url.URL) httpClient { | ||||
| 		return &redirectFollowingHTTPClient{ | ||||
| 			checkRedirect: cr, | ||||
| 			client: &simpleHTTPClient{ | ||||
| 				transport:     tr, | ||||
| 				endpoint:      ep, | ||||
| 				headerTimeout: headerTimeout, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type credentials struct { | ||||
| 	username string | ||||
| 	password string | ||||
| } | ||||
|  | ||||
| type httpClientFactory func(url.URL) httpClient | ||||
|  | ||||
| type httpAction interface { | ||||
| 	HTTPRequest(url.URL) *http.Request | ||||
| } | ||||
|  | ||||
| type httpClusterClient struct { | ||||
| 	clientFactory httpClientFactory | ||||
| 	endpoints     []url.URL | ||||
| 	pinned        int | ||||
| 	credentials   *credentials | ||||
| 	sync.RWMutex | ||||
| 	rand          *rand.Rand | ||||
| 	selectionMode EndpointSelectionMode | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) getLeaderEndpoint(ctx context.Context, eps []url.URL) (string, error) { | ||||
| 	ceps := make([]url.URL, len(eps)) | ||||
| 	copy(ceps, eps) | ||||
|  | ||||
| 	// To perform a lookup on the new endpoint list without using the current | ||||
| 	// client, we'll copy it | ||||
| 	clientCopy := &httpClusterClient{ | ||||
| 		clientFactory: c.clientFactory, | ||||
| 		credentials:   c.credentials, | ||||
| 		rand:          c.rand, | ||||
|  | ||||
| 		pinned:    0, | ||||
| 		endpoints: ceps, | ||||
| 	} | ||||
|  | ||||
| 	mAPI := NewMembersAPI(clientCopy) | ||||
| 	leader, err := mAPI.Leader(ctx) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if len(leader.ClientURLs) == 0 { | ||||
| 		return "", ErrNoLeaderEndpoint | ||||
| 	} | ||||
|  | ||||
| 	return leader.ClientURLs[0], nil // TODO: how to handle multiple client URLs? | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) parseEndpoints(eps []string) ([]url.URL, error) { | ||||
| 	if len(eps) == 0 { | ||||
| 		return []url.URL{}, ErrNoEndpoints | ||||
| 	} | ||||
|  | ||||
| 	neps := make([]url.URL, len(eps)) | ||||
| 	for i, ep := range eps { | ||||
| 		u, err := url.Parse(ep) | ||||
| 		if err != nil { | ||||
| 			return []url.URL{}, err | ||||
| 		} | ||||
| 		neps[i] = *u | ||||
| 	} | ||||
| 	return neps, nil | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) SetEndpoints(eps []string) error { | ||||
| 	neps, err := c.parseEndpoints(eps) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
|  | ||||
| 	c.endpoints = shuffleEndpoints(c.rand, neps) | ||||
| 	// We're not doing anything for PrioritizeLeader here. This is | ||||
| 	// due to not having a context meaning we can't call getLeaderEndpoint | ||||
| 	// However, if you're using PrioritizeLeader, you've already been told | ||||
| 	// to regularly call sync, where we do have a ctx, and can figure the | ||||
| 	// leader. PrioritizeLeader is also quite a loose guarantee, so deal | ||||
| 	// with it | ||||
| 	c.pinned = 0 | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) { | ||||
| 	action := act | ||||
| 	c.RLock() | ||||
| 	leps := len(c.endpoints) | ||||
| 	eps := make([]url.URL, leps) | ||||
| 	n := copy(eps, c.endpoints) | ||||
| 	pinned := c.pinned | ||||
|  | ||||
| 	if c.credentials != nil { | ||||
| 		action = &authedAction{ | ||||
| 			act:         act, | ||||
| 			credentials: *c.credentials, | ||||
| 		} | ||||
| 	} | ||||
| 	c.RUnlock() | ||||
|  | ||||
| 	if leps == 0 { | ||||
| 		return nil, nil, ErrNoEndpoints | ||||
| 	} | ||||
|  | ||||
| 	if leps != n { | ||||
| 		return nil, nil, errors.New("unable to pick endpoint: copy failed") | ||||
| 	} | ||||
|  | ||||
| 	var resp *http.Response | ||||
| 	var body []byte | ||||
| 	var err error | ||||
| 	cerr := &ClusterError{} | ||||
| 	isOneShot := ctx.Value(&oneShotCtxValue) != nil | ||||
|  | ||||
| 	for i := pinned; i < leps+pinned; i++ { | ||||
| 		k := i % leps | ||||
| 		hc := c.clientFactory(eps[k]) | ||||
| 		resp, body, err = hc.Do(ctx, action) | ||||
| 		if err != nil { | ||||
| 			cerr.Errors = append(cerr.Errors, err) | ||||
| 			if err == ctx.Err() { | ||||
| 				return nil, nil, ctx.Err() | ||||
| 			} | ||||
| 			if err == context.Canceled || err == context.DeadlineExceeded { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 		} else if resp.StatusCode/100 == 5 { | ||||
| 			switch resp.StatusCode { | ||||
| 			case http.StatusInternalServerError, http.StatusServiceUnavailable: | ||||
| 				// TODO: make sure this is a no leader response | ||||
| 				cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s has no leader", eps[k].String())) | ||||
| 			default: | ||||
| 				cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode))) | ||||
| 			} | ||||
| 			err = cerr.Errors[0] | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			if !isOneShot { | ||||
| 				continue | ||||
| 			} | ||||
| 			c.Lock() | ||||
| 			c.pinned = (k + 1) % leps | ||||
| 			c.Unlock() | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		if k != pinned { | ||||
| 			c.Lock() | ||||
| 			c.pinned = k | ||||
| 			c.Unlock() | ||||
| 		} | ||||
| 		return resp, body, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, nil, cerr | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) Endpoints() []string { | ||||
| 	c.RLock() | ||||
| 	defer c.RUnlock() | ||||
|  | ||||
| 	eps := make([]string, len(c.endpoints)) | ||||
| 	for i, ep := range c.endpoints { | ||||
| 		eps[i] = ep.String() | ||||
| 	} | ||||
|  | ||||
| 	return eps | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) Sync(ctx context.Context) error { | ||||
| 	mAPI := NewMembersAPI(c) | ||||
| 	ms, err := mAPI.List(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var eps []string | ||||
| 	for _, m := range ms { | ||||
| 		eps = append(eps, m.ClientURLs...) | ||||
| 	} | ||||
|  | ||||
| 	neps, err := c.parseEndpoints(eps) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	npin := 0 | ||||
|  | ||||
| 	switch c.selectionMode { | ||||
| 	case EndpointSelectionRandom: | ||||
| 		c.RLock() | ||||
| 		eq := endpointsEqual(c.endpoints, neps) | ||||
| 		c.RUnlock() | ||||
|  | ||||
| 		if eq { | ||||
| 			return nil | ||||
| 		} | ||||
| 		// When items in the endpoint list changes, we choose a new pin | ||||
| 		neps = shuffleEndpoints(c.rand, neps) | ||||
| 	case EndpointSelectionPrioritizeLeader: | ||||
| 		nle, err := c.getLeaderEndpoint(ctx, neps) | ||||
| 		if err != nil { | ||||
| 			return ErrNoLeaderEndpoint | ||||
| 		} | ||||
|  | ||||
| 		for i, n := range neps { | ||||
| 			if n.String() == nle { | ||||
| 				npin = i | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	default: | ||||
| 		return fmt.Errorf("invalid endpoint selection mode: %d", c.selectionMode) | ||||
| 	} | ||||
|  | ||||
| 	c.Lock() | ||||
| 	defer c.Unlock() | ||||
| 	c.endpoints = neps | ||||
| 	c.pinned = npin | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error { | ||||
| 	ticker := time.NewTicker(interval) | ||||
| 	defer ticker.Stop() | ||||
| 	for { | ||||
| 		err := c.Sync(ctx) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			return ctx.Err() | ||||
| 		case <-ticker.C: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) { | ||||
| 	act := &getAction{Prefix: "/version"} | ||||
|  | ||||
| 	resp, body, err := c.Do(ctx, act) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	switch resp.StatusCode { | ||||
| 	case http.StatusOK: | ||||
| 		if len(body) == 0 { | ||||
| 			return nil, ErrEmptyBody | ||||
| 		} | ||||
| 		var vresp version.Versions | ||||
| 		if err := json.Unmarshal(body, &vresp); err != nil { | ||||
| 			return nil, ErrInvalidJSON | ||||
| 		} | ||||
| 		return &vresp, nil | ||||
| 	default: | ||||
| 		var etcdErr Error | ||||
| 		if err := json.Unmarshal(body, &etcdErr); err != nil { | ||||
| 			return nil, ErrInvalidJSON | ||||
| 		} | ||||
| 		return nil, etcdErr | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type roundTripResponse struct { | ||||
| 	resp *http.Response | ||||
| 	err  error | ||||
| } | ||||
|  | ||||
| type simpleHTTPClient struct { | ||||
| 	transport     CancelableTransport | ||||
| 	endpoint      url.URL | ||||
| 	headerTimeout time.Duration | ||||
| } | ||||
|  | ||||
| func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) { | ||||
| 	req := act.HTTPRequest(c.endpoint) | ||||
|  | ||||
| 	if err := printcURL(req); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	isWait := false | ||||
| 	if req != nil && req.URL != nil { | ||||
| 		ws := req.URL.Query().Get("wait") | ||||
| 		if len(ws) != 0 { | ||||
| 			var err error | ||||
| 			isWait, err = strconv.ParseBool(ws) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, fmt.Errorf("wrong wait value %s (%v for %+v)", ws, err, req) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var hctx context.Context | ||||
| 	var hcancel context.CancelFunc | ||||
| 	if !isWait && c.headerTimeout > 0 { | ||||
| 		hctx, hcancel = context.WithTimeout(ctx, c.headerTimeout) | ||||
| 	} else { | ||||
| 		hctx, hcancel = context.WithCancel(ctx) | ||||
| 	} | ||||
| 	defer hcancel() | ||||
|  | ||||
| 	reqcancel := requestCanceler(c.transport, req) | ||||
|  | ||||
| 	rtchan := make(chan roundTripResponse, 1) | ||||
| 	go func() { | ||||
| 		resp, err := c.transport.RoundTrip(req) | ||||
| 		rtchan <- roundTripResponse{resp: resp, err: err} | ||||
| 		close(rtchan) | ||||
| 	}() | ||||
|  | ||||
| 	var resp *http.Response | ||||
| 	var err error | ||||
|  | ||||
| 	select { | ||||
| 	case rtresp := <-rtchan: | ||||
| 		resp, err = rtresp.resp, rtresp.err | ||||
| 	case <-hctx.Done(): | ||||
| 		// cancel and wait for request to actually exit before continuing | ||||
| 		reqcancel() | ||||
| 		rtresp := <-rtchan | ||||
| 		resp = rtresp.resp | ||||
| 		switch { | ||||
| 		case ctx.Err() != nil: | ||||
| 			err = ctx.Err() | ||||
| 		case hctx.Err() != nil: | ||||
| 			err = fmt.Errorf("client: endpoint %s exceeded header timeout", c.endpoint.String()) | ||||
| 		default: | ||||
| 			panic("failed to get error from context") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// always check for resp nil-ness to deal with possible | ||||
| 	// race conditions between channels above | ||||
| 	defer func() { | ||||
| 		if resp != nil { | ||||
| 			resp.Body.Close() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	var body []byte | ||||
| 	done := make(chan struct{}) | ||||
| 	go func() { | ||||
| 		body, err = ioutil.ReadAll(resp.Body) | ||||
| 		done <- struct{}{} | ||||
| 	}() | ||||
|  | ||||
| 	select { | ||||
| 	case <-ctx.Done(): | ||||
| 		resp.Body.Close() | ||||
| 		<-done | ||||
| 		return nil, nil, ctx.Err() | ||||
| 	case <-done: | ||||
| 	} | ||||
|  | ||||
| 	return resp, body, err | ||||
| } | ||||
|  | ||||
| type authedAction struct { | ||||
| 	act         httpAction | ||||
| 	credentials credentials | ||||
| } | ||||
|  | ||||
| func (a *authedAction) HTTPRequest(url url.URL) *http.Request { | ||||
| 	r := a.act.HTTPRequest(url) | ||||
| 	r.SetBasicAuth(a.credentials.username, a.credentials.password) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| type redirectFollowingHTTPClient struct { | ||||
| 	client        httpClient | ||||
| 	checkRedirect CheckRedirectFunc | ||||
| } | ||||
|  | ||||
| func (r *redirectFollowingHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) { | ||||
| 	next := act | ||||
| 	for i := 0; i < 100; i++ { | ||||
| 		if i > 0 { | ||||
| 			if err := r.checkRedirect(i); err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 		} | ||||
| 		resp, body, err := r.client.Do(ctx, next) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		if resp.StatusCode/100 == 3 { | ||||
| 			hdr := resp.Header.Get("Location") | ||||
| 			if hdr == "" { | ||||
| 				return nil, nil, fmt.Errorf("Location header not set") | ||||
| 			} | ||||
| 			loc, err := url.Parse(hdr) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, fmt.Errorf("Location header not valid URL: %s", hdr) | ||||
| 			} | ||||
| 			next = &redirectedHTTPAction{ | ||||
| 				action:   act, | ||||
| 				location: *loc, | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		return resp, body, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, nil, errTooManyRedirectChecks | ||||
| } | ||||
|  | ||||
| type redirectedHTTPAction struct { | ||||
| 	action   httpAction | ||||
| 	location url.URL | ||||
| } | ||||
|  | ||||
| func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	orig := r.action.HTTPRequest(ep) | ||||
| 	orig.URL = &r.location | ||||
| 	return orig | ||||
| } | ||||
|  | ||||
| func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL { | ||||
| 	// copied from Go 1.9<= rand.Rand.Perm | ||||
| 	n := len(eps) | ||||
| 	p := make([]int, n) | ||||
| 	for i := 0; i < n; i++ { | ||||
| 		j := r.Intn(i + 1) | ||||
| 		p[i] = p[j] | ||||
| 		p[j] = i | ||||
| 	} | ||||
| 	neps := make([]url.URL, n) | ||||
| 	for i, k := range p { | ||||
| 		neps[i] = eps[k] | ||||
| 	} | ||||
| 	return neps | ||||
| } | ||||
|  | ||||
| func endpointsEqual(left, right []url.URL) bool { | ||||
| 	if len(left) != len(right) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	sLeft := make([]string, len(left)) | ||||
| 	sRight := make([]string, len(right)) | ||||
| 	for i, l := range left { | ||||
| 		sLeft[i] = l.String() | ||||
| 	} | ||||
| 	for i, r := range right { | ||||
| 		sRight[i] = r.String() | ||||
| 	} | ||||
|  | ||||
| 	sort.Strings(sLeft) | ||||
| 	sort.Strings(sRight) | ||||
| 	for i := range sLeft { | ||||
| 		if sLeft[i] != sRight[i] { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
							
								
								
									
										37
									
								
								vendor/github.com/coreos/etcd/client/cluster_error.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/coreos/etcd/client/cluster_error.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| type ClusterError struct { | ||||
| 	Errors []error | ||||
| } | ||||
|  | ||||
| func (ce *ClusterError) Error() string { | ||||
| 	s := ErrClusterUnavailable.Error() | ||||
| 	for i, e := range ce.Errors { | ||||
| 		s += fmt.Sprintf("; error #%d: %s\n", i, e) | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| func (ce *ClusterError) Detail() string { | ||||
| 	s := "" | ||||
| 	for i, e := range ce.Errors { | ||||
| 		s += fmt.Sprintf("error #%d: %s\n", i, e) | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
							
								
								
									
										70
									
								
								vendor/github.com/coreos/etcd/client/curl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vendor/github.com/coreos/etcd/client/curl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	cURLDebug = false | ||||
| ) | ||||
|  | ||||
| func EnablecURLDebug() { | ||||
| 	cURLDebug = true | ||||
| } | ||||
|  | ||||
| func DisablecURLDebug() { | ||||
| 	cURLDebug = false | ||||
| } | ||||
|  | ||||
| // printcURL prints the cURL equivalent request to stderr. | ||||
| // It returns an error if the body of the request cannot | ||||
| // be read. | ||||
| // The caller MUST cancel the request if there is an error. | ||||
| func printcURL(req *http.Request) error { | ||||
| 	if !cURLDebug { | ||||
| 		return nil | ||||
| 	} | ||||
| 	var ( | ||||
| 		command string | ||||
| 		b       []byte | ||||
| 		err     error | ||||
| 	) | ||||
|  | ||||
| 	if req.URL != nil { | ||||
| 		command = fmt.Sprintf("curl -X %s %s", req.Method, req.URL.String()) | ||||
| 	} | ||||
|  | ||||
| 	if req.Body != nil { | ||||
| 		b, err = ioutil.ReadAll(req.Body) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		command += fmt.Sprintf(" -d %q", string(b)) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Fprintf(os.Stderr, "cURL Command: %s\n", command) | ||||
|  | ||||
| 	// reset body | ||||
| 	body := bytes.NewBuffer(b) | ||||
| 	req.Body = ioutil.NopCloser(body) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										40
									
								
								vendor/github.com/coreos/etcd/client/discover.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/coreos/etcd/client/discover.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"github.com/coreos/etcd/pkg/srv" | ||||
| ) | ||||
|  | ||||
| // Discoverer is an interface that wraps the Discover method. | ||||
| type Discoverer interface { | ||||
| 	// Discover looks up the etcd servers for the domain. | ||||
| 	Discover(domain string) ([]string, error) | ||||
| } | ||||
|  | ||||
| type srvDiscover struct{} | ||||
|  | ||||
| // NewSRVDiscover constructs a new Discoverer that uses the stdlib to lookup SRV records. | ||||
| func NewSRVDiscover() Discoverer { | ||||
| 	return &srvDiscover{} | ||||
| } | ||||
|  | ||||
| func (d *srvDiscover) Discover(domain string) ([]string, error) { | ||||
| 	srvs, err := srv.GetClient("etcd-client", domain) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return srvs.Endpoints, nil | ||||
| } | ||||
							
								
								
									
										73
									
								
								vendor/github.com/coreos/etcd/client/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								vendor/github.com/coreos/etcd/client/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client provides bindings for the etcd APIs. | ||||
|  | ||||
| Create a Config and exchange it for a Client: | ||||
|  | ||||
| 	import ( | ||||
| 		"net/http" | ||||
| 		"context" | ||||
|  | ||||
| 		"github.com/coreos/etcd/client" | ||||
| 	) | ||||
|  | ||||
| 	cfg := client.Config{ | ||||
| 		Endpoints: []string{"http://127.0.0.1:2379"}, | ||||
| 		Transport: DefaultTransport, | ||||
| 	} | ||||
|  | ||||
| 	c, err := client.New(cfg) | ||||
| 	if err != nil { | ||||
| 		// handle error | ||||
| 	} | ||||
|  | ||||
| Clients are safe for concurrent use by multiple goroutines. | ||||
|  | ||||
| Create a KeysAPI using the Client, then use it to interact with etcd: | ||||
|  | ||||
| 	kAPI := client.NewKeysAPI(c) | ||||
|  | ||||
| 	// create a new key /foo with the value "bar" | ||||
| 	_, err = kAPI.Create(context.Background(), "/foo", "bar") | ||||
| 	if err != nil { | ||||
| 		// handle error | ||||
| 	} | ||||
|  | ||||
| 	// delete the newly created key only if the value is still "bar" | ||||
| 	_, err = kAPI.Delete(context.Background(), "/foo", &DeleteOptions{PrevValue: "bar"}) | ||||
| 	if err != nil { | ||||
| 		// handle error | ||||
| 	} | ||||
|  | ||||
| Use a custom context to set timeouts on your operations: | ||||
|  | ||||
| 	import "time" | ||||
|  | ||||
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	// set a new key, ignoring its previous state | ||||
| 	_, err := kAPI.Set(ctx, "/ping", "pong", nil) | ||||
| 	if err != nil { | ||||
| 		if err == context.DeadlineExceeded { | ||||
| 			// request took longer than 5s | ||||
| 		} else { | ||||
| 			// handle error | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| */ | ||||
| package client | ||||
							
								
								
									
										17
									
								
								vendor/github.com/coreos/etcd/client/integration/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/coreos/etcd/client/integration/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // Copyright 2016 The etcd Authors | ||||
| // | ||||
| // 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 integration implements tests built upon embedded etcd, focusing on | ||||
| // the correctness of the etcd v2 client. | ||||
| package integration | ||||
							
								
								
									
										5218
									
								
								vendor/github.com/coreos/etcd/client/keys.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5218
									
								
								vendor/github.com/coreos/etcd/client/keys.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										681
									
								
								vendor/github.com/coreos/etcd/client/keys.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										681
									
								
								vendor/github.com/coreos/etcd/client/keys.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,681 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| //go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/etcd/pkg/pathutil" | ||||
| 	"github.com/ugorji/go/codec" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ErrorCodeKeyNotFound  = 100 | ||||
| 	ErrorCodeTestFailed   = 101 | ||||
| 	ErrorCodeNotFile      = 102 | ||||
| 	ErrorCodeNotDir       = 104 | ||||
| 	ErrorCodeNodeExist    = 105 | ||||
| 	ErrorCodeRootROnly    = 107 | ||||
| 	ErrorCodeDirNotEmpty  = 108 | ||||
| 	ErrorCodeUnauthorized = 110 | ||||
|  | ||||
| 	ErrorCodePrevValueRequired = 201 | ||||
| 	ErrorCodeTTLNaN            = 202 | ||||
| 	ErrorCodeIndexNaN          = 203 | ||||
| 	ErrorCodeInvalidField      = 209 | ||||
| 	ErrorCodeInvalidForm       = 210 | ||||
|  | ||||
| 	ErrorCodeRaftInternal = 300 | ||||
| 	ErrorCodeLeaderElect  = 301 | ||||
|  | ||||
| 	ErrorCodeWatcherCleared    = 400 | ||||
| 	ErrorCodeEventIndexCleared = 401 | ||||
| ) | ||||
|  | ||||
| type Error struct { | ||||
| 	Code    int    `json:"errorCode"` | ||||
| 	Message string `json:"message"` | ||||
| 	Cause   string `json:"cause"` | ||||
| 	Index   uint64 `json:"index"` | ||||
| } | ||||
|  | ||||
| func (e Error) Error() string { | ||||
| 	return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index) | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint.") | ||||
| 	ErrEmptyBody   = errors.New("client: response body is empty") | ||||
| ) | ||||
|  | ||||
| // PrevExistType is used to define an existence condition when setting | ||||
| // or deleting Nodes. | ||||
| type PrevExistType string | ||||
|  | ||||
| const ( | ||||
| 	PrevIgnore  = PrevExistType("") | ||||
| 	PrevExist   = PrevExistType("true") | ||||
| 	PrevNoExist = PrevExistType("false") | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	defaultV2KeysPrefix = "/v2/keys" | ||||
| ) | ||||
|  | ||||
| // NewKeysAPI builds a KeysAPI that interacts with etcd's key-value | ||||
| // API over HTTP. | ||||
| func NewKeysAPI(c Client) KeysAPI { | ||||
| 	return NewKeysAPIWithPrefix(c, defaultV2KeysPrefix) | ||||
| } | ||||
|  | ||||
| // NewKeysAPIWithPrefix acts like NewKeysAPI, but allows the caller | ||||
| // to provide a custom base URL path. This should only be used in | ||||
| // very rare cases. | ||||
| func NewKeysAPIWithPrefix(c Client, p string) KeysAPI { | ||||
| 	return &httpKeysAPI{ | ||||
| 		client: c, | ||||
| 		prefix: p, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type KeysAPI interface { | ||||
| 	// Get retrieves a set of Nodes from etcd | ||||
| 	Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) | ||||
|  | ||||
| 	// Set assigns a new value to a Node identified by a given key. The caller | ||||
| 	// may define a set of conditions in the SetOptions. If SetOptions.Dir=true | ||||
| 	// then value is ignored. | ||||
| 	Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error) | ||||
|  | ||||
| 	// Delete removes a Node identified by the given key, optionally destroying | ||||
| 	// all of its children as well. The caller may define a set of required | ||||
| 	// conditions in an DeleteOptions object. | ||||
| 	Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) | ||||
|  | ||||
| 	// Create is an alias for Set w/ PrevExist=false | ||||
| 	Create(ctx context.Context, key, value string) (*Response, error) | ||||
|  | ||||
| 	// CreateInOrder is used to atomically create in-order keys within the given directory. | ||||
| 	CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error) | ||||
|  | ||||
| 	// Update is an alias for Set w/ PrevExist=true | ||||
| 	Update(ctx context.Context, key, value string) (*Response, error) | ||||
|  | ||||
| 	// Watcher builds a new Watcher targeted at a specific Node identified | ||||
| 	// by the given key. The Watcher may be configured at creation time | ||||
| 	// through a WatcherOptions object. The returned Watcher is designed | ||||
| 	// to emit events that happen to a Node, and optionally to its children. | ||||
| 	Watcher(key string, opts *WatcherOptions) Watcher | ||||
| } | ||||
|  | ||||
| type WatcherOptions struct { | ||||
| 	// AfterIndex defines the index after-which the Watcher should | ||||
| 	// start emitting events. For example, if a value of 5 is | ||||
| 	// provided, the first event will have an index >= 6. | ||||
| 	// | ||||
| 	// Setting AfterIndex to 0 (default) means that the Watcher | ||||
| 	// should start watching for events starting at the current | ||||
| 	// index, whatever that may be. | ||||
| 	AfterIndex uint64 | ||||
|  | ||||
| 	// Recursive specifies whether or not the Watcher should emit | ||||
| 	// events that occur in children of the given keyspace. If set | ||||
| 	// to false (default), events will be limited to those that | ||||
| 	// occur for the exact key. | ||||
| 	Recursive bool | ||||
| } | ||||
|  | ||||
| type CreateInOrderOptions struct { | ||||
| 	// TTL defines a period of time after-which the Node should | ||||
| 	// expire and no longer exist. Values <= 0 are ignored. Given | ||||
| 	// that the zero-value is ignored, TTL cannot be used to set | ||||
| 	// a TTL of 0. | ||||
| 	TTL time.Duration | ||||
| } | ||||
|  | ||||
| type SetOptions struct { | ||||
| 	// PrevValue specifies what the current value of the Node must | ||||
| 	// be in order for the Set operation to succeed. | ||||
| 	// | ||||
| 	// Leaving this field empty means that the caller wishes to | ||||
| 	// ignore the current value of the Node. This cannot be used | ||||
| 	// to compare the Node's current value to an empty string. | ||||
| 	// | ||||
| 	// PrevValue is ignored if Dir=true | ||||
| 	PrevValue string | ||||
|  | ||||
| 	// PrevIndex indicates what the current ModifiedIndex of the | ||||
| 	// Node must be in order for the Set operation to succeed. | ||||
| 	// | ||||
| 	// If PrevIndex is set to 0 (default), no comparison is made. | ||||
| 	PrevIndex uint64 | ||||
|  | ||||
| 	// PrevExist specifies whether the Node must currently exist | ||||
| 	// (PrevExist) or not (PrevNoExist). If the caller does not | ||||
| 	// care about existence, set PrevExist to PrevIgnore, or simply | ||||
| 	// leave it unset. | ||||
| 	PrevExist PrevExistType | ||||
|  | ||||
| 	// TTL defines a period of time after-which the Node should | ||||
| 	// expire and no longer exist. Values <= 0 are ignored. Given | ||||
| 	// that the zero-value is ignored, TTL cannot be used to set | ||||
| 	// a TTL of 0. | ||||
| 	TTL time.Duration | ||||
|  | ||||
| 	// Refresh set to true means a TTL value can be updated | ||||
| 	// without firing a watch or changing the node value. A | ||||
| 	// value must not be provided when refreshing a key. | ||||
| 	Refresh bool | ||||
|  | ||||
| 	// Dir specifies whether or not this Node should be created as a directory. | ||||
| 	Dir bool | ||||
|  | ||||
| 	// NoValueOnSuccess specifies whether the response contains the current value of the Node. | ||||
| 	// If set, the response will only contain the current value when the request fails. | ||||
| 	NoValueOnSuccess bool | ||||
| } | ||||
|  | ||||
| type GetOptions struct { | ||||
| 	// Recursive defines whether or not all children of the Node | ||||
| 	// should be returned. | ||||
| 	Recursive bool | ||||
|  | ||||
| 	// Sort instructs the server whether or not to sort the Nodes. | ||||
| 	// If true, the Nodes are sorted alphabetically by key in | ||||
| 	// ascending order (A to z). If false (default), the Nodes will | ||||
| 	// not be sorted and the ordering used should not be considered | ||||
| 	// predictable. | ||||
| 	Sort bool | ||||
|  | ||||
| 	// Quorum specifies whether it gets the latest committed value that | ||||
| 	// has been applied in quorum of members, which ensures external | ||||
| 	// consistency (or linearizability). | ||||
| 	Quorum bool | ||||
| } | ||||
|  | ||||
| type DeleteOptions struct { | ||||
| 	// PrevValue specifies what the current value of the Node must | ||||
| 	// be in order for the Delete operation to succeed. | ||||
| 	// | ||||
| 	// Leaving this field empty means that the caller wishes to | ||||
| 	// ignore the current value of the Node. This cannot be used | ||||
| 	// to compare the Node's current value to an empty string. | ||||
| 	PrevValue string | ||||
|  | ||||
| 	// PrevIndex indicates what the current ModifiedIndex of the | ||||
| 	// Node must be in order for the Delete operation to succeed. | ||||
| 	// | ||||
| 	// If PrevIndex is set to 0 (default), no comparison is made. | ||||
| 	PrevIndex uint64 | ||||
|  | ||||
| 	// Recursive defines whether or not all children of the Node | ||||
| 	// should be deleted. If set to true, all children of the Node | ||||
| 	// identified by the given key will be deleted. If left unset | ||||
| 	// or explicitly set to false, only a single Node will be | ||||
| 	// deleted. | ||||
| 	Recursive bool | ||||
|  | ||||
| 	// Dir specifies whether or not this Node should be removed as a directory. | ||||
| 	Dir bool | ||||
| } | ||||
|  | ||||
| type Watcher interface { | ||||
| 	// Next blocks until an etcd event occurs, then returns a Response | ||||
| 	// representing that event. The behavior of Next depends on the | ||||
| 	// WatcherOptions used to construct the Watcher. Next is designed to | ||||
| 	// be called repeatedly, each time blocking until a subsequent event | ||||
| 	// is available. | ||||
| 	// | ||||
| 	// If the provided context is cancelled, Next will return a non-nil | ||||
| 	// error. Any other failures encountered while waiting for the next | ||||
| 	// event (connection issues, deserialization failures, etc) will | ||||
| 	// also result in a non-nil error. | ||||
| 	Next(context.Context) (*Response, error) | ||||
| } | ||||
|  | ||||
| type Response struct { | ||||
| 	// Action is the name of the operation that occurred. Possible values | ||||
| 	// include get, set, delete, update, create, compareAndSwap, | ||||
| 	// compareAndDelete and expire. | ||||
| 	Action string `json:"action"` | ||||
|  | ||||
| 	// Node represents the state of the relevant etcd Node. | ||||
| 	Node *Node `json:"node"` | ||||
|  | ||||
| 	// PrevNode represents the previous state of the Node. PrevNode is non-nil | ||||
| 	// only if the Node existed before the action occurred and the action | ||||
| 	// caused a change to the Node. | ||||
| 	PrevNode *Node `json:"prevNode"` | ||||
|  | ||||
| 	// Index holds the cluster-level index at the time the Response was generated. | ||||
| 	// This index is not tied to the Node(s) contained in this Response. | ||||
| 	Index uint64 `json:"-"` | ||||
|  | ||||
| 	// ClusterID holds the cluster-level ID reported by the server.  This | ||||
| 	// should be different for different etcd clusters. | ||||
| 	ClusterID string `json:"-"` | ||||
| } | ||||
|  | ||||
| type Node struct { | ||||
| 	// Key represents the unique location of this Node (e.g. "/foo/bar"). | ||||
| 	Key string `json:"key"` | ||||
|  | ||||
| 	// Dir reports whether node describes a directory. | ||||
| 	Dir bool `json:"dir,omitempty"` | ||||
|  | ||||
| 	// Value is the current data stored on this Node. If this Node | ||||
| 	// is a directory, Value will be empty. | ||||
| 	Value string `json:"value"` | ||||
|  | ||||
| 	// Nodes holds the children of this Node, only if this Node is a directory. | ||||
| 	// This slice of will be arbitrarily deep (children, grandchildren, great- | ||||
| 	// grandchildren, etc.) if a recursive Get or Watch request were made. | ||||
| 	Nodes Nodes `json:"nodes"` | ||||
|  | ||||
| 	// CreatedIndex is the etcd index at-which this Node was created. | ||||
| 	CreatedIndex uint64 `json:"createdIndex"` | ||||
|  | ||||
| 	// ModifiedIndex is the etcd index at-which this Node was last modified. | ||||
| 	ModifiedIndex uint64 `json:"modifiedIndex"` | ||||
|  | ||||
| 	// Expiration is the server side expiration time of the key. | ||||
| 	Expiration *time.Time `json:"expiration,omitempty"` | ||||
|  | ||||
| 	// TTL is the time to live of the key in second. | ||||
| 	TTL int64 `json:"ttl,omitempty"` | ||||
| } | ||||
|  | ||||
| func (n *Node) String() string { | ||||
| 	return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d, TTL: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex, n.TTL) | ||||
| } | ||||
|  | ||||
| // TTLDuration returns the Node's TTL as a time.Duration object | ||||
| func (n *Node) TTLDuration() time.Duration { | ||||
| 	return time.Duration(n.TTL) * time.Second | ||||
| } | ||||
|  | ||||
| type Nodes []*Node | ||||
|  | ||||
| // interfaces for sorting | ||||
|  | ||||
| func (ns Nodes) Len() int           { return len(ns) } | ||||
| func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key } | ||||
| func (ns Nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] } | ||||
|  | ||||
| type httpKeysAPI struct { | ||||
| 	client httpClient | ||||
| 	prefix string | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions) (*Response, error) { | ||||
| 	act := &setAction{ | ||||
| 		Prefix: k.prefix, | ||||
| 		Key:    key, | ||||
| 		Value:  val, | ||||
| 	} | ||||
|  | ||||
| 	if opts != nil { | ||||
| 		act.PrevValue = opts.PrevValue | ||||
| 		act.PrevIndex = opts.PrevIndex | ||||
| 		act.PrevExist = opts.PrevExist | ||||
| 		act.TTL = opts.TTL | ||||
| 		act.Refresh = opts.Refresh | ||||
| 		act.Dir = opts.Dir | ||||
| 		act.NoValueOnSuccess = opts.NoValueOnSuccess | ||||
| 	} | ||||
|  | ||||
| 	doCtx := ctx | ||||
| 	if act.PrevExist == PrevNoExist { | ||||
| 		doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue) | ||||
| 	} | ||||
| 	resp, body, err := k.client.Do(doCtx, act) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body) | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, error) { | ||||
| 	return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist}) | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) { | ||||
| 	act := &createInOrderAction{ | ||||
| 		Prefix: k.prefix, | ||||
| 		Dir:    dir, | ||||
| 		Value:  val, | ||||
| 	} | ||||
|  | ||||
| 	if opts != nil { | ||||
| 		act.TTL = opts.TTL | ||||
| 	} | ||||
|  | ||||
| 	resp, body, err := k.client.Do(ctx, act) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body) | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) { | ||||
| 	return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist}) | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) { | ||||
| 	act := &deleteAction{ | ||||
| 		Prefix: k.prefix, | ||||
| 		Key:    key, | ||||
| 	} | ||||
|  | ||||
| 	if opts != nil { | ||||
| 		act.PrevValue = opts.PrevValue | ||||
| 		act.PrevIndex = opts.PrevIndex | ||||
| 		act.Dir = opts.Dir | ||||
| 		act.Recursive = opts.Recursive | ||||
| 	} | ||||
|  | ||||
| 	doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue) | ||||
| 	resp, body, err := k.client.Do(doCtx, act) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body) | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) { | ||||
| 	act := &getAction{ | ||||
| 		Prefix: k.prefix, | ||||
| 		Key:    key, | ||||
| 	} | ||||
|  | ||||
| 	if opts != nil { | ||||
| 		act.Recursive = opts.Recursive | ||||
| 		act.Sorted = opts.Sort | ||||
| 		act.Quorum = opts.Quorum | ||||
| 	} | ||||
|  | ||||
| 	resp, body, err := k.client.Do(ctx, act) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body) | ||||
| } | ||||
|  | ||||
| func (k *httpKeysAPI) Watcher(key string, opts *WatcherOptions) Watcher { | ||||
| 	act := waitAction{ | ||||
| 		Prefix: k.prefix, | ||||
| 		Key:    key, | ||||
| 	} | ||||
|  | ||||
| 	if opts != nil { | ||||
| 		act.Recursive = opts.Recursive | ||||
| 		if opts.AfterIndex > 0 { | ||||
| 			act.WaitIndex = opts.AfterIndex + 1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &httpWatcher{ | ||||
| 		client:   k.client, | ||||
| 		nextWait: act, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type httpWatcher struct { | ||||
| 	client   httpClient | ||||
| 	nextWait waitAction | ||||
| } | ||||
|  | ||||
| func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) { | ||||
| 	for { | ||||
| 		httpresp, body, err := hw.client.Do(ctx, &hw.nextWait) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body) | ||||
| 		if err != nil { | ||||
| 			if err == ErrEmptyBody { | ||||
| 				continue | ||||
| 			} | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1 | ||||
| 		return resp, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // v2KeysURL forms a URL representing the location of a key. | ||||
| // The endpoint argument represents the base URL of an etcd | ||||
| // server. The prefix is the path needed to route from the | ||||
| // provided endpoint's path to the root of the keys API | ||||
| // (typically "/v2/keys"). | ||||
| func v2KeysURL(ep url.URL, prefix, key string) *url.URL { | ||||
| 	// We concatenate all parts together manually. We cannot use | ||||
| 	// path.Join because it does not reserve trailing slash. | ||||
| 	// We call CanonicalURLPath to further cleanup the path. | ||||
| 	if prefix != "" && prefix[0] != '/' { | ||||
| 		prefix = "/" + prefix | ||||
| 	} | ||||
| 	if key != "" && key[0] != '/' { | ||||
| 		key = "/" + key | ||||
| 	} | ||||
| 	ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key) | ||||
| 	return &ep | ||||
| } | ||||
|  | ||||
| type getAction struct { | ||||
| 	Prefix    string | ||||
| 	Key       string | ||||
| 	Recursive bool | ||||
| 	Sorted    bool | ||||
| 	Quorum    bool | ||||
| } | ||||
|  | ||||
| func (g *getAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2KeysURL(ep, g.Prefix, g.Key) | ||||
|  | ||||
| 	params := u.Query() | ||||
| 	params.Set("recursive", strconv.FormatBool(g.Recursive)) | ||||
| 	params.Set("sorted", strconv.FormatBool(g.Sorted)) | ||||
| 	params.Set("quorum", strconv.FormatBool(g.Quorum)) | ||||
| 	u.RawQuery = params.Encode() | ||||
|  | ||||
| 	req, _ := http.NewRequest("GET", u.String(), nil) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type waitAction struct { | ||||
| 	Prefix    string | ||||
| 	Key       string | ||||
| 	WaitIndex uint64 | ||||
| 	Recursive bool | ||||
| } | ||||
|  | ||||
| func (w *waitAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2KeysURL(ep, w.Prefix, w.Key) | ||||
|  | ||||
| 	params := u.Query() | ||||
| 	params.Set("wait", "true") | ||||
| 	params.Set("waitIndex", strconv.FormatUint(w.WaitIndex, 10)) | ||||
| 	params.Set("recursive", strconv.FormatBool(w.Recursive)) | ||||
| 	u.RawQuery = params.Encode() | ||||
|  | ||||
| 	req, _ := http.NewRequest("GET", u.String(), nil) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type setAction struct { | ||||
| 	Prefix           string | ||||
| 	Key              string | ||||
| 	Value            string | ||||
| 	PrevValue        string | ||||
| 	PrevIndex        uint64 | ||||
| 	PrevExist        PrevExistType | ||||
| 	TTL              time.Duration | ||||
| 	Refresh          bool | ||||
| 	Dir              bool | ||||
| 	NoValueOnSuccess bool | ||||
| } | ||||
|  | ||||
| func (a *setAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2KeysURL(ep, a.Prefix, a.Key) | ||||
|  | ||||
| 	params := u.Query() | ||||
| 	form := url.Values{} | ||||
|  | ||||
| 	// we're either creating a directory or setting a key | ||||
| 	if a.Dir { | ||||
| 		params.Set("dir", strconv.FormatBool(a.Dir)) | ||||
| 	} else { | ||||
| 		// These options are only valid for setting a key | ||||
| 		if a.PrevValue != "" { | ||||
| 			params.Set("prevValue", a.PrevValue) | ||||
| 		} | ||||
| 		form.Add("value", a.Value) | ||||
| 	} | ||||
|  | ||||
| 	// Options which apply to both setting a key and creating a dir | ||||
| 	if a.PrevIndex != 0 { | ||||
| 		params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10)) | ||||
| 	} | ||||
| 	if a.PrevExist != PrevIgnore { | ||||
| 		params.Set("prevExist", string(a.PrevExist)) | ||||
| 	} | ||||
| 	if a.TTL > 0 { | ||||
| 		form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10)) | ||||
| 	} | ||||
|  | ||||
| 	if a.Refresh { | ||||
| 		form.Add("refresh", "true") | ||||
| 	} | ||||
| 	if a.NoValueOnSuccess { | ||||
| 		params.Set("noValueOnSuccess", strconv.FormatBool(a.NoValueOnSuccess)) | ||||
| 	} | ||||
|  | ||||
| 	u.RawQuery = params.Encode() | ||||
| 	body := strings.NewReader(form.Encode()) | ||||
|  | ||||
| 	req, _ := http.NewRequest("PUT", u.String(), body) | ||||
| 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||||
|  | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type deleteAction struct { | ||||
| 	Prefix    string | ||||
| 	Key       string | ||||
| 	PrevValue string | ||||
| 	PrevIndex uint64 | ||||
| 	Dir       bool | ||||
| 	Recursive bool | ||||
| } | ||||
|  | ||||
| func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2KeysURL(ep, a.Prefix, a.Key) | ||||
|  | ||||
| 	params := u.Query() | ||||
| 	if a.PrevValue != "" { | ||||
| 		params.Set("prevValue", a.PrevValue) | ||||
| 	} | ||||
| 	if a.PrevIndex != 0 { | ||||
| 		params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10)) | ||||
| 	} | ||||
| 	if a.Dir { | ||||
| 		params.Set("dir", "true") | ||||
| 	} | ||||
| 	if a.Recursive { | ||||
| 		params.Set("recursive", "true") | ||||
| 	} | ||||
| 	u.RawQuery = params.Encode() | ||||
|  | ||||
| 	req, _ := http.NewRequest("DELETE", u.String(), nil) | ||||
| 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||||
|  | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type createInOrderAction struct { | ||||
| 	Prefix string | ||||
| 	Dir    string | ||||
| 	Value  string | ||||
| 	TTL    time.Duration | ||||
| } | ||||
|  | ||||
| func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2KeysURL(ep, a.Prefix, a.Dir) | ||||
|  | ||||
| 	form := url.Values{} | ||||
| 	form.Add("value", a.Value) | ||||
| 	if a.TTL > 0 { | ||||
| 		form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10)) | ||||
| 	} | ||||
| 	body := strings.NewReader(form.Encode()) | ||||
|  | ||||
| 	req, _ := http.NewRequest("POST", u.String(), body) | ||||
| 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) { | ||||
| 	switch code { | ||||
| 	case http.StatusOK, http.StatusCreated: | ||||
| 		if len(body) == 0 { | ||||
| 			return nil, ErrEmptyBody | ||||
| 		} | ||||
| 		res, err = unmarshalSuccessfulKeysResponse(header, body) | ||||
| 	default: | ||||
| 		err = unmarshalFailedKeysResponse(body) | ||||
| 	} | ||||
| 	return res, err | ||||
| } | ||||
|  | ||||
| func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) { | ||||
| 	var res Response | ||||
| 	err := codec.NewDecoderBytes(body, new(codec.JsonHandle)).Decode(&res) | ||||
| 	if err != nil { | ||||
| 		return nil, ErrInvalidJSON | ||||
| 	} | ||||
| 	if header.Get("X-Etcd-Index") != "" { | ||||
| 		res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	res.ClusterID = header.Get("X-Etcd-Cluster-ID") | ||||
| 	return &res, nil | ||||
| } | ||||
|  | ||||
| func unmarshalFailedKeysResponse(body []byte) error { | ||||
| 	var etcdErr Error | ||||
| 	if err := json.Unmarshal(body, &etcdErr); err != nil { | ||||
| 		return ErrInvalidJSON | ||||
| 	} | ||||
| 	return etcdErr | ||||
| } | ||||
							
								
								
									
										303
									
								
								vendor/github.com/coreos/etcd/client/members.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								vendor/github.com/coreos/etcd/client/members.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/coreos/etcd/pkg/types" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	defaultV2MembersPrefix = "/v2/members" | ||||
| 	defaultLeaderSuffix    = "/leader" | ||||
| ) | ||||
|  | ||||
| type Member struct { | ||||
| 	// ID is the unique identifier of this Member. | ||||
| 	ID string `json:"id"` | ||||
|  | ||||
| 	// Name is a human-readable, non-unique identifier of this Member. | ||||
| 	Name string `json:"name"` | ||||
|  | ||||
| 	// PeerURLs represents the HTTP(S) endpoints this Member uses to | ||||
| 	// participate in etcd's consensus protocol. | ||||
| 	PeerURLs []string `json:"peerURLs"` | ||||
|  | ||||
| 	// ClientURLs represents the HTTP(S) endpoints on which this Member | ||||
| 	// serves its client-facing APIs. | ||||
| 	ClientURLs []string `json:"clientURLs"` | ||||
| } | ||||
|  | ||||
| type memberCollection []Member | ||||
|  | ||||
| func (c *memberCollection) UnmarshalJSON(data []byte) error { | ||||
| 	d := struct { | ||||
| 		Members []Member | ||||
| 	}{} | ||||
|  | ||||
| 	if err := json.Unmarshal(data, &d); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if d.Members == nil { | ||||
| 		*c = make([]Member, 0) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	*c = d.Members | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type memberCreateOrUpdateRequest struct { | ||||
| 	PeerURLs types.URLs | ||||
| } | ||||
|  | ||||
| func (m *memberCreateOrUpdateRequest) MarshalJSON() ([]byte, error) { | ||||
| 	s := struct { | ||||
| 		PeerURLs []string `json:"peerURLs"` | ||||
| 	}{ | ||||
| 		PeerURLs: make([]string, len(m.PeerURLs)), | ||||
| 	} | ||||
|  | ||||
| 	for i, u := range m.PeerURLs { | ||||
| 		s.PeerURLs[i] = u.String() | ||||
| 	} | ||||
|  | ||||
| 	return json.Marshal(&s) | ||||
| } | ||||
|  | ||||
| // NewMembersAPI constructs a new MembersAPI that uses HTTP to | ||||
| // interact with etcd's membership API. | ||||
| func NewMembersAPI(c Client) MembersAPI { | ||||
| 	return &httpMembersAPI{ | ||||
| 		client: c, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type MembersAPI interface { | ||||
| 	// List enumerates the current cluster membership. | ||||
| 	List(ctx context.Context) ([]Member, error) | ||||
|  | ||||
| 	// Add instructs etcd to accept a new Member into the cluster. | ||||
| 	Add(ctx context.Context, peerURL string) (*Member, error) | ||||
|  | ||||
| 	// Remove demotes an existing Member out of the cluster. | ||||
| 	Remove(ctx context.Context, mID string) error | ||||
|  | ||||
| 	// Update instructs etcd to update an existing Member in the cluster. | ||||
| 	Update(ctx context.Context, mID string, peerURLs []string) error | ||||
|  | ||||
| 	// Leader gets current leader of the cluster | ||||
| 	Leader(ctx context.Context) (*Member, error) | ||||
| } | ||||
|  | ||||
| type httpMembersAPI struct { | ||||
| 	client httpClient | ||||
| } | ||||
|  | ||||
| func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) { | ||||
| 	req := &membersAPIActionList{} | ||||
| 	resp, body, err := m.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var mCollection memberCollection | ||||
| 	if err := json.Unmarshal(body, &mCollection); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return []Member(mCollection), nil | ||||
| } | ||||
|  | ||||
| func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) { | ||||
| 	urls, err := types.NewURLs([]string{peerURL}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	req := &membersAPIActionAdd{peerURLs: urls} | ||||
| 	resp, body, err := m.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if resp.StatusCode != http.StatusCreated { | ||||
| 		var merr membersError | ||||
| 		if err := json.Unmarshal(body, &merr); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return nil, merr | ||||
| 	} | ||||
|  | ||||
| 	var memb Member | ||||
| 	if err := json.Unmarshal(body, &memb); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &memb, nil | ||||
| } | ||||
|  | ||||
| func (m *httpMembersAPI) Update(ctx context.Context, memberID string, peerURLs []string) error { | ||||
| 	urls, err := types.NewURLs(peerURLs) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	req := &membersAPIActionUpdate{peerURLs: urls, memberID: memberID} | ||||
| 	resp, body, err := m.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusNotFound, http.StatusConflict); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if resp.StatusCode != http.StatusNoContent { | ||||
| 		var merr membersError | ||||
| 		if err := json.Unmarshal(body, &merr); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return merr | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error { | ||||
| 	req := &membersAPIActionRemove{memberID: memberID} | ||||
| 	resp, _, err := m.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone) | ||||
| } | ||||
|  | ||||
| func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) { | ||||
| 	req := &membersAPIActionLeader{} | ||||
| 	resp, body, err := m.client.Do(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var leader Member | ||||
| 	if err := json.Unmarshal(body, &leader); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &leader, nil | ||||
| } | ||||
|  | ||||
| type membersAPIActionList struct{} | ||||
|  | ||||
| func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2MembersURL(ep) | ||||
| 	req, _ := http.NewRequest("GET", u.String(), nil) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type membersAPIActionRemove struct { | ||||
| 	memberID string | ||||
| } | ||||
|  | ||||
| func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2MembersURL(ep) | ||||
| 	u.Path = path.Join(u.Path, d.memberID) | ||||
| 	req, _ := http.NewRequest("DELETE", u.String(), nil) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type membersAPIActionAdd struct { | ||||
| 	peerURLs types.URLs | ||||
| } | ||||
|  | ||||
| func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2MembersURL(ep) | ||||
| 	m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs} | ||||
| 	b, _ := json.Marshal(&m) | ||||
| 	req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b)) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| type membersAPIActionUpdate struct { | ||||
| 	memberID string | ||||
| 	peerURLs types.URLs | ||||
| } | ||||
|  | ||||
| func (a *membersAPIActionUpdate) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2MembersURL(ep) | ||||
| 	m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs} | ||||
| 	u.Path = path.Join(u.Path, a.memberID) | ||||
| 	b, _ := json.Marshal(&m) | ||||
| 	req, _ := http.NewRequest("PUT", u.String(), bytes.NewReader(b)) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| func assertStatusCode(got int, want ...int) (err error) { | ||||
| 	for _, w := range want { | ||||
| 		if w == got { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return fmt.Errorf("unexpected status code %d", got) | ||||
| } | ||||
|  | ||||
| type membersAPIActionLeader struct{} | ||||
|  | ||||
| func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request { | ||||
| 	u := v2MembersURL(ep) | ||||
| 	u.Path = path.Join(u.Path, defaultLeaderSuffix) | ||||
| 	req, _ := http.NewRequest("GET", u.String(), nil) | ||||
| 	return req | ||||
| } | ||||
|  | ||||
| // v2MembersURL add the necessary path to the provided endpoint | ||||
| // to route requests to the default v2 members API. | ||||
| func v2MembersURL(ep url.URL) *url.URL { | ||||
| 	ep.Path = path.Join(ep.Path, defaultV2MembersPrefix) | ||||
| 	return &ep | ||||
| } | ||||
|  | ||||
| type membersError struct { | ||||
| 	Message string `json:"message"` | ||||
| 	Code    int    `json:"-"` | ||||
| } | ||||
|  | ||||
| func (e membersError) Error() string { | ||||
| 	return e.Message | ||||
| } | ||||
							
								
								
									
										53
									
								
								vendor/github.com/coreos/etcd/client/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/coreos/etcd/client/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // Copyright 2016 The etcd Authors | ||||
| // | ||||
| // 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 client | ||||
|  | ||||
| import ( | ||||
| 	"regexp" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	roleNotFoundRegExp *regexp.Regexp | ||||
| 	userNotFoundRegExp *regexp.Regexp | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	roleNotFoundRegExp = regexp.MustCompile("auth: Role .* does not exist.") | ||||
| 	userNotFoundRegExp = regexp.MustCompile("auth: User .* does not exist.") | ||||
| } | ||||
|  | ||||
| // IsKeyNotFound returns true if the error code is ErrorCodeKeyNotFound. | ||||
| func IsKeyNotFound(err error) bool { | ||||
| 	if cErr, ok := err.(Error); ok { | ||||
| 		return cErr.Code == ErrorCodeKeyNotFound | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // IsRoleNotFound returns true if the error means role not found of v2 API. | ||||
| func IsRoleNotFound(err error) bool { | ||||
| 	if ae, ok := err.(authError); ok { | ||||
| 		return roleNotFoundRegExp.MatchString(ae.Message) | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // IsUserNotFound returns true if the error means user not found of v2 API. | ||||
| func IsUserNotFound(err error) bool { | ||||
| 	if ae, ok := err.(authError); ok { | ||||
| 		return userNotFoundRegExp.MatchString(ae.Message) | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										31
									
								
								vendor/github.com/coreos/etcd/pkg/pathutil/path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/coreos/etcd/pkg/pathutil/path.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // Copyright 2009 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package pathutil implements utility functions for handling slash-separated | ||||
| // paths. | ||||
| package pathutil | ||||
|  | ||||
| import "path" | ||||
|  | ||||
| // CanonicalURLPath returns the canonical url path for p, which follows the rules: | ||||
| // 1. the path always starts with "/" | ||||
| // 2. replace multiple slashes with a single slash | ||||
| // 3. replace each '.' '..' path name element with equivalent one | ||||
| // 4. keep the trailing slash | ||||
| // The function is borrowed from stdlib http.cleanPath in server.go. | ||||
| func CanonicalURLPath(p string) string { | ||||
| 	if p == "" { | ||||
| 		return "/" | ||||
| 	} | ||||
| 	if p[0] != '/' { | ||||
| 		p = "/" + p | ||||
| 	} | ||||
| 	np := path.Clean(p) | ||||
| 	// path.Clean removes trailing slash except for root, | ||||
| 	// put the trailing slash back if necessary. | ||||
| 	if p[len(p)-1] == '/' && np != "/" { | ||||
| 		np += "/" | ||||
| 	} | ||||
| 	return np | ||||
| } | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/srv/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/srv/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										130
									
								
								vendor/github.com/coreos/etcd/pkg/srv/srv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								vendor/github.com/coreos/etcd/pkg/srv/srv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 srv looks up DNS SRV records. | ||||
| package srv | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/coreos/etcd/pkg/types" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// indirection for testing | ||||
| 	lookupSRV      = net.LookupSRV // net.DefaultResolver.LookupSRV when ctxs don't conflict | ||||
| 	resolveTCPAddr = net.ResolveTCPAddr | ||||
| ) | ||||
|  | ||||
| // GetCluster gets the cluster information via DNS discovery. | ||||
| // Also sees each entry as a separate instance. | ||||
| func GetCluster(serviceScheme, service, name, dns string, apurls types.URLs) ([]string, error) { | ||||
| 	tempName := int(0) | ||||
| 	tcp2ap := make(map[string]url.URL) | ||||
|  | ||||
| 	// First, resolve the apurls | ||||
| 	for _, url := range apurls { | ||||
| 		tcpAddr, err := resolveTCPAddr("tcp", url.Host) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		tcp2ap[tcpAddr.String()] = url | ||||
| 	} | ||||
|  | ||||
| 	stringParts := []string{} | ||||
| 	updateNodeMap := func(service, scheme string) error { | ||||
| 		_, addrs, err := lookupSRV(service, "tcp", dns) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, srv := range addrs { | ||||
| 			port := fmt.Sprintf("%d", srv.Port) | ||||
| 			host := net.JoinHostPort(srv.Target, port) | ||||
| 			tcpAddr, terr := resolveTCPAddr("tcp", host) | ||||
| 			if terr != nil { | ||||
| 				err = terr | ||||
| 				continue | ||||
| 			} | ||||
| 			n := "" | ||||
| 			url, ok := tcp2ap[tcpAddr.String()] | ||||
| 			if ok { | ||||
| 				n = name | ||||
| 			} | ||||
| 			if n == "" { | ||||
| 				n = fmt.Sprintf("%d", tempName) | ||||
| 				tempName++ | ||||
| 			} | ||||
| 			// SRV records have a trailing dot but URL shouldn't. | ||||
| 			shortHost := strings.TrimSuffix(srv.Target, ".") | ||||
| 			urlHost := net.JoinHostPort(shortHost, port) | ||||
| 			if ok && url.Scheme != scheme { | ||||
| 				err = fmt.Errorf("bootstrap at %s from DNS for %s has scheme mismatch with expected peer %s", scheme+"://"+urlHost, service, url.String()) | ||||
| 			} else { | ||||
| 				stringParts = append(stringParts, fmt.Sprintf("%s=%s://%s", n, scheme, urlHost)) | ||||
| 			} | ||||
| 		} | ||||
| 		if len(stringParts) == 0 { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	err := updateNodeMap(service, serviceScheme) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error querying DNS SRV records for _%s %s", service, err) | ||||
| 	} | ||||
| 	return stringParts, nil | ||||
| } | ||||
|  | ||||
| type SRVClients struct { | ||||
| 	Endpoints []string | ||||
| 	SRVs      []*net.SRV | ||||
| } | ||||
|  | ||||
| // GetClient looks up the client endpoints for a service and domain. | ||||
| func GetClient(service, domain string) (*SRVClients, error) { | ||||
| 	var urls []*url.URL | ||||
| 	var srvs []*net.SRV | ||||
|  | ||||
| 	updateURLs := func(service, scheme string) error { | ||||
| 		_, addrs, err := lookupSRV(service, "tcp", domain) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, srv := range addrs { | ||||
| 			urls = append(urls, &url.URL{ | ||||
| 				Scheme: scheme, | ||||
| 				Host:   net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)), | ||||
| 			}) | ||||
| 		} | ||||
| 		srvs = append(srvs, addrs...) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	errHTTPS := updateURLs(service+"-ssl", "https") | ||||
| 	errHTTP := updateURLs(service, "http") | ||||
|  | ||||
| 	if errHTTPS != nil && errHTTP != nil { | ||||
| 		return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP) | ||||
| 	} | ||||
|  | ||||
| 	endpoints := make([]string, len(urls)) | ||||
| 	for i := range urls { | ||||
| 		endpoints[i] = urls[i].String() | ||||
| 	} | ||||
| 	return &SRVClients{Endpoints: endpoints, SRVs: srvs}, nil | ||||
| } | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/types/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/pkg/types/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										17
									
								
								vendor/github.com/coreos/etcd/pkg/types/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/coreos/etcd/pkg/types/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 types declares various data types and implements type-checking | ||||
| // functions. | ||||
| package types | ||||
							
								
								
									
										41
									
								
								vendor/github.com/coreos/etcd/pkg/types/id.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/coreos/etcd/pkg/types/id.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 types | ||||
|  | ||||
| import ( | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // ID represents a generic identifier which is canonically | ||||
| // stored as a uint64 but is typically represented as a | ||||
| // base-16 string for input/output | ||||
| type ID uint64 | ||||
|  | ||||
| func (i ID) String() string { | ||||
| 	return strconv.FormatUint(uint64(i), 16) | ||||
| } | ||||
|  | ||||
| // IDFromString attempts to create an ID from a base-16 string. | ||||
| func IDFromString(s string) (ID, error) { | ||||
| 	i, err := strconv.ParseUint(s, 16, 64) | ||||
| 	return ID(i), err | ||||
| } | ||||
|  | ||||
| // IDSlice implements the sort interface | ||||
| type IDSlice []ID | ||||
|  | ||||
| func (p IDSlice) Len() int           { return len(p) } | ||||
| func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) } | ||||
| func (p IDSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] } | ||||
							
								
								
									
										178
									
								
								vendor/github.com/coreos/etcd/pkg/types/set.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								vendor/github.com/coreos/etcd/pkg/types/set.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 types | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| type Set interface { | ||||
| 	Add(string) | ||||
| 	Remove(string) | ||||
| 	Contains(string) bool | ||||
| 	Equals(Set) bool | ||||
| 	Length() int | ||||
| 	Values() []string | ||||
| 	Copy() Set | ||||
| 	Sub(Set) Set | ||||
| } | ||||
|  | ||||
| func NewUnsafeSet(values ...string) *unsafeSet { | ||||
| 	set := &unsafeSet{make(map[string]struct{})} | ||||
| 	for _, v := range values { | ||||
| 		set.Add(v) | ||||
| 	} | ||||
| 	return set | ||||
| } | ||||
|  | ||||
| func NewThreadsafeSet(values ...string) *tsafeSet { | ||||
| 	us := NewUnsafeSet(values...) | ||||
| 	return &tsafeSet{us, sync.RWMutex{}} | ||||
| } | ||||
|  | ||||
| type unsafeSet struct { | ||||
| 	d map[string]struct{} | ||||
| } | ||||
|  | ||||
| // Add adds a new value to the set (no-op if the value is already present) | ||||
| func (us *unsafeSet) Add(value string) { | ||||
| 	us.d[value] = struct{}{} | ||||
| } | ||||
|  | ||||
| // Remove removes the given value from the set | ||||
| func (us *unsafeSet) Remove(value string) { | ||||
| 	delete(us.d, value) | ||||
| } | ||||
|  | ||||
| // Contains returns whether the set contains the given value | ||||
| func (us *unsafeSet) Contains(value string) (exists bool) { | ||||
| 	_, exists = us.d[value] | ||||
| 	return exists | ||||
| } | ||||
|  | ||||
| // ContainsAll returns whether the set contains all given values | ||||
| func (us *unsafeSet) ContainsAll(values []string) bool { | ||||
| 	for _, s := range values { | ||||
| 		if !us.Contains(s) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Equals returns whether the contents of two sets are identical | ||||
| func (us *unsafeSet) Equals(other Set) bool { | ||||
| 	v1 := sort.StringSlice(us.Values()) | ||||
| 	v2 := sort.StringSlice(other.Values()) | ||||
| 	v1.Sort() | ||||
| 	v2.Sort() | ||||
| 	return reflect.DeepEqual(v1, v2) | ||||
| } | ||||
|  | ||||
| // Length returns the number of elements in the set | ||||
| func (us *unsafeSet) Length() int { | ||||
| 	return len(us.d) | ||||
| } | ||||
|  | ||||
| // Values returns the values of the Set in an unspecified order. | ||||
| func (us *unsafeSet) Values() (values []string) { | ||||
| 	values = make([]string, 0) | ||||
| 	for val := range us.d { | ||||
| 		values = append(values, val) | ||||
| 	} | ||||
| 	return values | ||||
| } | ||||
|  | ||||
| // Copy creates a new Set containing the values of the first | ||||
| func (us *unsafeSet) Copy() Set { | ||||
| 	cp := NewUnsafeSet() | ||||
| 	for val := range us.d { | ||||
| 		cp.Add(val) | ||||
| 	} | ||||
|  | ||||
| 	return cp | ||||
| } | ||||
|  | ||||
| // Sub removes all elements in other from the set | ||||
| func (us *unsafeSet) Sub(other Set) Set { | ||||
| 	oValues := other.Values() | ||||
| 	result := us.Copy().(*unsafeSet) | ||||
|  | ||||
| 	for _, val := range oValues { | ||||
| 		if _, ok := result.d[val]; !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		delete(result.d, val) | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| type tsafeSet struct { | ||||
| 	us *unsafeSet | ||||
| 	m  sync.RWMutex | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Add(value string) { | ||||
| 	ts.m.Lock() | ||||
| 	defer ts.m.Unlock() | ||||
| 	ts.us.Add(value) | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Remove(value string) { | ||||
| 	ts.m.Lock() | ||||
| 	defer ts.m.Unlock() | ||||
| 	ts.us.Remove(value) | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Contains(value string) (exists bool) { | ||||
| 	ts.m.RLock() | ||||
| 	defer ts.m.RUnlock() | ||||
| 	return ts.us.Contains(value) | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Equals(other Set) bool { | ||||
| 	ts.m.RLock() | ||||
| 	defer ts.m.RUnlock() | ||||
| 	return ts.us.Equals(other) | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Length() int { | ||||
| 	ts.m.RLock() | ||||
| 	defer ts.m.RUnlock() | ||||
| 	return ts.us.Length() | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Values() (values []string) { | ||||
| 	ts.m.RLock() | ||||
| 	defer ts.m.RUnlock() | ||||
| 	return ts.us.Values() | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Copy() Set { | ||||
| 	ts.m.RLock() | ||||
| 	defer ts.m.RUnlock() | ||||
| 	usResult := ts.us.Copy().(*unsafeSet) | ||||
| 	return &tsafeSet{usResult, sync.RWMutex{}} | ||||
| } | ||||
|  | ||||
| func (ts *tsafeSet) Sub(other Set) Set { | ||||
| 	ts.m.RLock() | ||||
| 	defer ts.m.RUnlock() | ||||
| 	usResult := ts.us.Sub(other).(*unsafeSet) | ||||
| 	return &tsafeSet{usResult, sync.RWMutex{}} | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/coreos/etcd/pkg/types/slice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/coreos/etcd/pkg/types/slice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 types | ||||
|  | ||||
| // Uint64Slice implements sort interface | ||||
| type Uint64Slice []uint64 | ||||
|  | ||||
| func (p Uint64Slice) Len() int           { return len(p) } | ||||
| func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] } | ||||
| func (p Uint64Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] } | ||||
							
								
								
									
										82
									
								
								vendor/github.com/coreos/etcd/pkg/types/urls.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/coreos/etcd/pkg/types/urls.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 types | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type URLs []url.URL | ||||
|  | ||||
| func NewURLs(strs []string) (URLs, error) { | ||||
| 	all := make([]url.URL, len(strs)) | ||||
| 	if len(all) == 0 { | ||||
| 		return nil, errors.New("no valid URLs given") | ||||
| 	} | ||||
| 	for i, in := range strs { | ||||
| 		in = strings.TrimSpace(in) | ||||
| 		u, err := url.Parse(in) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" && u.Scheme != "unixs" { | ||||
| 			return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in) | ||||
| 		} | ||||
| 		if _, _, err := net.SplitHostPort(u.Host); err != nil { | ||||
| 			return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in) | ||||
| 		} | ||||
| 		if u.Path != "" { | ||||
| 			return nil, fmt.Errorf("URL must not contain a path: %s", in) | ||||
| 		} | ||||
| 		all[i] = *u | ||||
| 	} | ||||
| 	us := URLs(all) | ||||
| 	us.Sort() | ||||
|  | ||||
| 	return us, nil | ||||
| } | ||||
|  | ||||
| func MustNewURLs(strs []string) URLs { | ||||
| 	urls, err := NewURLs(strs) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return urls | ||||
| } | ||||
|  | ||||
| func (us URLs) String() string { | ||||
| 	return strings.Join(us.StringSlice(), ",") | ||||
| } | ||||
|  | ||||
| func (us *URLs) Sort() { | ||||
| 	sort.Sort(us) | ||||
| } | ||||
| func (us URLs) Len() int           { return len(us) } | ||||
| func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() } | ||||
| func (us URLs) Swap(i, j int)      { us[i], us[j] = us[j], us[i] } | ||||
|  | ||||
| func (us URLs) StringSlice() []string { | ||||
| 	out := make([]string, len(us)) | ||||
| 	for i := range us { | ||||
| 		out[i] = us[i].String() | ||||
| 	} | ||||
|  | ||||
| 	return out | ||||
| } | ||||
							
								
								
									
										107
									
								
								vendor/github.com/coreos/etcd/pkg/types/urlsmap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								vendor/github.com/coreos/etcd/pkg/types/urlsmap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // Copyright 2015 The etcd Authors | ||||
| // | ||||
| // 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 types | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // URLsMap is a map from a name to its URLs. | ||||
| type URLsMap map[string]URLs | ||||
|  | ||||
| // NewURLsMap returns a URLsMap instantiated from the given string, | ||||
| // which consists of discovery-formatted names-to-URLs, like: | ||||
| // mach0=http://1.1.1.1:2380,mach0=http://2.2.2.2::2380,mach1=http://3.3.3.3:2380,mach2=http://4.4.4.4:2380 | ||||
| func NewURLsMap(s string) (URLsMap, error) { | ||||
| 	m := parse(s) | ||||
|  | ||||
| 	cl := URLsMap{} | ||||
| 	for name, urls := range m { | ||||
| 		us, err := NewURLs(urls) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		cl[name] = us | ||||
| 	} | ||||
| 	return cl, nil | ||||
| } | ||||
|  | ||||
| // NewURLsMapFromStringMap takes a map of strings and returns a URLsMap. The | ||||
| // string values in the map can be multiple values separated by the sep string. | ||||
| func NewURLsMapFromStringMap(m map[string]string, sep string) (URLsMap, error) { | ||||
| 	var err error | ||||
| 	um := URLsMap{} | ||||
| 	for k, v := range m { | ||||
| 		um[k], err = NewURLs(strings.Split(v, sep)) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return um, nil | ||||
| } | ||||
|  | ||||
| // String turns URLsMap into discovery-formatted name-to-URLs sorted by name. | ||||
| func (c URLsMap) String() string { | ||||
| 	var pairs []string | ||||
| 	for name, urls := range c { | ||||
| 		for _, url := range urls { | ||||
| 			pairs = append(pairs, fmt.Sprintf("%s=%s", name, url.String())) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Strings(pairs) | ||||
| 	return strings.Join(pairs, ",") | ||||
| } | ||||
|  | ||||
| // URLs returns a list of all URLs. | ||||
| // The returned list is sorted in ascending lexicographical order. | ||||
| func (c URLsMap) URLs() []string { | ||||
| 	var urls []string | ||||
| 	for _, us := range c { | ||||
| 		for _, u := range us { | ||||
| 			urls = append(urls, u.String()) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Strings(urls) | ||||
| 	return urls | ||||
| } | ||||
|  | ||||
| // Len returns the size of URLsMap. | ||||
| func (c URLsMap) Len() int { | ||||
| 	return len(c) | ||||
| } | ||||
|  | ||||
| // parse parses the given string and returns a map listing the values specified for each key. | ||||
| func parse(s string) map[string][]string { | ||||
| 	m := make(map[string][]string) | ||||
| 	for s != "" { | ||||
| 		key := s | ||||
| 		if i := strings.IndexAny(key, ","); i >= 0 { | ||||
| 			key, s = key[:i], key[i+1:] | ||||
| 		} else { | ||||
| 			s = "" | ||||
| 		} | ||||
| 		if key == "" { | ||||
| 			continue | ||||
| 		} | ||||
| 		value := "" | ||||
| 		if i := strings.Index(key, "="); i >= 0 { | ||||
| 			key, value = key[:i], key[i+1:] | ||||
| 		} | ||||
| 		m[key] = append(m[key], value) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										268
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/semver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/semver.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| // Copyright 2013-2015 CoreOS, 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. | ||||
|  | ||||
| // Semantic Versions http://semver.org | ||||
| package semver | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Version struct { | ||||
| 	Major      int64 | ||||
| 	Minor      int64 | ||||
| 	Patch      int64 | ||||
| 	PreRelease PreRelease | ||||
| 	Metadata   string | ||||
| } | ||||
|  | ||||
| type PreRelease string | ||||
|  | ||||
| func splitOff(input *string, delim string) (val string) { | ||||
| 	parts := strings.SplitN(*input, delim, 2) | ||||
|  | ||||
| 	if len(parts) == 2 { | ||||
| 		*input = parts[0] | ||||
| 		val = parts[1] | ||||
| 	} | ||||
|  | ||||
| 	return val | ||||
| } | ||||
|  | ||||
| func New(version string) *Version { | ||||
| 	return Must(NewVersion(version)) | ||||
| } | ||||
|  | ||||
| func NewVersion(version string) (*Version, error) { | ||||
| 	v := Version{} | ||||
|  | ||||
| 	if err := v.Set(version); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &v, nil | ||||
| } | ||||
|  | ||||
| // Must is a helper for wrapping NewVersion and will panic if err is not nil. | ||||
| func Must(v *Version, err error) *Version { | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // Set parses and updates v from the given version string. Implements flag.Value | ||||
| func (v *Version) Set(version string) error { | ||||
| 	metadata := splitOff(&version, "+") | ||||
| 	preRelease := PreRelease(splitOff(&version, "-")) | ||||
| 	dotParts := strings.SplitN(version, ".", 3) | ||||
|  | ||||
| 	if len(dotParts) != 3 { | ||||
| 		return fmt.Errorf("%s is not in dotted-tri format", version) | ||||
| 	} | ||||
|  | ||||
| 	parsed := make([]int64, 3, 3) | ||||
|  | ||||
| 	for i, v := range dotParts[:3] { | ||||
| 		val, err := strconv.ParseInt(v, 10, 64) | ||||
| 		parsed[i] = val | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	v.Metadata = metadata | ||||
| 	v.PreRelease = preRelease | ||||
| 	v.Major = parsed[0] | ||||
| 	v.Minor = parsed[1] | ||||
| 	v.Patch = parsed[2] | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v Version) String() string { | ||||
| 	var buffer bytes.Buffer | ||||
|  | ||||
| 	fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch) | ||||
|  | ||||
| 	if v.PreRelease != "" { | ||||
| 		fmt.Fprintf(&buffer, "-%s", v.PreRelease) | ||||
| 	} | ||||
|  | ||||
| 	if v.Metadata != "" { | ||||
| 		fmt.Fprintf(&buffer, "+%s", v.Metadata) | ||||
| 	} | ||||
|  | ||||
| 	return buffer.String() | ||||
| } | ||||
|  | ||||
| func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||
| 	var data string | ||||
| 	if err := unmarshal(&data); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return v.Set(data) | ||||
| } | ||||
|  | ||||
| func (v Version) MarshalJSON() ([]byte, error) { | ||||
| 	return []byte(`"` + v.String() + `"`), nil | ||||
| } | ||||
|  | ||||
| func (v *Version) UnmarshalJSON(data []byte) error { | ||||
| 	l := len(data) | ||||
| 	if l == 0 || string(data) == `""` { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if l < 2 || data[0] != '"' || data[l-1] != '"' { | ||||
| 		return errors.New("invalid semver string") | ||||
| 	} | ||||
| 	return v.Set(string(data[1 : l-1])) | ||||
| } | ||||
|  | ||||
| // Compare tests if v is less than, equal to, or greater than versionB, | ||||
| // returning -1, 0, or +1 respectively. | ||||
| func (v Version) Compare(versionB Version) int { | ||||
| 	if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 { | ||||
| 		return cmp | ||||
| 	} | ||||
| 	return preReleaseCompare(v, versionB) | ||||
| } | ||||
|  | ||||
| // Equal tests if v is equal to versionB. | ||||
| func (v Version) Equal(versionB Version) bool { | ||||
| 	return v.Compare(versionB) == 0 | ||||
| } | ||||
|  | ||||
| // LessThan tests if v is less than versionB. | ||||
| func (v Version) LessThan(versionB Version) bool { | ||||
| 	return v.Compare(versionB) < 0 | ||||
| } | ||||
|  | ||||
| // Slice converts the comparable parts of the semver into a slice of integers. | ||||
| func (v Version) Slice() []int64 { | ||||
| 	return []int64{v.Major, v.Minor, v.Patch} | ||||
| } | ||||
|  | ||||
| func (p PreRelease) Slice() []string { | ||||
| 	preRelease := string(p) | ||||
| 	return strings.Split(preRelease, ".") | ||||
| } | ||||
|  | ||||
| func preReleaseCompare(versionA Version, versionB Version) int { | ||||
| 	a := versionA.PreRelease | ||||
| 	b := versionB.PreRelease | ||||
|  | ||||
| 	/* Handle the case where if two versions are otherwise equal it is the | ||||
| 	 * one without a PreRelease that is greater */ | ||||
| 	if len(a) == 0 && (len(b) > 0) { | ||||
| 		return 1 | ||||
| 	} else if len(b) == 0 && (len(a) > 0) { | ||||
| 		return -1 | ||||
| 	} | ||||
|  | ||||
| 	// If there is a prerelease, check and compare each part. | ||||
| 	return recursivePreReleaseCompare(a.Slice(), b.Slice()) | ||||
| } | ||||
|  | ||||
| func recursiveCompare(versionA []int64, versionB []int64) int { | ||||
| 	if len(versionA) == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	a := versionA[0] | ||||
| 	b := versionB[0] | ||||
|  | ||||
| 	if a > b { | ||||
| 		return 1 | ||||
| 	} else if a < b { | ||||
| 		return -1 | ||||
| 	} | ||||
|  | ||||
| 	return recursiveCompare(versionA[1:], versionB[1:]) | ||||
| } | ||||
|  | ||||
| func recursivePreReleaseCompare(versionA []string, versionB []string) int { | ||||
| 	// A larger set of pre-release fields has a higher precedence than a smaller set, | ||||
| 	// if all of the preceding identifiers are equal. | ||||
| 	if len(versionA) == 0 { | ||||
| 		if len(versionB) > 0 { | ||||
| 			return -1 | ||||
| 		} | ||||
| 		return 0 | ||||
| 	} else if len(versionB) == 0 { | ||||
| 		// We're longer than versionB so return 1. | ||||
| 		return 1 | ||||
| 	} | ||||
|  | ||||
| 	a := versionA[0] | ||||
| 	b := versionB[0] | ||||
|  | ||||
| 	aInt := false | ||||
| 	bInt := false | ||||
|  | ||||
| 	aI, err := strconv.Atoi(versionA[0]) | ||||
| 	if err == nil { | ||||
| 		aInt = true | ||||
| 	} | ||||
|  | ||||
| 	bI, err := strconv.Atoi(versionB[0]) | ||||
| 	if err == nil { | ||||
| 		bInt = true | ||||
| 	} | ||||
|  | ||||
| 	// Handle Integer Comparison | ||||
| 	if aInt && bInt { | ||||
| 		if aI > bI { | ||||
| 			return 1 | ||||
| 		} else if aI < bI { | ||||
| 			return -1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Handle String Comparison | ||||
| 	if a > b { | ||||
| 		return 1 | ||||
| 	} else if a < b { | ||||
| 		return -1 | ||||
| 	} | ||||
|  | ||||
| 	return recursivePreReleaseCompare(versionA[1:], versionB[1:]) | ||||
| } | ||||
|  | ||||
| // BumpMajor increments the Major field by 1 and resets all other fields to their default values | ||||
| func (v *Version) BumpMajor() { | ||||
| 	v.Major += 1 | ||||
| 	v.Minor = 0 | ||||
| 	v.Patch = 0 | ||||
| 	v.PreRelease = PreRelease("") | ||||
| 	v.Metadata = "" | ||||
| } | ||||
|  | ||||
| // BumpMinor increments the Minor field by 1 and resets all other fields to their default values | ||||
| func (v *Version) BumpMinor() { | ||||
| 	v.Minor += 1 | ||||
| 	v.Patch = 0 | ||||
| 	v.PreRelease = PreRelease("") | ||||
| 	v.Metadata = "" | ||||
| } | ||||
|  | ||||
| // BumpPatch increments the Patch field by 1 and resets all other fields to their default values | ||||
| func (v *Version) BumpPatch() { | ||||
| 	v.Patch += 1 | ||||
| 	v.PreRelease = PreRelease("") | ||||
| 	v.Metadata = "" | ||||
| } | ||||
							
								
								
									
										38
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/sort.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/sort.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // Copyright 2013-2015 CoreOS, 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 semver | ||||
|  | ||||
| import ( | ||||
| 	"sort" | ||||
| ) | ||||
|  | ||||
| type Versions []*Version | ||||
|  | ||||
| func (s Versions) Len() int { | ||||
| 	return len(s) | ||||
| } | ||||
|  | ||||
| func (s Versions) Swap(i, j int) { | ||||
| 	s[i], s[j] = s[j], s[i] | ||||
| } | ||||
|  | ||||
| func (s Versions) Less(i, j int) bool { | ||||
| 	return s[i].LessThan(*s[j]) | ||||
| } | ||||
|  | ||||
| // Sort sorts the given slice of Version | ||||
| func Sort(versions []*Version) { | ||||
| 	sort.Sort(Versions(versions)) | ||||
| } | ||||
							
								
								
									
										185
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/0doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/0doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| /* | ||||
| High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library for | ||||
| binc, msgpack, cbor, json | ||||
|  | ||||
| Supported Serialization formats are: | ||||
|  | ||||
|   - msgpack: https://github.com/msgpack/msgpack | ||||
|   - binc:    http://github.com/ugorji/binc | ||||
|   - cbor:    http://cbor.io http://tools.ietf.org/html/rfc7049 | ||||
|   - json:    http://json.org http://tools.ietf.org/html/rfc7159 | ||||
|   - simple: | ||||
|  | ||||
| To install: | ||||
|  | ||||
|     go get github.com/ugorji/go/codec | ||||
|  | ||||
| This package will carefully use 'unsafe' for performance reasons in specific places. | ||||
| You can build without unsafe use by passing the safe or appengine tag | ||||
| i.e. 'go install -tags=safe ...'. Note that unsafe is only supported for the last 3 | ||||
| go sdk versions e.g. current go release is go 1.9, so we support unsafe use only from | ||||
| go 1.7+ . This is because supporting unsafe requires knowledge of implementation details. | ||||
|  | ||||
| For detailed usage information, read the primer at http://ugorji.net/blog/go-codec-primer . | ||||
|  | ||||
| The idiomatic Go support is as seen in other encoding packages in | ||||
| the standard library (ie json, xml, gob, etc). | ||||
|  | ||||
| Rich Feature Set includes: | ||||
|  | ||||
|   - Simple but extremely powerful and feature-rich API | ||||
|   - Support for go1.4 and above, while selectively using newer APIs for later releases | ||||
|   - Good code coverage ( > 70% ) | ||||
|   - Very High Performance. | ||||
|     Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X. | ||||
|   - Careful selected use of 'unsafe' for targeted performance gains. | ||||
|     100% mode exists where 'unsafe' is not used at all. | ||||
|   - Lock-free (sans mutex) concurrency for scaling to 100's of cores | ||||
|   - Multiple conversions: | ||||
|     Package coerces types where appropriate | ||||
|     e.g. decode an int in the stream into a float, etc. | ||||
|   - Corner Cases: | ||||
|     Overflows, nil maps/slices, nil values in streams are handled correctly | ||||
|   - Standard field renaming via tags | ||||
|   - Support for omitting empty fields during an encoding | ||||
|   - Encoding from any value and decoding into pointer to any value | ||||
|     (struct, slice, map, primitives, pointers, interface{}, etc) | ||||
|   - Extensions to support efficient encoding/decoding of any named types | ||||
|   - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces | ||||
|   - Decoding without a schema (into a interface{}). | ||||
|     Includes Options to configure what specific map or slice type to use | ||||
|     when decoding an encoded list or map into a nil interface{} | ||||
|   - Encode a struct as an array, and decode struct from an array in the data stream | ||||
|   - Comprehensive support for anonymous fields | ||||
|   - Fast (no-reflection) encoding/decoding of common maps and slices | ||||
|   - Code-generation for faster performance. | ||||
|   - Support binary (e.g. messagepack, cbor) and text (e.g. json) formats | ||||
|   - Support indefinite-length formats to enable true streaming | ||||
|     (for formats which support it e.g. json, cbor) | ||||
|   - Support canonical encoding, where a value is ALWAYS encoded as same sequence of bytes. | ||||
|     This mostly applies to maps, where iteration order is non-deterministic. | ||||
|   - NIL in data stream decoded as zero value | ||||
|   - Never silently skip data when decoding. | ||||
|     User decides whether to return an error or silently skip data when keys or indexes | ||||
|     in the data stream do not map to fields in the struct. | ||||
|   - Detect and error when encoding a cyclic reference (instead of stack overflow shutdown) | ||||
|   - Encode/Decode from/to chan types (for iterative streaming support) | ||||
|   - Drop-in replacement for encoding/json. `json:` key in struct tag supported. | ||||
|   - Provides a RPC Server and Client Codec for net/rpc communication protocol. | ||||
|   - Handle unique idiosyncrasies of codecs e.g. | ||||
|     - For messagepack, configure how ambiguities in handling raw bytes are resolved | ||||
|     - For messagepack, provide rpc server/client codec to support | ||||
|       msgpack-rpc protocol defined at: | ||||
|       https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md | ||||
|  | ||||
| Extension Support | ||||
|  | ||||
| Users can register a function to handle the encoding or decoding of | ||||
| their custom types. | ||||
|  | ||||
| There are no restrictions on what the custom type can be. Some examples: | ||||
|  | ||||
|     type BisSet   []int | ||||
|     type BitSet64 uint64 | ||||
|     type UUID     string | ||||
|     type MyStructWithUnexportedFields struct { a int; b bool; c []int; } | ||||
|     type GifImage struct { ... } | ||||
|  | ||||
| As an illustration, MyStructWithUnexportedFields would normally be | ||||
| encoded as an empty map because it has no exported fields, while UUID | ||||
| would be encoded as a string. However, with extension support, you can | ||||
| encode any of these however you like. | ||||
|  | ||||
| RPC | ||||
|  | ||||
| RPC Client and Server Codecs are implemented, so the codecs can be used | ||||
| with the standard net/rpc package. | ||||
|  | ||||
| Usage | ||||
|  | ||||
| The Handle is SAFE for concurrent READ, but NOT SAFE for concurrent modification. | ||||
|  | ||||
| The Encoder and Decoder are NOT safe for concurrent use. | ||||
|  | ||||
| Consequently, the usage model is basically: | ||||
|  | ||||
|     - Create and initialize the Handle before any use. | ||||
|       Once created, DO NOT modify it. | ||||
|     - Multiple Encoders or Decoders can now use the Handle concurrently. | ||||
|       They only read information off the Handle (never write). | ||||
|     - However, each Encoder or Decoder MUST not be used concurrently | ||||
|     - To re-use an Encoder/Decoder, call Reset(...) on it first. | ||||
|       This allows you use state maintained on the Encoder/Decoder. | ||||
|  | ||||
| Sample usage model: | ||||
|  | ||||
|     // create and configure Handle | ||||
|     var ( | ||||
|       bh codec.BincHandle | ||||
|       mh codec.MsgpackHandle | ||||
|       ch codec.CborHandle | ||||
|     ) | ||||
|  | ||||
|     mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) | ||||
|  | ||||
|     // configure extensions | ||||
|     // e.g. for msgpack, define functions and enable Time support for tag 1 | ||||
|     // mh.SetExt(reflect.TypeOf(time.Time{}), 1, myExt) | ||||
|  | ||||
|     // create and use decoder/encoder | ||||
|     var ( | ||||
|       r io.Reader | ||||
|       w io.Writer | ||||
|       b []byte | ||||
|       h = &bh // or mh to use msgpack | ||||
|     ) | ||||
|  | ||||
|     dec = codec.NewDecoder(r, h) | ||||
|     dec = codec.NewDecoderBytes(b, h) | ||||
|     err = dec.Decode(&v) | ||||
|  | ||||
|     enc = codec.NewEncoder(w, h) | ||||
|     enc = codec.NewEncoderBytes(&b, h) | ||||
|     err = enc.Encode(v) | ||||
|  | ||||
|     //RPC Server | ||||
|     go func() { | ||||
|         for { | ||||
|             conn, err := listener.Accept() | ||||
|             rpcCodec := codec.GoRpc.ServerCodec(conn, h) | ||||
|             //OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h) | ||||
|             rpc.ServeCodec(rpcCodec) | ||||
|         } | ||||
|     }() | ||||
|  | ||||
|     //RPC Communication (client side) | ||||
|     conn, err = net.Dial("tcp", "localhost:5555") | ||||
|     rpcCodec := codec.GoRpc.ClientCodec(conn, h) | ||||
|     //OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h) | ||||
|     client := rpc.NewClientWithCodec(rpcCodec) | ||||
|  | ||||
| Running Tests | ||||
|  | ||||
| To run tests, use the following: | ||||
|  | ||||
|     go test | ||||
|  | ||||
| To run the full suite of tests, use the following: | ||||
|  | ||||
|     go test -tags alltests -run Suite | ||||
|  | ||||
| You can run the tag 'safe' to run tests or build in safe mode. e.g. | ||||
|  | ||||
|     go test -tags safe -run Json | ||||
|     go test -tags "alltests safe" -run Suite | ||||
|  | ||||
| Running Benchmarks | ||||
|  | ||||
| Please see http://github.com/ugorji/go-codec-bench . | ||||
|  | ||||
| */ | ||||
| package codec | ||||
|  | ||||
							
								
								
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										946
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/binc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										946
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/binc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,946 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const bincDoPrune = true // No longer needed. Needed before as C lib did not support pruning. | ||||
|  | ||||
| // vd as low 4 bits (there are 16 slots) | ||||
| const ( | ||||
| 	bincVdSpecial byte = iota | ||||
| 	bincVdPosInt | ||||
| 	bincVdNegInt | ||||
| 	bincVdFloat | ||||
|  | ||||
| 	bincVdString | ||||
| 	bincVdByteArray | ||||
| 	bincVdArray | ||||
| 	bincVdMap | ||||
|  | ||||
| 	bincVdTimestamp | ||||
| 	bincVdSmallInt | ||||
| 	bincVdUnicodeOther | ||||
| 	bincVdSymbol | ||||
|  | ||||
| 	bincVdDecimal | ||||
| 	_               // open slot | ||||
| 	_               // open slot | ||||
| 	bincVdCustomExt = 0x0f | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	bincSpNil byte = iota | ||||
| 	bincSpFalse | ||||
| 	bincSpTrue | ||||
| 	bincSpNan | ||||
| 	bincSpPosInf | ||||
| 	bincSpNegInf | ||||
| 	bincSpZeroFloat | ||||
| 	bincSpZero | ||||
| 	bincSpNegOne | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	bincFlBin16 byte = iota | ||||
| 	bincFlBin32 | ||||
| 	_ // bincFlBin32e | ||||
| 	bincFlBin64 | ||||
| 	_ // bincFlBin64e | ||||
| 	// others not currently supported | ||||
| ) | ||||
|  | ||||
| type bincEncDriver struct { | ||||
| 	e *Encoder | ||||
| 	w encWriter | ||||
| 	m map[string]uint16 // symbols | ||||
| 	b [scratchByteArrayLen]byte | ||||
| 	s uint16 // symbols sequencer | ||||
| 	// encNoSeparator | ||||
| 	encDriverNoopContainerWriter | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) IsBuiltinType(rt uintptr) bool { | ||||
| 	return rt == timeTypId | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeBuiltin(rt uintptr, v interface{}) { | ||||
| 	if rt == timeTypId { | ||||
| 		var bs []byte | ||||
| 		switch x := v.(type) { | ||||
| 		case time.Time: | ||||
| 			bs = encodeTime(x) | ||||
| 		case *time.Time: | ||||
| 			bs = encodeTime(*x) | ||||
| 		default: | ||||
| 			e.e.errorf("binc error encoding builtin: expect time.Time, received %T", v) | ||||
| 		} | ||||
| 		e.w.writen1(bincVdTimestamp<<4 | uint8(len(bs))) | ||||
| 		e.w.writeb(bs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeNil() { | ||||
| 	e.w.writen1(bincVdSpecial<<4 | bincSpNil) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeBool(b bool) { | ||||
| 	if b { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpTrue) | ||||
| 	} else { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpFalse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeFloat32(f float32) { | ||||
| 	if f == 0 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat) | ||||
| 		return | ||||
| 	} | ||||
| 	e.w.writen1(bincVdFloat<<4 | bincFlBin32) | ||||
| 	bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeFloat64(f float64) { | ||||
| 	if f == 0 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat) | ||||
| 		return | ||||
| 	} | ||||
| 	bigen.PutUint64(e.b[:8], math.Float64bits(f)) | ||||
| 	if bincDoPrune { | ||||
| 		i := 7 | ||||
| 		for ; i >= 0 && (e.b[i] == 0); i-- { | ||||
| 		} | ||||
| 		i++ | ||||
| 		if i <= 6 { | ||||
| 			e.w.writen1(bincVdFloat<<4 | 0x8 | bincFlBin64) | ||||
| 			e.w.writen1(byte(i)) | ||||
| 			e.w.writeb(e.b[:i]) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	e.w.writen1(bincVdFloat<<4 | bincFlBin64) | ||||
| 	e.w.writeb(e.b[:8]) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encIntegerPrune(bd byte, pos bool, v uint64, lim uint8) { | ||||
| 	if lim == 4 { | ||||
| 		bigen.PutUint32(e.b[:lim], uint32(v)) | ||||
| 	} else { | ||||
| 		bigen.PutUint64(e.b[:lim], v) | ||||
| 	} | ||||
| 	if bincDoPrune { | ||||
| 		i := pruneSignExt(e.b[:lim], pos) | ||||
| 		e.w.writen1(bd | lim - 1 - byte(i)) | ||||
| 		e.w.writeb(e.b[i:lim]) | ||||
| 	} else { | ||||
| 		e.w.writen1(bd | lim - 1) | ||||
| 		e.w.writeb(e.b[:lim]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeInt(v int64) { | ||||
| 	const nbd byte = bincVdNegInt << 4 | ||||
| 	if v >= 0 { | ||||
| 		e.encUint(bincVdPosInt<<4, true, uint64(v)) | ||||
| 	} else if v == -1 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpNegOne) | ||||
| 	} else { | ||||
| 		e.encUint(bincVdNegInt<<4, false, uint64(-v)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeUint(v uint64) { | ||||
| 	e.encUint(bincVdPosInt<<4, true, v) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encUint(bd byte, pos bool, v uint64) { | ||||
| 	if v == 0 { | ||||
| 		e.w.writen1(bincVdSpecial<<4 | bincSpZero) | ||||
| 	} else if pos && v >= 1 && v <= 16 { | ||||
| 		e.w.writen1(bincVdSmallInt<<4 | byte(v-1)) | ||||
| 	} else if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd|0x0, byte(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd | 0x01) | ||||
| 		bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.encIntegerPrune(bd, pos, v, 4) | ||||
| 	} else { | ||||
| 		e.encIntegerPrune(bd, pos, v, 8) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, _ *Encoder) { | ||||
| 	bs := ext.WriteExt(rv) | ||||
| 	if bs == nil { | ||||
| 		e.EncodeNil() | ||||
| 		return | ||||
| 	} | ||||
| 	e.encodeExtPreamble(uint8(xtag), len(bs)) | ||||
| 	e.w.writeb(bs) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) { | ||||
| 	e.encodeExtPreamble(uint8(re.Tag), len(re.Data)) | ||||
| 	e.w.writeb(re.Data) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encodeExtPreamble(xtag byte, length int) { | ||||
| 	e.encLen(bincVdCustomExt<<4, uint64(length)) | ||||
| 	e.w.writen1(xtag) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) WriteArrayStart(length int) { | ||||
| 	e.encLen(bincVdArray<<4, uint64(length)) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) WriteMapStart(length int) { | ||||
| 	e.encLen(bincVdMap<<4, uint64(length)) | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeString(c charEncoding, v string) { | ||||
| 	l := uint64(len(v)) | ||||
| 	e.encBytesLen(c, l) | ||||
| 	if l > 0 { | ||||
| 		e.w.writestr(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeSymbol(v string) { | ||||
| 	// if WriteSymbolsNoRefs { | ||||
| 	// 	e.encodeString(c_UTF8, v) | ||||
| 	// 	return | ||||
| 	// } | ||||
|  | ||||
| 	//symbols only offer benefit when string length > 1. | ||||
| 	//This is because strings with length 1 take only 2 bytes to store | ||||
| 	//(bd with embedded length, and single byte for string val). | ||||
|  | ||||
| 	l := len(v) | ||||
| 	if l == 0 { | ||||
| 		e.encBytesLen(c_UTF8, 0) | ||||
| 		return | ||||
| 	} else if l == 1 { | ||||
| 		e.encBytesLen(c_UTF8, 1) | ||||
| 		e.w.writen1(v[0]) | ||||
| 		return | ||||
| 	} | ||||
| 	if e.m == nil { | ||||
| 		e.m = make(map[string]uint16, 16) | ||||
| 	} | ||||
| 	ui, ok := e.m[v] | ||||
| 	if ok { | ||||
| 		if ui <= math.MaxUint8 { | ||||
| 			e.w.writen2(bincVdSymbol<<4, byte(ui)) | ||||
| 		} else { | ||||
| 			e.w.writen1(bincVdSymbol<<4 | 0x8) | ||||
| 			bigenHelper{e.b[:2], e.w}.writeUint16(ui) | ||||
| 		} | ||||
| 	} else { | ||||
| 		e.s++ | ||||
| 		ui = e.s | ||||
| 		//ui = uint16(atomic.AddUint32(&e.s, 1)) | ||||
| 		e.m[v] = ui | ||||
| 		var lenprec uint8 | ||||
| 		if l <= math.MaxUint8 { | ||||
| 			// lenprec = 0 | ||||
| 		} else if l <= math.MaxUint16 { | ||||
| 			lenprec = 1 | ||||
| 		} else if int64(l) <= math.MaxUint32 { | ||||
| 			lenprec = 2 | ||||
| 		} else { | ||||
| 			lenprec = 3 | ||||
| 		} | ||||
| 		if ui <= math.MaxUint8 { | ||||
| 			e.w.writen2(bincVdSymbol<<4|0x0|0x4|lenprec, byte(ui)) | ||||
| 		} else { | ||||
| 			e.w.writen1(bincVdSymbol<<4 | 0x8 | 0x4 | lenprec) | ||||
| 			bigenHelper{e.b[:2], e.w}.writeUint16(ui) | ||||
| 		} | ||||
| 		if lenprec == 0 { | ||||
| 			e.w.writen1(byte(l)) | ||||
| 		} else if lenprec == 1 { | ||||
| 			bigenHelper{e.b[:2], e.w}.writeUint16(uint16(l)) | ||||
| 		} else if lenprec == 2 { | ||||
| 			bigenHelper{e.b[:4], e.w}.writeUint32(uint32(l)) | ||||
| 		} else { | ||||
| 			bigenHelper{e.b[:8], e.w}.writeUint64(uint64(l)) | ||||
| 		} | ||||
| 		e.w.writestr(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) { | ||||
| 	l := uint64(len(v)) | ||||
| 	e.encBytesLen(c, l) | ||||
| 	if l > 0 { | ||||
| 		e.w.writeb(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) { | ||||
| 	//TODO: support bincUnicodeOther (for now, just use string or bytearray) | ||||
| 	if c == c_RAW { | ||||
| 		e.encLen(bincVdByteArray<<4, length) | ||||
| 	} else { | ||||
| 		e.encLen(bincVdString<<4, length) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encLen(bd byte, l uint64) { | ||||
| 	if l < 12 { | ||||
| 		e.w.writen1(bd | uint8(l+4)) | ||||
| 	} else { | ||||
| 		e.encLenNumber(bd, l) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) encLenNumber(bd byte, v uint64) { | ||||
| 	if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd, byte(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd | 0x01) | ||||
| 		bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.w.writen1(bd | 0x02) | ||||
| 		bigenHelper{e.b[:4], e.w}.writeUint32(uint32(v)) | ||||
| 	} else { | ||||
| 		e.w.writen1(bd | 0x03) | ||||
| 		bigenHelper{e.b[:8], e.w}.writeUint64(uint64(v)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| //------------------------------------ | ||||
|  | ||||
| type bincDecSymbol struct { | ||||
| 	s string | ||||
| 	b []byte | ||||
| 	i uint16 | ||||
| } | ||||
|  | ||||
| type bincDecDriver struct { | ||||
| 	d      *Decoder | ||||
| 	h      *BincHandle | ||||
| 	r      decReader | ||||
| 	br     bool // bytes reader | ||||
| 	bdRead bool | ||||
| 	bd     byte | ||||
| 	vd     byte | ||||
| 	vs     byte | ||||
| 	// noStreamingCodec | ||||
| 	// decNoSeparator | ||||
| 	b [scratchByteArrayLen]byte | ||||
|  | ||||
| 	// linear searching on this slice is ok, | ||||
| 	// because we typically expect < 32 symbols in each stream. | ||||
| 	s []bincDecSymbol | ||||
| 	decDriverNoopContainerReader | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) readNextBd() { | ||||
| 	d.bd = d.r.readn1() | ||||
| 	d.vd = d.bd >> 4 | ||||
| 	d.vs = d.bd & 0x0f | ||||
| 	d.bdRead = true | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) uncacheRead() { | ||||
| 	if d.bdRead { | ||||
| 		d.r.unreadn1() | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) ContainerType() (vt valueType) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd == bincVdSpecial && d.vs == bincSpNil { | ||||
| 		return valueTypeNil | ||||
| 	} else if d.vd == bincVdByteArray { | ||||
| 		return valueTypeBytes | ||||
| 	} else if d.vd == bincVdString { | ||||
| 		return valueTypeString | ||||
| 	} else if d.vd == bincVdArray { | ||||
| 		return valueTypeArray | ||||
| 	} else if d.vd == bincVdMap { | ||||
| 		return valueTypeMap | ||||
| 	} else { | ||||
| 		// d.d.errorf("isContainerType: unsupported parameter: %v", vt) | ||||
| 	} | ||||
| 	return valueTypeUnset | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) TryDecodeAsNil() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == bincVdSpecial<<4|bincSpNil { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) IsBuiltinType(rt uintptr) bool { | ||||
| 	return rt == timeTypId | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeBuiltin(rt uintptr, v interface{}) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if rt == timeTypId { | ||||
| 		if d.vd != bincVdTimestamp { | ||||
| 			d.d.errorf("Invalid d.vd. Expecting 0x%x. Received: 0x%x", bincVdTimestamp, d.vd) | ||||
| 			return | ||||
| 		} | ||||
| 		tt, err := decodeTime(d.r.readx(int(d.vs))) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		var vt *time.Time = v.(*time.Time) | ||||
| 		*vt = tt | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decFloatPre(vs, defaultLen byte) { | ||||
| 	if vs&0x8 == 0 { | ||||
| 		d.r.readb(d.b[0:defaultLen]) | ||||
| 	} else { | ||||
| 		l := d.r.readn1() | ||||
| 		if l > 8 { | ||||
| 			d.d.errorf("At most 8 bytes used to represent float. Received: %v bytes", l) | ||||
| 			return | ||||
| 		} | ||||
| 		for i := l; i < 8; i++ { | ||||
| 			d.b[i] = 0 | ||||
| 		} | ||||
| 		d.r.readb(d.b[0:l]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decFloat() (f float64) { | ||||
| 	//if true { f = math.Float64frombits(bigen.Uint64(d.r.readx(8))); break; } | ||||
| 	if x := d.vs & 0x7; x == bincFlBin32 { | ||||
| 		d.decFloatPre(d.vs, 4) | ||||
| 		f = float64(math.Float32frombits(bigen.Uint32(d.b[0:4]))) | ||||
| 	} else if x == bincFlBin64 { | ||||
| 		d.decFloatPre(d.vs, 8) | ||||
| 		f = math.Float64frombits(bigen.Uint64(d.b[0:8])) | ||||
| 	} else { | ||||
| 		d.d.errorf("only float32 and float64 are supported. d.vd: 0x%x, d.vs: 0x%x", d.vd, d.vs) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decUint() (v uint64) { | ||||
| 	// need to inline the code (interface conversion and type assertion expensive) | ||||
| 	switch d.vs { | ||||
| 	case 0: | ||||
| 		v = uint64(d.r.readn1()) | ||||
| 	case 1: | ||||
| 		d.r.readb(d.b[6:8]) | ||||
| 		v = uint64(bigen.Uint16(d.b[6:8])) | ||||
| 	case 2: | ||||
| 		d.b[4] = 0 | ||||
| 		d.r.readb(d.b[5:8]) | ||||
| 		v = uint64(bigen.Uint32(d.b[4:8])) | ||||
| 	case 3: | ||||
| 		d.r.readb(d.b[4:8]) | ||||
| 		v = uint64(bigen.Uint32(d.b[4:8])) | ||||
| 	case 4, 5, 6: | ||||
| 		lim := int(7 - d.vs) | ||||
| 		d.r.readb(d.b[lim:8]) | ||||
| 		for i := 0; i < lim; i++ { | ||||
| 			d.b[i] = 0 | ||||
| 		} | ||||
| 		v = uint64(bigen.Uint64(d.b[:8])) | ||||
| 	case 7: | ||||
| 		d.r.readb(d.b[:8]) | ||||
| 		v = uint64(bigen.Uint64(d.b[:8])) | ||||
| 	default: | ||||
| 		d.d.errorf("unsigned integers with greater than 64 bits of precision not supported") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decCheckInteger() (ui uint64, neg bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	vd, vs := d.vd, d.vs | ||||
| 	if vd == bincVdPosInt { | ||||
| 		ui = d.decUint() | ||||
| 	} else if vd == bincVdNegInt { | ||||
| 		ui = d.decUint() | ||||
| 		neg = true | ||||
| 	} else if vd == bincVdSmallInt { | ||||
| 		ui = uint64(d.vs) + 1 | ||||
| 	} else if vd == bincVdSpecial { | ||||
| 		if vs == bincSpZero { | ||||
| 			//i = 0 | ||||
| 		} else if vs == bincSpNegOne { | ||||
| 			neg = true | ||||
| 			ui = 1 | ||||
| 		} else { | ||||
| 			d.d.errorf("numeric decode fails for special value: d.vs: 0x%x", d.vs) | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		d.d.errorf("number can only be decoded from uint or int values. d.bd: 0x%x, d.vd: 0x%x", d.bd, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeInt(bitsize uint8) (i int64) { | ||||
| 	ui, neg := d.decCheckInteger() | ||||
| 	i, overflow := chkOvf.SignedInt(ui) | ||||
| 	if overflow { | ||||
| 		d.d.errorf("simple: overflow converting %v to signed integer", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	if neg { | ||||
| 		i = -i | ||||
| 	} | ||||
| 	if chkOvf.Int(i, bitsize) { | ||||
| 		d.d.errorf("binc: overflow integer: %v for num bits: %v", i, bitsize) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeUint(bitsize uint8) (ui uint64) { | ||||
| 	ui, neg := d.decCheckInteger() | ||||
| 	if neg { | ||||
| 		d.d.errorf("Assigning negative signed value to unsigned type") | ||||
| 		return | ||||
| 	} | ||||
| 	if chkOvf.Uint(ui, bitsize) { | ||||
| 		d.d.errorf("binc: overflow integer: %v", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	vd, vs := d.vd, d.vs | ||||
| 	if vd == bincVdSpecial { | ||||
| 		d.bdRead = false | ||||
| 		if vs == bincSpNan { | ||||
| 			return math.NaN() | ||||
| 		} else if vs == bincSpPosInf { | ||||
| 			return math.Inf(1) | ||||
| 		} else if vs == bincSpZeroFloat || vs == bincSpZero { | ||||
| 			return | ||||
| 		} else if vs == bincSpNegInf { | ||||
| 			return math.Inf(-1) | ||||
| 		} else { | ||||
| 			d.d.errorf("Invalid d.vs decoding float where d.vd=bincVdSpecial: %v", d.vs) | ||||
| 			return | ||||
| 		} | ||||
| 	} else if vd == bincVdFloat { | ||||
| 		f = d.decFloat() | ||||
| 	} else { | ||||
| 		f = float64(d.DecodeInt(64)) | ||||
| 	} | ||||
| 	if chkOverflow32 && chkOvf.Float32(f) { | ||||
| 		d.d.errorf("binc: float32 overflow: %v", f) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // bool can be decoded from bool only (single byte). | ||||
| func (d *bincDecDriver) DecodeBool() (b bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if bd := d.bd; bd == (bincVdSpecial | bincSpFalse) { | ||||
| 		// b = false | ||||
| 	} else if bd == (bincVdSpecial | bincSpTrue) { | ||||
| 		b = true | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) ReadMapStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd != bincVdMap { | ||||
| 		d.d.errorf("Invalid d.vd for map. Expecting 0x%x. Got: 0x%x", bincVdMap, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	length = d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) ReadArrayStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd != bincVdArray { | ||||
| 		d.d.errorf("Invalid d.vd for array. Expecting 0x%x. Got: 0x%x", bincVdArray, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	length = d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decLen() int { | ||||
| 	if d.vs > 3 { | ||||
| 		return int(d.vs - 4) | ||||
| 	} | ||||
| 	return int(d.decLenNumber()) | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decLenNumber() (v uint64) { | ||||
| 	if x := d.vs; x == 0 { | ||||
| 		v = uint64(d.r.readn1()) | ||||
| 	} else if x == 1 { | ||||
| 		d.r.readb(d.b[6:8]) | ||||
| 		v = uint64(bigen.Uint16(d.b[6:8])) | ||||
| 	} else if x == 2 { | ||||
| 		d.r.readb(d.b[4:8]) | ||||
| 		v = uint64(bigen.Uint32(d.b[4:8])) | ||||
| 	} else { | ||||
| 		d.r.readb(d.b[:8]) | ||||
| 		v = bigen.Uint64(d.b[:8]) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool) (bs2 []byte, s string) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == bincVdSpecial<<4|bincSpNil { | ||||
| 		d.bdRead = false | ||||
| 		return | ||||
| 	} | ||||
| 	var slen int = -1 | ||||
| 	// var ok bool | ||||
| 	switch d.vd { | ||||
| 	case bincVdString, bincVdByteArray: | ||||
| 		slen = d.decLen() | ||||
| 		if zerocopy { | ||||
| 			if d.br { | ||||
| 				bs2 = d.r.readx(slen) | ||||
| 			} else if len(bs) == 0 { | ||||
| 				bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, d.b[:]) | ||||
| 			} else { | ||||
| 				bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, bs) | ||||
| 			} | ||||
| 		} else { | ||||
| 			bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, bs) | ||||
| 		} | ||||
| 		if withString { | ||||
| 			s = string(bs2) | ||||
| 		} | ||||
| 	case bincVdSymbol: | ||||
| 		// zerocopy doesn't apply for symbols, | ||||
| 		// as the values must be stored in a table for later use. | ||||
| 		// | ||||
| 		//from vs: extract numSymbolBytes, containsStringVal, strLenPrecision, | ||||
| 		//extract symbol | ||||
| 		//if containsStringVal, read it and put in map | ||||
| 		//else look in map for string value | ||||
| 		var symbol uint16 | ||||
| 		vs := d.vs | ||||
| 		if vs&0x8 == 0 { | ||||
| 			symbol = uint16(d.r.readn1()) | ||||
| 		} else { | ||||
| 			symbol = uint16(bigen.Uint16(d.r.readx(2))) | ||||
| 		} | ||||
| 		if d.s == nil { | ||||
| 			d.s = make([]bincDecSymbol, 0, 16) | ||||
| 		} | ||||
|  | ||||
| 		if vs&0x4 == 0 { | ||||
| 			for i := range d.s { | ||||
| 				j := &d.s[i] | ||||
| 				if j.i == symbol { | ||||
| 					bs2 = j.b | ||||
| 					if withString { | ||||
| 						if j.s == "" && bs2 != nil { | ||||
| 							j.s = string(bs2) | ||||
| 						} | ||||
| 						s = j.s | ||||
| 					} | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			switch vs & 0x3 { | ||||
| 			case 0: | ||||
| 				slen = int(d.r.readn1()) | ||||
| 			case 1: | ||||
| 				slen = int(bigen.Uint16(d.r.readx(2))) | ||||
| 			case 2: | ||||
| 				slen = int(bigen.Uint32(d.r.readx(4))) | ||||
| 			case 3: | ||||
| 				slen = int(bigen.Uint64(d.r.readx(8))) | ||||
| 			} | ||||
| 			// since using symbols, do not store any part of | ||||
| 			// the parameter bs in the map, as it might be a shared buffer. | ||||
| 			// bs2 = decByteSlice(d.r, slen, bs) | ||||
| 			bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, nil) | ||||
| 			if withString { | ||||
| 				s = string(bs2) | ||||
| 			} | ||||
| 			d.s = append(d.s, bincDecSymbol{i: symbol, s: s, b: bs2}) | ||||
| 		} | ||||
| 	default: | ||||
| 		d.d.errorf("Invalid d.vd. Expecting string:0x%x, bytearray:0x%x or symbol: 0x%x. Got: 0x%x", | ||||
| 			bincVdString, bincVdByteArray, bincVdSymbol, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeString() (s string) { | ||||
| 	// DecodeBytes does not accommodate symbols, whose impl stores string version in map. | ||||
| 	// Use decStringAndBytes directly. | ||||
| 	// return string(d.DecodeBytes(d.b[:], true, true)) | ||||
| 	_, s = d.decStringAndBytes(d.b[:], true, true) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeStringAsBytes() (s []byte) { | ||||
| 	s, _ = d.decStringAndBytes(d.b[:], false, true) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == bincVdSpecial<<4|bincSpNil { | ||||
| 		d.bdRead = false | ||||
| 		return nil | ||||
| 	} | ||||
| 	var clen int | ||||
| 	if d.vd == bincVdString || d.vd == bincVdByteArray { | ||||
| 		clen = d.decLen() | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid d.vd for bytes. Expecting string:0x%x or bytearray:0x%x. Got: 0x%x", | ||||
| 			bincVdString, bincVdByteArray, d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	if zerocopy { | ||||
| 		if d.br { | ||||
| 			return d.r.readx(clen) | ||||
| 		} else if len(bs) == 0 { | ||||
| 			bs = d.b[:] | ||||
| 		} | ||||
| 	} | ||||
| 	return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { | ||||
| 	if xtag > 0xff { | ||||
| 		d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag) | ||||
| 		return | ||||
| 	} | ||||
| 	realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag)) | ||||
| 	realxtag = uint64(realxtag1) | ||||
| 	if ext == nil { | ||||
| 		re := rv.(*RawExt) | ||||
| 		re.Tag = realxtag | ||||
| 		re.Data = detachZeroCopyBytes(d.br, re.Data, xbs) | ||||
| 	} else { | ||||
| 		ext.ReadExt(rv, xbs) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.vd == bincVdCustomExt { | ||||
| 		l := d.decLen() | ||||
| 		xtag = d.r.readn1() | ||||
| 		if verifyTag && xtag != tag { | ||||
| 			d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag) | ||||
| 			return | ||||
| 		} | ||||
| 		xbs = d.r.readx(l) | ||||
| 	} else if d.vd == bincVdByteArray { | ||||
| 		xbs = d.DecodeBytes(nil, true) | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid d.vd for extensions (Expecting extensions or byte array). Got: 0x%x", d.vd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) DecodeNaked() { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
|  | ||||
| 	n := d.d.n | ||||
| 	var decodeFurther bool | ||||
|  | ||||
| 	switch d.vd { | ||||
| 	case bincVdSpecial: | ||||
| 		switch d.vs { | ||||
| 		case bincSpNil: | ||||
| 			n.v = valueTypeNil | ||||
| 		case bincSpFalse: | ||||
| 			n.v = valueTypeBool | ||||
| 			n.b = false | ||||
| 		case bincSpTrue: | ||||
| 			n.v = valueTypeBool | ||||
| 			n.b = true | ||||
| 		case bincSpNan: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = math.NaN() | ||||
| 		case bincSpPosInf: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = math.Inf(1) | ||||
| 		case bincSpNegInf: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = math.Inf(-1) | ||||
| 		case bincSpZeroFloat: | ||||
| 			n.v = valueTypeFloat | ||||
| 			n.f = float64(0) | ||||
| 		case bincSpZero: | ||||
| 			n.v = valueTypeUint | ||||
| 			n.u = uint64(0) // int8(0) | ||||
| 		case bincSpNegOne: | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = int64(-1) // int8(-1) | ||||
| 		default: | ||||
| 			d.d.errorf("decodeNaked: Unrecognized special value 0x%x", d.vs) | ||||
| 		} | ||||
| 	case bincVdSmallInt: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = uint64(int8(d.vs)) + 1 // int8(d.vs) + 1 | ||||
| 	case bincVdPosInt: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = d.decUint() | ||||
| 	case bincVdNegInt: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = -(int64(d.decUint())) | ||||
| 	case bincVdFloat: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.decFloat() | ||||
| 	case bincVdSymbol: | ||||
| 		n.v = valueTypeSymbol | ||||
| 		n.s = d.DecodeString() | ||||
| 	case bincVdString: | ||||
| 		n.v = valueTypeString | ||||
| 		n.s = d.DecodeString() | ||||
| 	case bincVdByteArray: | ||||
| 		n.v = valueTypeBytes | ||||
| 		n.l = d.DecodeBytes(nil, false) | ||||
| 	case bincVdTimestamp: | ||||
| 		n.v = valueTypeTimestamp | ||||
| 		tt, err := decodeTime(d.r.readx(int(d.vs))) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		n.t = tt | ||||
| 	case bincVdCustomExt: | ||||
| 		n.v = valueTypeExt | ||||
| 		l := d.decLen() | ||||
| 		n.u = uint64(d.r.readn1()) | ||||
| 		n.l = d.r.readx(l) | ||||
| 	case bincVdArray: | ||||
| 		n.v = valueTypeArray | ||||
| 		decodeFurther = true | ||||
| 	case bincVdMap: | ||||
| 		n.v = valueTypeMap | ||||
| 		decodeFurther = true | ||||
| 	default: | ||||
| 		d.d.errorf("decodeNaked: Unrecognized d.vd: 0x%x", d.vd) | ||||
| 	} | ||||
|  | ||||
| 	if !decodeFurther { | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	if n.v == valueTypeUint && d.h.SignedInteger { | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(n.u) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| //------------------------------------ | ||||
|  | ||||
| //BincHandle is a Handle for the Binc Schema-Free Encoding Format | ||||
| //defined at https://github.com/ugorji/binc . | ||||
| // | ||||
| //BincHandle currently supports all Binc features with the following EXCEPTIONS: | ||||
| //  - only integers up to 64 bits of precision are supported. | ||||
| //    big integers are unsupported. | ||||
| //  - Only IEEE 754 binary32 and binary64 floats are supported (ie Go float32 and float64 types). | ||||
| //    extended precision and decimal IEEE 754 floats are unsupported. | ||||
| //  - Only UTF-8 strings supported. | ||||
| //    Unicode_Other Binc types (UTF16, UTF32) are currently unsupported. | ||||
| // | ||||
| //Note that these EXCEPTIONS are temporary and full support is possible and may happen soon. | ||||
| type BincHandle struct { | ||||
| 	BasicHandle | ||||
| 	binaryEncodingType | ||||
| 	noElemSeparators | ||||
| } | ||||
|  | ||||
| func (h *BincHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { | ||||
| 	return h.SetExt(rt, tag, &setExtWrapper{b: ext}) | ||||
| } | ||||
|  | ||||
| func (h *BincHandle) newEncDriver(e *Encoder) encDriver { | ||||
| 	return &bincEncDriver{e: e, w: e.w} | ||||
| } | ||||
|  | ||||
| func (h *BincHandle) newDecDriver(d *Decoder) decDriver { | ||||
| 	return &bincDecDriver{d: d, h: h, r: d.r, br: d.bytes} | ||||
| } | ||||
|  | ||||
| func (_ *BincHandle) IsBuiltinType(rt uintptr) bool { | ||||
| 	return rt == timeTypId | ||||
| } | ||||
|  | ||||
| func (e *bincEncDriver) reset() { | ||||
| 	e.w = e.e.w | ||||
| 	e.s = 0 | ||||
| 	e.m = nil | ||||
| } | ||||
|  | ||||
| func (d *bincDecDriver) reset() { | ||||
| 	d.r, d.br = d.d.r, d.d.bytes | ||||
| 	d.s = nil | ||||
| 	d.bd, d.bdRead, d.vd, d.vs = 0, false, 0, 0 | ||||
| } | ||||
|  | ||||
| var _ decDriver = (*bincDecDriver)(nil) | ||||
| var _ encDriver = (*bincEncDriver)(nil) | ||||
							
								
								
									
										631
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/cbor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										631
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/cbor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,631 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborMajorUint byte = iota | ||||
| 	cborMajorNegInt | ||||
| 	cborMajorBytes | ||||
| 	cborMajorText | ||||
| 	cborMajorArray | ||||
| 	cborMajorMap | ||||
| 	cborMajorTag | ||||
| 	cborMajorOther | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborBdFalse byte = 0xf4 + iota | ||||
| 	cborBdTrue | ||||
| 	cborBdNil | ||||
| 	cborBdUndefined | ||||
| 	cborBdExt | ||||
| 	cborBdFloat16 | ||||
| 	cborBdFloat32 | ||||
| 	cborBdFloat64 | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborBdIndefiniteBytes  byte = 0x5f | ||||
| 	cborBdIndefiniteString      = 0x7f | ||||
| 	cborBdIndefiniteArray       = 0x9f | ||||
| 	cborBdIndefiniteMap         = 0xbf | ||||
| 	cborBdBreak                 = 0xff | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CborStreamBytes  byte = 0x5f | ||||
| 	CborStreamString      = 0x7f | ||||
| 	CborStreamArray       = 0x9f | ||||
| 	CborStreamMap         = 0xbf | ||||
| 	CborStreamBreak       = 0xff | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cborBaseUint   byte = 0x00 | ||||
| 	cborBaseNegInt      = 0x20 | ||||
| 	cborBaseBytes       = 0x40 | ||||
| 	cborBaseString      = 0x60 | ||||
| 	cborBaseArray       = 0x80 | ||||
| 	cborBaseMap         = 0xa0 | ||||
| 	cborBaseTag         = 0xc0 | ||||
| 	cborBaseSimple      = 0xe0 | ||||
| ) | ||||
|  | ||||
| // ------------------- | ||||
|  | ||||
| type cborEncDriver struct { | ||||
| 	noBuiltInTypes | ||||
| 	encDriverNoopContainerWriter | ||||
| 	// encNoSeparator | ||||
| 	e *Encoder | ||||
| 	w encWriter | ||||
| 	h *CborHandle | ||||
| 	x [8]byte | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeNil() { | ||||
| 	e.w.writen1(cborBdNil) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeBool(b bool) { | ||||
| 	if b { | ||||
| 		e.w.writen1(cborBdTrue) | ||||
| 	} else { | ||||
| 		e.w.writen1(cborBdFalse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeFloat32(f float32) { | ||||
| 	e.w.writen1(cborBdFloat32) | ||||
| 	bigenHelper{e.x[:4], e.w}.writeUint32(math.Float32bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeFloat64(f float64) { | ||||
| 	e.w.writen1(cborBdFloat64) | ||||
| 	bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) encUint(v uint64, bd byte) { | ||||
| 	if v <= 0x17 { | ||||
| 		e.w.writen1(byte(v) + bd) | ||||
| 	} else if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd+0x18, uint8(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd + 0x19) | ||||
| 		bigenHelper{e.x[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.w.writen1(bd + 0x1a) | ||||
| 		bigenHelper{e.x[:4], e.w}.writeUint32(uint32(v)) | ||||
| 	} else { // if v <= math.MaxUint64 { | ||||
| 		e.w.writen1(bd + 0x1b) | ||||
| 		bigenHelper{e.x[:8], e.w}.writeUint64(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeInt(v int64) { | ||||
| 	if v < 0 { | ||||
| 		e.encUint(uint64(-1-v), cborBaseNegInt) | ||||
| 	} else { | ||||
| 		e.encUint(uint64(v), cborBaseUint) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeUint(v uint64) { | ||||
| 	e.encUint(v, cborBaseUint) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) encLen(bd byte, length int) { | ||||
| 	e.encUint(uint64(length), bd) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, en *Encoder) { | ||||
| 	e.encUint(uint64(xtag), cborBaseTag) | ||||
| 	if v := ext.ConvertExt(rv); v == nil { | ||||
| 		e.EncodeNil() | ||||
| 	} else { | ||||
| 		en.encode(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeRawExt(re *RawExt, en *Encoder) { | ||||
| 	e.encUint(uint64(re.Tag), cborBaseTag) | ||||
| 	if false && re.Data != nil { | ||||
| 		en.encode(re.Data) | ||||
| 	} else if re.Value != nil { | ||||
| 		en.encode(re.Value) | ||||
| 	} else { | ||||
| 		e.EncodeNil() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteArrayStart(length int) { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdIndefiniteArray) | ||||
| 	} else { | ||||
| 		e.encLen(cborBaseArray, length) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteMapStart(length int) { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdIndefiniteMap) | ||||
| 	} else { | ||||
| 		e.encLen(cborBaseMap, length) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteMapEnd() { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdBreak) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) WriteArrayEnd() { | ||||
| 	if e.h.IndefiniteLength { | ||||
| 		e.w.writen1(cborBdBreak) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeString(c charEncoding, v string) { | ||||
| 	e.encLen(cborBaseString, len(v)) | ||||
| 	e.w.writestr(v) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeSymbol(v string) { | ||||
| 	e.EncodeString(c_UTF8, v) | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) EncodeStringBytes(c charEncoding, v []byte) { | ||||
| 	if c == c_RAW { | ||||
| 		e.encLen(cborBaseBytes, len(v)) | ||||
| 	} else { | ||||
| 		e.encLen(cborBaseString, len(v)) | ||||
| 	} | ||||
| 	e.w.writeb(v) | ||||
| } | ||||
|  | ||||
| // ---------------------- | ||||
|  | ||||
| type cborDecDriver struct { | ||||
| 	d      *Decoder | ||||
| 	h      *CborHandle | ||||
| 	r      decReader | ||||
| 	b      [scratchByteArrayLen]byte | ||||
| 	br     bool // bytes reader | ||||
| 	bdRead bool | ||||
| 	bd     byte | ||||
| 	noBuiltInTypes | ||||
| 	// decNoSeparator | ||||
| 	decDriverNoopContainerReader | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) readNextBd() { | ||||
| 	d.bd = d.r.readn1() | ||||
| 	d.bdRead = true | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) uncacheRead() { | ||||
| 	if d.bdRead { | ||||
| 		d.r.unreadn1() | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) ContainerType() (vt valueType) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == cborBdNil { | ||||
| 		return valueTypeNil | ||||
| 	} else if d.bd == cborBdIndefiniteBytes || (d.bd >= cborBaseBytes && d.bd < cborBaseString) { | ||||
| 		return valueTypeBytes | ||||
| 	} else if d.bd == cborBdIndefiniteString || (d.bd >= cborBaseString && d.bd < cborBaseArray) { | ||||
| 		return valueTypeString | ||||
| 	} else if d.bd == cborBdIndefiniteArray || (d.bd >= cborBaseArray && d.bd < cborBaseMap) { | ||||
| 		return valueTypeArray | ||||
| 	} else if d.bd == cborBdIndefiniteMap || (d.bd >= cborBaseMap && d.bd < cborBaseTag) { | ||||
| 		return valueTypeMap | ||||
| 	} else { | ||||
| 		// d.d.errorf("isContainerType: unsupported parameter: %v", vt) | ||||
| 	} | ||||
| 	return valueTypeUnset | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) TryDecodeAsNil() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	// treat Nil and Undefined as nil values | ||||
| 	if d.bd == cborBdNil || d.bd == cborBdUndefined { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) CheckBreak() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == cborBdBreak { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decUint() (ui uint64) { | ||||
| 	v := d.bd & 0x1f | ||||
| 	if v <= 0x17 { | ||||
| 		ui = uint64(v) | ||||
| 	} else { | ||||
| 		if v == 0x18 { | ||||
| 			ui = uint64(d.r.readn1()) | ||||
| 		} else if v == 0x19 { | ||||
| 			ui = uint64(bigen.Uint16(d.r.readx(2))) | ||||
| 		} else if v == 0x1a { | ||||
| 			ui = uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 		} else if v == 0x1b { | ||||
| 			ui = uint64(bigen.Uint64(d.r.readx(8))) | ||||
| 		} else { | ||||
| 			d.d.errorf("decUint: Invalid descriptor: %v", d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decCheckInteger() (neg bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	major := d.bd >> 5 | ||||
| 	if major == cborMajorUint { | ||||
| 	} else if major == cborMajorNegInt { | ||||
| 		neg = true | ||||
| 	} else { | ||||
| 		d.d.errorf("invalid major: %v (bd: %v)", major, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeInt(bitsize uint8) (i int64) { | ||||
| 	neg := d.decCheckInteger() | ||||
| 	ui := d.decUint() | ||||
| 	// check if this number can be converted to an int without overflow | ||||
| 	var overflow bool | ||||
| 	if neg { | ||||
| 		if i, overflow = chkOvf.SignedInt(ui + 1); overflow { | ||||
| 			d.d.errorf("cbor: overflow converting %v to signed integer", ui+1) | ||||
| 			return | ||||
| 		} | ||||
| 		i = -i | ||||
| 	} else { | ||||
| 		if i, overflow = chkOvf.SignedInt(ui); overflow { | ||||
| 			d.d.errorf("cbor: overflow converting %v to signed integer", ui) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if chkOvf.Int(i, bitsize) { | ||||
| 		d.d.errorf("cbor: overflow integer: %v", i) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeUint(bitsize uint8) (ui uint64) { | ||||
| 	if d.decCheckInteger() { | ||||
| 		d.d.errorf("Assigning negative signed value to unsigned type") | ||||
| 		return | ||||
| 	} | ||||
| 	ui = d.decUint() | ||||
| 	if chkOvf.Uint(ui, bitsize) { | ||||
| 		d.d.errorf("cbor: overflow integer: %v", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if bd := d.bd; bd == cborBdFloat16 { | ||||
| 		f = float64(math.Float32frombits(halfFloatToFloatBits(bigen.Uint16(d.r.readx(2))))) | ||||
| 	} else if bd == cborBdFloat32 { | ||||
| 		f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4)))) | ||||
| 	} else if bd == cborBdFloat64 { | ||||
| 		f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) | ||||
| 	} else if bd >= cborBaseUint && bd < cborBaseBytes { | ||||
| 		f = float64(d.DecodeInt(64)) | ||||
| 	} else { | ||||
| 		d.d.errorf("Float only valid from float16/32/64: Invalid descriptor: %v", bd) | ||||
| 		return | ||||
| 	} | ||||
| 	if chkOverflow32 && chkOvf.Float32(f) { | ||||
| 		d.d.errorf("cbor: float32 overflow: %v", f) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // bool can be decoded from bool only (single byte). | ||||
| func (d *cborDecDriver) DecodeBool() (b bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if bd := d.bd; bd == cborBdTrue { | ||||
| 		b = true | ||||
| 	} else if bd == cborBdFalse { | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) ReadMapStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	if d.bd == cborBdIndefiniteMap { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	return d.decLen() | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) ReadArrayStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	if d.bd == cborBdIndefiniteArray { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	return d.decLen() | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decLen() int { | ||||
| 	return int(d.decUint()) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) decAppendIndefiniteBytes(bs []byte) []byte { | ||||
| 	d.bdRead = false | ||||
| 	for { | ||||
| 		if d.CheckBreak() { | ||||
| 			break | ||||
| 		} | ||||
| 		if major := d.bd >> 5; major != cborMajorBytes && major != cborMajorText { | ||||
| 			d.d.errorf("cbor: expect bytes or string major type in indefinite string/bytes; got: %v, byte: %v", major, d.bd) | ||||
| 			return nil | ||||
| 		} | ||||
| 		n := d.decLen() | ||||
| 		oldLen := len(bs) | ||||
| 		newLen := oldLen + n | ||||
| 		if newLen > cap(bs) { | ||||
| 			bs2 := make([]byte, newLen, 2*cap(bs)+n) | ||||
| 			copy(bs2, bs) | ||||
| 			bs = bs2 | ||||
| 		} else { | ||||
| 			bs = bs[:newLen] | ||||
| 		} | ||||
| 		d.r.readb(bs[oldLen:newLen]) | ||||
| 		// bs = append(bs, d.r.readn()...) | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return bs | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == cborBdNil || d.bd == cborBdUndefined { | ||||
| 		d.bdRead = false | ||||
| 		return nil | ||||
| 	} | ||||
| 	if d.bd == cborBdIndefiniteBytes || d.bd == cborBdIndefiniteString { | ||||
| 		if bs == nil { | ||||
| 			return d.decAppendIndefiniteBytes(nil) | ||||
| 		} | ||||
| 		return d.decAppendIndefiniteBytes(bs[:0]) | ||||
| 	} | ||||
| 	clen := d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	if zerocopy { | ||||
| 		if d.br { | ||||
| 			return d.r.readx(clen) | ||||
| 		} else if len(bs) == 0 { | ||||
| 			bs = d.b[:] | ||||
| 		} | ||||
| 	} | ||||
| 	return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeString() (s string) { | ||||
| 	return string(d.DecodeBytes(d.b[:], true)) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeStringAsBytes() (s []byte) { | ||||
| 	return d.DecodeBytes(d.b[:], true) | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	u := d.decUint() | ||||
| 	d.bdRead = false | ||||
| 	realxtag = u | ||||
| 	if ext == nil { | ||||
| 		re := rv.(*RawExt) | ||||
| 		re.Tag = realxtag | ||||
| 		d.d.decode(&re.Value) | ||||
| 	} else if xtag != realxtag { | ||||
| 		d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", realxtag, xtag) | ||||
| 		return | ||||
| 	} else { | ||||
| 		var v interface{} | ||||
| 		d.d.decode(&v) | ||||
| 		ext.UpdateExt(rv, v) | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) DecodeNaked() { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
|  | ||||
| 	n := d.d.n | ||||
| 	var decodeFurther bool | ||||
|  | ||||
| 	switch d.bd { | ||||
| 	case cborBdNil: | ||||
| 		n.v = valueTypeNil | ||||
| 	case cborBdFalse: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = false | ||||
| 	case cborBdTrue: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = true | ||||
| 	case cborBdFloat16, cborBdFloat32: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.DecodeFloat(true) | ||||
| 	case cborBdFloat64: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.DecodeFloat(false) | ||||
| 	case cborBdIndefiniteBytes: | ||||
| 		n.v = valueTypeBytes | ||||
| 		n.l = d.DecodeBytes(nil, false) | ||||
| 	case cborBdIndefiniteString: | ||||
| 		n.v = valueTypeString | ||||
| 		n.s = d.DecodeString() | ||||
| 	case cborBdIndefiniteArray: | ||||
| 		n.v = valueTypeArray | ||||
| 		decodeFurther = true | ||||
| 	case cborBdIndefiniteMap: | ||||
| 		n.v = valueTypeMap | ||||
| 		decodeFurther = true | ||||
| 	default: | ||||
| 		switch { | ||||
| 		case d.bd >= cborBaseUint && d.bd < cborBaseNegInt: | ||||
| 			if d.h.SignedInteger { | ||||
| 				n.v = valueTypeInt | ||||
| 				n.i = d.DecodeInt(64) | ||||
| 			} else { | ||||
| 				n.v = valueTypeUint | ||||
| 				n.u = d.DecodeUint(64) | ||||
| 			} | ||||
| 		case d.bd >= cborBaseNegInt && d.bd < cborBaseBytes: | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = d.DecodeInt(64) | ||||
| 		case d.bd >= cborBaseBytes && d.bd < cborBaseString: | ||||
| 			n.v = valueTypeBytes | ||||
| 			n.l = d.DecodeBytes(nil, false) | ||||
| 		case d.bd >= cborBaseString && d.bd < cborBaseArray: | ||||
| 			n.v = valueTypeString | ||||
| 			n.s = d.DecodeString() | ||||
| 		case d.bd >= cborBaseArray && d.bd < cborBaseMap: | ||||
| 			n.v = valueTypeArray | ||||
| 			decodeFurther = true | ||||
| 		case d.bd >= cborBaseMap && d.bd < cborBaseTag: | ||||
| 			n.v = valueTypeMap | ||||
| 			decodeFurther = true | ||||
| 		case d.bd >= cborBaseTag && d.bd < cborBaseSimple: | ||||
| 			n.v = valueTypeExt | ||||
| 			n.u = d.decUint() | ||||
| 			n.l = nil | ||||
| 			// d.bdRead = false | ||||
| 			// d.d.decode(&re.Value) // handled by decode itself. | ||||
| 			// decodeFurther = true | ||||
| 		default: | ||||
| 			d.d.errorf("decodeNaked: Unrecognized d.bd: 0x%x", d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !decodeFurther { | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ------------------------- | ||||
|  | ||||
| // CborHandle is a Handle for the CBOR encoding format, | ||||
| // defined at http://tools.ietf.org/html/rfc7049 and documented further at http://cbor.io . | ||||
| // | ||||
| // CBOR is comprehensively supported, including support for: | ||||
| //   - indefinite-length arrays/maps/bytes/strings | ||||
| //   - (extension) tags in range 0..0xffff (0 .. 65535) | ||||
| //   - half, single and double-precision floats | ||||
| //   - all numbers (1, 2, 4 and 8-byte signed and unsigned integers) | ||||
| //   - nil, true, false, ... | ||||
| //   - arrays and maps, bytes and text strings | ||||
| // | ||||
| // None of the optional extensions (with tags) defined in the spec are supported out-of-the-box. | ||||
| // Users can implement them as needed (using SetExt), including spec-documented ones: | ||||
| //   - timestamp, BigNum, BigFloat, Decimals, Encoded Text (e.g. URL, regexp, base64, MIME Message), etc. | ||||
| // | ||||
| // To encode with indefinite lengths (streaming), users will use | ||||
| // (Must)Encode methods of *Encoder, along with writing CborStreamXXX constants. | ||||
| // | ||||
| // For example, to encode "one-byte" as an indefinite length string: | ||||
| //     var buf bytes.Buffer | ||||
| //     e := NewEncoder(&buf, new(CborHandle)) | ||||
| //     buf.WriteByte(CborStreamString) | ||||
| //     e.MustEncode("one-") | ||||
| //     e.MustEncode("byte") | ||||
| //     buf.WriteByte(CborStreamBreak) | ||||
| //     encodedBytes := buf.Bytes() | ||||
| //     var vv interface{} | ||||
| //     NewDecoderBytes(buf.Bytes(), new(CborHandle)).MustDecode(&vv) | ||||
| //     // Now, vv contains the same string "one-byte" | ||||
| // | ||||
| type CborHandle struct { | ||||
| 	binaryEncodingType | ||||
| 	noElemSeparators | ||||
| 	BasicHandle | ||||
|  | ||||
| 	// IndefiniteLength=true, means that we encode using indefinitelength | ||||
| 	IndefiniteLength bool | ||||
| } | ||||
|  | ||||
| func (h *CborHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) { | ||||
| 	return h.SetExt(rt, tag, &setExtWrapper{i: ext}) | ||||
| } | ||||
|  | ||||
| func (h *CborHandle) newEncDriver(e *Encoder) encDriver { | ||||
| 	return &cborEncDriver{e: e, w: e.w, h: h} | ||||
| } | ||||
|  | ||||
| func (h *CborHandle) newDecDriver(d *Decoder) decDriver { | ||||
| 	return &cborDecDriver{d: d, h: h, r: d.r, br: d.bytes} | ||||
| } | ||||
|  | ||||
| func (e *cborEncDriver) reset() { | ||||
| 	e.w = e.e.w | ||||
| } | ||||
|  | ||||
| func (d *cborDecDriver) reset() { | ||||
| 	d.r, d.br = d.d.r, d.d.bytes | ||||
| 	d.bd, d.bdRead = 0, false | ||||
| } | ||||
|  | ||||
| var _ decDriver = (*cborDecDriver)(nil) | ||||
| var _ encDriver = (*cborEncDriver)(nil) | ||||
							
								
								
									
										2520
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/decode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2520
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/decode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1414
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/encode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1414
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/encode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										33034
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33034
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										35
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.not.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.not.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // +build notfastpath | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| const fastpathEnabled = false | ||||
|  | ||||
| // The generated fast-path code is very large, and adds a few seconds to the build time. | ||||
| // This causes test execution, execution of small tools which use codec, etc | ||||
| // to take a long time. | ||||
| // | ||||
| // To mitigate, we now support the notfastpath tag. | ||||
| // This tag disables fastpath during build, allowing for faster build, test execution, | ||||
| // short-program runs, etc. | ||||
|  | ||||
| func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool        { return false } | ||||
| func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool        { return false } | ||||
| func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool   { return false } | ||||
| func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool     { return false } | ||||
| func fastpathDecodeSetZeroTypeSwitch(iv interface{}, d *Decoder) bool { return false } | ||||
|  | ||||
| type fastpathT struct{} | ||||
| type fastpathE struct { | ||||
| 	rtid  uintptr | ||||
| 	rt    reflect.Type | ||||
| 	encfn func(*Encoder, *codecFnInfo, reflect.Value) | ||||
| 	decfn func(*Decoder, *codecFnInfo, reflect.Value) | ||||
| } | ||||
| type fastpathA [0]fastpathE | ||||
|  | ||||
| func (x fastpathA) index(rtid uintptr) int { return -1 } | ||||
|  | ||||
| var fastpathAV fastpathA | ||||
| var fastpathTV fastpathT | ||||
							
								
								
									
										250
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen-helper.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen-helper.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| /* // +build ignore */ | ||||
|  | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // ************************************************************ | ||||
| // DO NOT EDIT. | ||||
| // THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl | ||||
| // ************************************************************ | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"encoding" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| // GenVersion is the current version of codecgen. | ||||
| const GenVersion = 8 | ||||
|  | ||||
| // This file is used to generate helper code for codecgen. | ||||
| // The values here i.e. genHelper(En|De)coder are not to be used directly by | ||||
| // library users. They WILL change continuously and without notice. | ||||
| // | ||||
| // To help enforce this, we create an unexported type with exported members. | ||||
| // The only way to get the type is via the one exported type that we control (somewhat). | ||||
| // | ||||
| // When static codecs are created for types, they will use this value | ||||
| // to perform encoding or decoding of primitives or known slice or map types. | ||||
|  | ||||
| // GenHelperEncoder is exported so that it can be used externally by codecgen. | ||||
| // | ||||
| // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. | ||||
| func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) { | ||||
| 	return genHelperEncoder{e: e}, e.e | ||||
| } | ||||
|  | ||||
| // GenHelperDecoder is exported so that it can be used externally by codecgen. | ||||
| // | ||||
| // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. | ||||
| func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) { | ||||
| 	return genHelperDecoder{d: d}, d.d | ||||
| } | ||||
|  | ||||
| // Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE. | ||||
| func BasicHandleDoNotUse(h Handle) *BasicHandle { | ||||
| 	return h.getBasicHandle() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| type genHelperEncoder struct { | ||||
| 	e *Encoder | ||||
| 	F fastpathT | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| type genHelperDecoder struct { | ||||
| 	d *Decoder | ||||
| 	F fastpathT | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncBasicHandle() *BasicHandle { | ||||
| 	return f.e.h | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncBinary() bool { | ||||
| 	return f.e.cf.be // f.e.hh.isBinaryEncoding() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncFallback(iv interface{}) { | ||||
| 	// println(">>>>>>>>> EncFallback") | ||||
| 	// f.e.encodeI(iv, false, false) | ||||
| 	f.e.encodeValue(reflect.ValueOf(iv), nil, false) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) { | ||||
| 	bs, fnerr := iv.MarshalText() | ||||
| 	f.e.marshal(bs, fnerr, false, c_UTF8) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) { | ||||
| 	bs, fnerr := iv.MarshalJSON() | ||||
| 	f.e.marshal(bs, fnerr, true, c_UTF8) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) { | ||||
| 	bs, fnerr := iv.MarshalBinary() | ||||
| 	f.e.marshal(bs, fnerr, false, c_RAW) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncRaw(iv Raw) { | ||||
| 	f.e.rawBytes(iv) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) TimeRtidIfBinc() uintptr { | ||||
| 	if _, ok := f.e.hh.(*BincHandle); ok { | ||||
| 		return timeTypId | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) IsJSONHandle() bool { | ||||
| 	return f.e.cf.js | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) HasExtensions() bool { | ||||
| 	return len(f.e.h.extHandle) != 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperEncoder) EncExt(v interface{}) (r bool) { | ||||
| 	rt := reflect.TypeOf(v) | ||||
| 	if rt.Kind() == reflect.Ptr { | ||||
| 		rt = rt.Elem() | ||||
| 	} | ||||
| 	rtid := rt2id(rt) | ||||
| 	if xfFn := f.e.h.getExt(rtid); xfFn != nil { | ||||
| 		f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e) | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // ---------------- DECODER FOLLOWS ----------------- | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecBasicHandle() *BasicHandle { | ||||
| 	return f.d.h | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecBinary() bool { | ||||
| 	return f.d.be // f.d.hh.isBinaryEncoding() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecSwallow() { | ||||
| 	f.d.swallow() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecScratchBuffer() []byte { | ||||
| 	return f.d.b[:] | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) { | ||||
| 	// println(">>>>>>>>> DecFallback") | ||||
| 	rv := reflect.ValueOf(iv) | ||||
| 	if chkPtr { | ||||
| 		rv = f.d.ensureDecodeable(rv) | ||||
| 	} | ||||
| 	f.d.decodeValue(rv, nil, false, false) | ||||
| 	// f.d.decodeValueFallback(rv) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecSliceHelperStart() (decSliceHelper, int) { | ||||
| 	return f.d.decSliceHelperStart() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecStructFieldNotFound(index int, name string) { | ||||
| 	f.d.structFieldNotFound(index, name) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecArrayCannotExpand(sliceLen, streamLen int) { | ||||
| 	f.d.arrayCannotExpand(sliceLen, streamLen) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecTextUnmarshal(tm encoding.TextUnmarshaler) { | ||||
| 	fnerr := tm.UnmarshalText(f.d.d.DecodeStringAsBytes()) | ||||
| 	if fnerr != nil { | ||||
| 		panic(fnerr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecJSONUnmarshal(tm jsonUnmarshaler) { | ||||
| 	// bs := f.dd.DecodeStringAsBytes() | ||||
| 	// grab the bytes to be read, as UnmarshalJSON needs the full JSON so as to unmarshal it itself. | ||||
| 	fnerr := tm.UnmarshalJSON(f.d.nextValueBytes()) | ||||
| 	if fnerr != nil { | ||||
| 		panic(fnerr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecBinaryUnmarshal(bm encoding.BinaryUnmarshaler) { | ||||
| 	fnerr := bm.UnmarshalBinary(f.d.d.DecodeBytes(nil, true)) | ||||
| 	if fnerr != nil { | ||||
| 		panic(fnerr) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecRaw() []byte { | ||||
| 	return f.d.rawBytes() | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) TimeRtidIfBinc() uintptr { | ||||
| 	if _, ok := f.d.hh.(*BincHandle); ok { | ||||
| 		return timeTypId | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) IsJSONHandle() bool { | ||||
| 	return f.d.js | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) HasExtensions() bool { | ||||
| 	return len(f.d.h.extHandle) != 0 | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecExt(v interface{}) (r bool) { | ||||
| 	rt := reflect.TypeOf(v).Elem() | ||||
| 	rtid := rt2id(rt) | ||||
| 	if xfFn := f.d.h.getExt(rtid); xfFn != nil { | ||||
| 		f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext) | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) DecInferLen(clen, maxlen, unit int) (rvlen int) { | ||||
| 	return decInferLen(clen, maxlen, unit) | ||||
| } | ||||
|  | ||||
| // FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE* | ||||
| func (f genHelperDecoder) StringView(v []byte) string { | ||||
| 	return stringView(v) | ||||
| } | ||||
							
								
								
									
										132
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.generated.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| // DO NOT EDIT. THIS FILE IS AUTO-GENERATED FROM gen-dec-(map|array).go.tmpl | ||||
|  | ||||
| const genDecMapTmpl = ` | ||||
| {{var "v"}} := *{{ .Varname }} | ||||
| {{var "l"}} := r.ReadMapStart() | ||||
| {{var "bh"}} := z.DecBasicHandle() | ||||
| if {{var "v"}} == nil { | ||||
| 	{{var "rl"}} := z.DecInferLen({{var "l"}}, {{var "bh"}}.MaxInitLen, {{ .Size }}) | ||||
| 	{{var "v"}} = make(map[{{ .KTyp }}]{{ .Typ }}, {{var "rl"}}) | ||||
| 	*{{ .Varname }} = {{var "v"}} | ||||
| } | ||||
| var {{var "mk"}} {{ .KTyp }} | ||||
| var {{var "mv"}} {{ .Typ }} | ||||
| var {{var "mg"}}, {{var "mdn"}} {{if decElemKindPtr}}, {{var "ms"}}, {{var "mok"}}{{end}} bool | ||||
| if {{var "bh"}}.MapValueReset { | ||||
| 	{{if decElemKindPtr}}{{var "mg"}} = true | ||||
| 	{{else if decElemKindIntf}}if !{{var "bh"}}.InterfaceReset { {{var "mg"}} = true } | ||||
| 	{{else if not decElemKindImmutable}}{{var "mg"}} = true | ||||
| 	{{end}} } | ||||
| if {{var "l"}} != 0 { | ||||
| {{var "hl"}} := {{var "l"}} > 0  | ||||
| 	for {{var "j"}} := 0; ({{var "hl"}} && {{var "j"}} < {{var "l"}}) || !({{var "hl"}} || r.CheckBreak()); {{var "j"}}++ { | ||||
| 	r.ReadMapElemKey() {{/* z.DecSendContainerState(codecSelfer_containerMapKey{{ .Sfx }}) */}} | ||||
| 	{{ $x := printf "%vmk%v" .TempVar .Rand }}{{ decLineVarK $x }} | ||||
| {{ if eq .KTyp "interface{}" }}{{/* // special case if a byte array. */}}if {{var "bv"}}, {{var "bok"}} := {{var "mk"}}.([]byte); {{var "bok"}} { | ||||
| 		{{var "mk"}} = string({{var "bv"}}) | ||||
| 	}{{ end }}{{if decElemKindPtr}} | ||||
| 	{{var "ms"}} = true{{end}} | ||||
| 	if {{var "mg"}} { | ||||
| 		{{if decElemKindPtr}}{{var "mv"}}, {{var "mok"}} = {{var "v"}}[{{var "mk"}}]  | ||||
| 		if {{var "mok"}} { | ||||
| 			{{var "ms"}} = false | ||||
| 		} {{else}}{{var "mv"}} = {{var "v"}}[{{var "mk"}}] {{end}} | ||||
| 	} {{if not decElemKindImmutable}}else { {{var "mv"}} = {{decElemZero}} }{{end}} | ||||
| 	r.ReadMapElemValue() {{/* z.DecSendContainerState(codecSelfer_containerMapValue{{ .Sfx }}) */}} | ||||
| 	{{var "mdn"}} = false | ||||
| 	{{ $x := printf "%vmv%v" .TempVar .Rand }}{{ $y := printf "%vmdn%v" .TempVar .Rand }}{{ decLineVar $x $y }} | ||||
| 	if {{var "mdn"}} { | ||||
| 		if {{ var "bh" }}.DeleteOnNilMapValue { delete({{var "v"}}, {{var "mk"}}) } else { {{var "v"}}[{{var "mk"}}] = {{decElemZero}} } | ||||
| 	} else if {{if decElemKindPtr}} {{var "ms"}} && {{end}} {{var "v"}} != nil { | ||||
| 		{{var "v"}}[{{var "mk"}}] = {{var "mv"}} | ||||
| 	} | ||||
| } | ||||
| } // else len==0: TODO: Should we clear map entries? | ||||
| r.ReadMapEnd() {{/* z.DecSendContainerState(codecSelfer_containerMapEnd{{ .Sfx }}) */}} | ||||
| ` | ||||
|  | ||||
| const genDecListTmpl = ` | ||||
| {{var "v"}} := {{if not isArray}}*{{end}}{{ .Varname }} | ||||
| {{var "h"}}, {{var "l"}} := z.DecSliceHelperStart() {{/* // helper, containerLenS */}}{{if not isArray}} | ||||
| var {{var "c"}} bool {{/* // changed */}} | ||||
| _ = {{var "c"}}{{end}} | ||||
| if {{var "l"}} == 0 { | ||||
| 	{{if isSlice }}if {{var "v"}} == nil { | ||||
| 		{{var "v"}} = []{{ .Typ }}{} | ||||
| 		{{var "c"}} = true | ||||
| 	} else if len({{var "v"}}) != 0 { | ||||
| 		{{var "v"}} = {{var "v"}}[:0] | ||||
| 		{{var "c"}} = true | ||||
| 	} {{end}} {{if isChan }}if {{var "v"}} == nil { | ||||
| 		{{var "v"}} = make({{ .CTyp }}, 0) | ||||
| 		{{var "c"}} = true | ||||
| 	} {{end}} | ||||
| } else { | ||||
| 	{{var "hl"}} := {{var "l"}} > 0 | ||||
| 	var {{var "rl"}} int; _ =  {{var "rl"}} | ||||
| 	{{if isSlice }} if {{var "hl"}} { | ||||
| 	if {{var "l"}} > cap({{var "v"}}) { | ||||
| 		{{var "rl"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }}) | ||||
| 		if {{var "rl"}} <= cap({{var "v"}}) { | ||||
| 			{{var "v"}} = {{var "v"}}[:{{var "rl"}}] | ||||
| 		} else { | ||||
| 			{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}}) | ||||
| 		} | ||||
| 		{{var "c"}} = true | ||||
| 	} else if {{var "l"}} != len({{var "v"}}) { | ||||
| 		{{var "v"}} = {{var "v"}}[:{{var "l"}}] | ||||
| 		{{var "c"}} = true | ||||
| 	} | ||||
| 	} {{end}} | ||||
| 	var {{var "j"}} int  | ||||
|     // var {{var "dn"}} bool  | ||||
| 	for ; ({{var "hl"}} && {{var "j"}} < {{var "l"}}) || !({{var "hl"}} || r.CheckBreak()); {{var "j"}}++ { | ||||
| 		{{if not isArray}} if {{var "j"}} == 0 && len({{var "v"}}) == 0 { | ||||
| 			if {{var "hl"}} { | ||||
| 				{{var "rl"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }}) | ||||
| 			} else { | ||||
| 				{{var "rl"}} = 8 | ||||
| 			} | ||||
| 			{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}}) | ||||
| 			{{var "c"}} = true  | ||||
| 		}{{end}} | ||||
| 		{{var "h"}}.ElemContainerState({{var "j"}}) | ||||
|         // {{var "dn"}} = r.TryDecodeAsNil() | ||||
|         {{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }} | ||||
| 		{{ decLineVar $x }} | ||||
| 		{{var "v"}} <- {{ $x }} | ||||
|         {{else}} | ||||
| 		// if indefinite, etc, then expand the slice if necessary | ||||
| 		var {{var "db"}} bool | ||||
| 		if {{var "j"}} >= len({{var "v"}}) { | ||||
| 			{{if isSlice }} {{var "v"}} = append({{var "v"}}, {{ zero }}); {{var "c"}} = true | ||||
| 			{{else}} z.DecArrayCannotExpand(len(v), {{var "j"}}+1); {{var "db"}} = true | ||||
| 			{{end}} | ||||
| 		} | ||||
| 		if {{var "db"}} { | ||||
| 			z.DecSwallow() | ||||
| 		} else { | ||||
| 			{{ $x := printf "%[1]vv%[2]v[%[1]vj%[2]v]" .TempVar .Rand }}{{ decLineVar $x }} | ||||
| 		} | ||||
|         {{end}} | ||||
| 	} | ||||
| 	{{if isSlice}} if {{var "j"}} < len({{var "v"}}) { | ||||
| 		{{var "v"}} = {{var "v"}}[:{{var "j"}}] | ||||
| 		{{var "c"}} = true | ||||
| 	} else if {{var "j"}} == 0 && {{var "v"}} == nil { | ||||
| 		{{var "v"}} = make([]{{ .Typ }}, 0) | ||||
| 		{{var "c"}} = true | ||||
| 	} {{end}} | ||||
| } | ||||
| {{var "h"}}.End() | ||||
| {{if not isArray }}if {{var "c"}} {  | ||||
| 	*{{ .Varname }} = {{var "v"}} | ||||
| }{{end}} | ||||
|  | ||||
| ` | ||||
|  | ||||
							
								
								
									
										2014
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2014
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build go1.5 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| const reflectArrayOfSupported = true | ||||
|  | ||||
| func reflectArrayOf(count int, elem reflect.Type) reflect.Type { | ||||
| 	return reflect.ArrayOf(count, elem) | ||||
| } | ||||
							
								
								
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build !go1.5 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| const reflectArrayOfSupported = false | ||||
|  | ||||
| func reflectArrayOf(count int, elem reflect.Type) reflect.Type { | ||||
| 	panic("codec: reflect.ArrayOf unsupported in this go version") | ||||
| } | ||||
							
								
								
									
										15
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build go1.9 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| func makeMapReflect(t reflect.Type, size int) reflect.Value { | ||||
| 	if size < 0 { | ||||
| 		return reflect.MakeMapWithSize(t, 4) | ||||
| 	} | ||||
| 	return reflect.MakeMapWithSize(t, size) | ||||
| } | ||||
							
								
								
									
										12
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build !go1.9 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "reflect" | ||||
|  | ||||
| func makeMapReflect(t reflect.Type, size int) reflect.Value { | ||||
| 	return reflect.MakeMap(t) | ||||
| } | ||||
							
								
								
									
										17
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build !go1.4 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| // This codec package will only work for go1.4 and above. | ||||
| // This is for the following reasons: | ||||
| //   - go 1.4 was released in 2014 | ||||
| //   - go runtime is written fully in go | ||||
| //   - interface only holds pointers | ||||
| //   - reflect.Value is stabilized as 3 words | ||||
|  | ||||
| func init() { | ||||
| 	panic("codec: go 1.3 and below are not supported") | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build go1.5,!go1.6 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "os" | ||||
|  | ||||
| var genCheckVendor = os.Getenv("GO15VENDOREXPERIMENT") == "1" | ||||
							
								
								
									
										10
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build go1.6,!go1.7 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import "os" | ||||
|  | ||||
| var genCheckVendor = os.Getenv("GO15VENDOREXPERIMENT") != "0" | ||||
							
								
								
									
										8
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build go1.7 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| const genCheckVendor = true | ||||
							
								
								
									
										8
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build !go1.5 | ||||
|  | ||||
| package codec | ||||
|  | ||||
| var genCheckVendor = false | ||||
							
								
								
									
										1944
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1944
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										221
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_internal.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_internal.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| // All non-std package dependencies live in this file, | ||||
| // so porting to different environment is easy (just update functions). | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| func panicValToErr(panicVal interface{}, err *error) { | ||||
| 	if panicVal == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	// case nil | ||||
| 	switch xerr := panicVal.(type) { | ||||
| 	case error: | ||||
| 		*err = xerr | ||||
| 	case string: | ||||
| 		*err = errors.New(xerr) | ||||
| 	default: | ||||
| 		*err = fmt.Errorf("%v", panicVal) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool { | ||||
| 	switch v.Kind() { | ||||
| 	case reflect.Invalid: | ||||
| 		return true | ||||
| 	case reflect.Array, reflect.Map, reflect.Slice, reflect.String: | ||||
| 		return v.Len() == 0 | ||||
| 	case reflect.Bool: | ||||
| 		return !v.Bool() | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||||
| 		return v.Int() == 0 | ||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | ||||
| 		return v.Uint() == 0 | ||||
| 	case reflect.Float32, reflect.Float64: | ||||
| 		return v.Float() == 0 | ||||
| 	case reflect.Interface, reflect.Ptr: | ||||
| 		if deref { | ||||
| 			if v.IsNil() { | ||||
| 				return true | ||||
| 			} | ||||
| 			return hIsEmptyValue(v.Elem(), deref, checkStruct) | ||||
| 		} else { | ||||
| 			return v.IsNil() | ||||
| 		} | ||||
| 	case reflect.Struct: | ||||
| 		if !checkStruct { | ||||
| 			return false | ||||
| 		} | ||||
| 		// return true if all fields are empty. else return false. | ||||
| 		// we cannot use equality check, because some fields may be maps/slices/etc | ||||
| 		// and consequently the structs are not comparable. | ||||
| 		// return v.Interface() == reflect.Zero(v.Type()).Interface() | ||||
| 		for i, n := 0, v.NumField(); i < n; i++ { | ||||
| 			if !hIsEmptyValue(v.Field(i), deref, checkStruct) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func isEmptyValue(v reflect.Value, deref, checkStruct bool) bool { | ||||
| 	return hIsEmptyValue(v, deref, checkStruct) | ||||
| } | ||||
|  | ||||
| func pruneSignExt(v []byte, pos bool) (n int) { | ||||
| 	if len(v) < 2 { | ||||
| 	} else if pos && v[0] == 0 { | ||||
| 		for ; v[n] == 0 && n+1 < len(v) && (v[n+1]&(1<<7) == 0); n++ { | ||||
| 		} | ||||
| 	} else if !pos && v[0] == 0xff { | ||||
| 		for ; v[n] == 0xff && n+1 < len(v) && (v[n+1]&(1<<7) != 0); n++ { | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func implementsIntf(typ, iTyp reflect.Type) (success bool, indir int8) { | ||||
| 	if typ == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	rt := typ | ||||
| 	// The type might be a pointer and we need to keep | ||||
| 	// dereferencing to the base type until we find an implementation. | ||||
| 	for { | ||||
| 		if rt.Implements(iTyp) { | ||||
| 			return true, indir | ||||
| 		} | ||||
| 		if p := rt; p.Kind() == reflect.Ptr { | ||||
| 			indir++ | ||||
| 			if indir >= math.MaxInt8 { // insane number of indirections | ||||
| 				return false, 0 | ||||
| 			} | ||||
| 			rt = p.Elem() | ||||
| 			continue | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
| 	// No luck yet, but if this is a base type (non-pointer), the pointer might satisfy. | ||||
| 	if typ.Kind() != reflect.Ptr { | ||||
| 		// Not a pointer, but does the pointer work? | ||||
| 		if reflect.PtrTo(typ).Implements(iTyp) { | ||||
| 			return true, -1 | ||||
| 		} | ||||
| 	} | ||||
| 	return false, 0 | ||||
| } | ||||
|  | ||||
| // validate that this function is correct ... | ||||
| // culled from OGRE (Object-Oriented Graphics Rendering Engine) | ||||
| // function: halfToFloatI (http://stderr.org/doc/ogre-doc/api/OgreBitwise_8h-source.html) | ||||
| func halfFloatToFloatBits(yy uint16) (d uint32) { | ||||
| 	y := uint32(yy) | ||||
| 	s := (y >> 15) & 0x01 | ||||
| 	e := (y >> 10) & 0x1f | ||||
| 	m := y & 0x03ff | ||||
|  | ||||
| 	if e == 0 { | ||||
| 		if m == 0 { // plu or minus 0 | ||||
| 			return s << 31 | ||||
| 		} else { // Denormalized number -- renormalize it | ||||
| 			for (m & 0x00000400) == 0 { | ||||
| 				m <<= 1 | ||||
| 				e -= 1 | ||||
| 			} | ||||
| 			e += 1 | ||||
| 			const zz uint32 = 0x0400 | ||||
| 			m &= ^zz | ||||
| 		} | ||||
| 	} else if e == 31 { | ||||
| 		if m == 0 { // Inf | ||||
| 			return (s << 31) | 0x7f800000 | ||||
| 		} else { // NaN | ||||
| 			return (s << 31) | 0x7f800000 | (m << 13) | ||||
| 		} | ||||
| 	} | ||||
| 	e = e + (127 - 15) | ||||
| 	m = m << 13 | ||||
| 	return (s << 31) | (e << 23) | m | ||||
| } | ||||
|  | ||||
| // GrowCap will return a new capacity for a slice, given the following: | ||||
| //   - oldCap: current capacity | ||||
| //   - unit: in-memory size of an element | ||||
| //   - num: number of elements to add | ||||
| func growCap(oldCap, unit, num int) (newCap int) { | ||||
| 	// appendslice logic (if cap < 1024, *2, else *1.25): | ||||
| 	//   leads to many copy calls, especially when copying bytes. | ||||
| 	//   bytes.Buffer model (2*cap + n): much better for bytes. | ||||
| 	// smarter way is to take the byte-size of the appended element(type) into account | ||||
|  | ||||
| 	// maintain 3 thresholds: | ||||
| 	// t1: if cap <= t1, newcap = 2x | ||||
| 	// t2: if cap <= t2, newcap = 1.75x | ||||
| 	// t3: if cap <= t3, newcap = 1.5x | ||||
| 	//     else          newcap = 1.25x | ||||
| 	// | ||||
| 	// t1, t2, t3 >= 1024 always. | ||||
| 	// i.e. if unit size >= 16, then always do 2x or 1.25x (ie t1, t2, t3 are all same) | ||||
| 	// | ||||
| 	// With this, appending for bytes increase by: | ||||
| 	//    100% up to 4K | ||||
| 	//     75% up to 8K | ||||
| 	//     50% up to 16K | ||||
| 	//     25% beyond that | ||||
|  | ||||
| 	// unit can be 0 e.g. for struct{}{}; handle that appropriately | ||||
| 	var t1, t2, t3 int // thresholds | ||||
| 	if unit <= 1 { | ||||
| 		t1, t2, t3 = 4*1024, 8*1024, 16*1024 | ||||
| 	} else if unit < 16 { | ||||
| 		t3 = 16 / unit * 1024 | ||||
| 		t1 = t3 * 1 / 4 | ||||
| 		t2 = t3 * 2 / 4 | ||||
| 	} else { | ||||
| 		t1, t2, t3 = 1024, 1024, 1024 | ||||
| 	} | ||||
|  | ||||
| 	var x int // temporary variable | ||||
|  | ||||
| 	// x is multiplier here: one of 5, 6, 7 or 8; incr of 25%, 50%, 75% or 100% respectively | ||||
| 	if oldCap <= t1 { // [0,t1] | ||||
| 		x = 8 | ||||
| 	} else if oldCap > t3 { // (t3,infinity] | ||||
| 		x = 5 | ||||
| 	} else if oldCap <= t2 { // (t1,t2] | ||||
| 		x = 7 | ||||
| 	} else { // (t2,t3] | ||||
| 		x = 6 | ||||
| 	} | ||||
| 	newCap = x * oldCap / 4 | ||||
|  | ||||
| 	if num > 0 { | ||||
| 		newCap += num | ||||
| 	} | ||||
|  | ||||
| 	// ensure newCap is a multiple of 64 (if it is > 64) or 16. | ||||
| 	if newCap > 64 { | ||||
| 		if x = newCap % 64; x != 0 { | ||||
| 			x = newCap / 64 | ||||
| 			newCap = 64 * (x + 1) | ||||
| 		} | ||||
| 	} else { | ||||
| 		if x = newCap % 16; x != 0 { | ||||
| 			x = newCap / 16 | ||||
| 			newCap = 16 * (x + 1) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										156
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| // +build !go1.7 safe appengine | ||||
|  | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
|  | ||||
| // stringView returns a view of the []byte as a string. | ||||
| // In unsafe mode, it doesn't incur allocation and copying caused by conversion. | ||||
| // In regular safe mode, it is an allocation and copy. | ||||
| // | ||||
| // Usage: Always maintain a reference to v while result of this call is in use, | ||||
| //        and call keepAlive4BytesView(v) at point where done with view. | ||||
| func stringView(v []byte) string { | ||||
| 	return string(v) | ||||
| } | ||||
|  | ||||
| // bytesView returns a view of the string as a []byte. | ||||
| // In unsafe mode, it doesn't incur allocation and copying caused by conversion. | ||||
| // In regular safe mode, it is an allocation and copy. | ||||
| // | ||||
| // Usage: Always maintain a reference to v while result of this call is in use, | ||||
| //        and call keepAlive4BytesView(v) at point where done with view. | ||||
| func bytesView(v string) []byte { | ||||
| 	return []byte(v) | ||||
| } | ||||
|  | ||||
| func definitelyNil(v interface{}) bool { | ||||
| 	return false | ||||
| 	// rv := reflect.ValueOf(v) | ||||
| 	// switch rv.Kind() { | ||||
| 	// case reflect.Invalid: | ||||
| 	// 	return true | ||||
| 	// case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Slice, reflect.Map, reflect.Func: | ||||
| 	// 	return rv.IsNil() | ||||
| 	// default: | ||||
| 	// 	return false | ||||
| 	// } | ||||
| } | ||||
|  | ||||
| // // keepAlive4BytesView maintains a reference to the input parameter for bytesView. | ||||
| // // | ||||
| // // Usage: call this at point where done with the bytes view. | ||||
| // func keepAlive4BytesView(v string) {} | ||||
|  | ||||
| // // keepAlive4BytesView maintains a reference to the input parameter for stringView. | ||||
| // // | ||||
| // // Usage: call this at point where done with the string view. | ||||
| // func keepAlive4StringView(v []byte) {} | ||||
|  | ||||
| func rv2i(rv reflect.Value) interface{} { | ||||
| 	return rv.Interface() | ||||
| } | ||||
|  | ||||
| func rt2id(rt reflect.Type) uintptr { | ||||
| 	return reflect.ValueOf(rt).Pointer() | ||||
| } | ||||
|  | ||||
| func rv2rtid(rv reflect.Value) uintptr { | ||||
| 	return reflect.ValueOf(rv.Type()).Pointer() | ||||
| } | ||||
|  | ||||
| // -------------------------- | ||||
| // type ptrToRvMap struct{} | ||||
|  | ||||
| // func (_ *ptrToRvMap) init() {} | ||||
| // func (_ *ptrToRvMap) get(i interface{}) reflect.Value { | ||||
| // 	return reflect.ValueOf(i).Elem() | ||||
| // } | ||||
|  | ||||
| // -------------------------- | ||||
| type atomicTypeInfoSlice struct { | ||||
| 	v atomic.Value | ||||
| } | ||||
|  | ||||
| func (x *atomicTypeInfoSlice) load() *[]rtid2ti { | ||||
| 	i := x.v.Load() | ||||
| 	if i == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return i.(*[]rtid2ti) | ||||
| } | ||||
|  | ||||
| func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) { | ||||
| 	x.v.Store(p) | ||||
| } | ||||
|  | ||||
| // -------------------------- | ||||
| func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetBytes(d.rawBytes()) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kString(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetString(d.d.DecodeString()) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetBool(d.d.DecodeBool()) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetFloat(d.d.DecodeFloat(true)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetFloat(d.d.DecodeFloat(false)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetInt(d.d.DecodeInt(intBitsize)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetInt(d.d.DecodeInt(8)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetInt(d.d.DecodeInt(16)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetInt(d.d.DecodeInt(32)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetInt(d.d.DecodeInt(64)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetUint(d.d.DecodeUint(uintBitsize)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetUint(d.d.DecodeUint(uintBitsize)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetUint(d.d.DecodeUint(8)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetUint(d.d.DecodeUint(16)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetUint(d.d.DecodeUint(32)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) { | ||||
| 	rv.SetUint(d.d.DecodeUint(64)) | ||||
| } | ||||
							
								
								
									
										418
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_unsafe.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										418
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_unsafe.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,418 @@ | ||||
| // +build !safe | ||||
| // +build !appengine | ||||
| // +build go1.7 | ||||
|  | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"sync/atomic" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| // This file has unsafe variants of some helper methods. | ||||
| // NOTE: See helper_not_unsafe.go for the usage information. | ||||
|  | ||||
| // var zeroRTv [4]uintptr | ||||
|  | ||||
| const unsafeFlagIndir = 1 << 7 // keep in sync with GO_ROOT/src/reflect/value.go | ||||
|  | ||||
| type unsafeString struct { | ||||
| 	Data uintptr | ||||
| 	Len  int | ||||
| } | ||||
|  | ||||
| type unsafeSlice struct { | ||||
| 	Data uintptr | ||||
| 	Len  int | ||||
| 	Cap  int | ||||
| } | ||||
|  | ||||
| type unsafeIntf struct { | ||||
| 	typ  unsafe.Pointer | ||||
| 	word unsafe.Pointer | ||||
| } | ||||
|  | ||||
| type unsafeReflectValue struct { | ||||
| 	typ  unsafe.Pointer | ||||
| 	ptr  unsafe.Pointer | ||||
| 	flag uintptr | ||||
| } | ||||
|  | ||||
| func stringView(v []byte) string { | ||||
| 	if len(v) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	bx := (*unsafeSlice)(unsafe.Pointer(&v)) | ||||
| 	sx := unsafeString{bx.Data, bx.Len} | ||||
| 	return *(*string)(unsafe.Pointer(&sx)) | ||||
| } | ||||
|  | ||||
| func bytesView(v string) []byte { | ||||
| 	if len(v) == 0 { | ||||
| 		return zeroByteSlice | ||||
| 	} | ||||
|  | ||||
| 	sx := (*unsafeString)(unsafe.Pointer(&v)) | ||||
| 	bx := unsafeSlice{sx.Data, sx.Len, sx.Len} | ||||
| 	return *(*[]byte)(unsafe.Pointer(&bx)) | ||||
| } | ||||
|  | ||||
| func definitelyNil(v interface{}) bool { | ||||
| 	return (*unsafeIntf)(unsafe.Pointer(&v)).word == nil | ||||
| } | ||||
|  | ||||
| // func keepAlive4BytesView(v string) { | ||||
| // 	runtime.KeepAlive(v) | ||||
| // } | ||||
|  | ||||
| // func keepAlive4StringView(v []byte) { | ||||
| // 	runtime.KeepAlive(v) | ||||
| // } | ||||
|  | ||||
| // TODO: consider a more generally-known optimization for reflect.Value ==> Interface | ||||
| // | ||||
| // Currently, we use this fragile method that taps into implememtation details from | ||||
| // the source go stdlib reflect/value.go, | ||||
| // and trims the implementation. | ||||
| func rv2i(rv reflect.Value) interface{} { | ||||
| 	if false { | ||||
| 		return rv.Interface() | ||||
| 	} | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	// references that are single-words (map, ptr) may be double-referenced as flagIndir | ||||
| 	kk := urv.flag & (1<<5 - 1) | ||||
| 	if (kk == uintptr(reflect.Map) || kk == uintptr(reflect.Ptr)) && urv.flag&unsafeFlagIndir != 0 { | ||||
| 		return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ})) | ||||
| 	} | ||||
| 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ})) | ||||
| } | ||||
|  | ||||
| func rt2id(rt reflect.Type) uintptr { | ||||
| 	return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word) | ||||
| } | ||||
|  | ||||
| func rv2rtid(rv reflect.Value) uintptr { | ||||
| 	return uintptr((*unsafeReflectValue)(unsafe.Pointer(&rv)).typ) | ||||
| } | ||||
|  | ||||
| // func rv0t(rt reflect.Type) reflect.Value { | ||||
| // 	ut := (*unsafeIntf)(unsafe.Pointer(&rt)) | ||||
| // 	// we need to determine whether ifaceIndir, and then whether to just pass 0 as the ptr | ||||
| // 	uv := unsafeReflectValue{ut.word, &zeroRTv, flag(rt.Kind())} | ||||
| // 	return *(*reflect.Value)(unsafe.Pointer(&uv}) | ||||
| // } | ||||
|  | ||||
| // -------------------------- | ||||
| type atomicTypeInfoSlice struct { | ||||
| 	v unsafe.Pointer | ||||
| } | ||||
|  | ||||
| func (x *atomicTypeInfoSlice) load() *[]rtid2ti { | ||||
| 	return (*[]rtid2ti)(atomic.LoadPointer(&x.v)) | ||||
| } | ||||
|  | ||||
| func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) { | ||||
| 	atomic.StorePointer(&x.v, unsafe.Pointer(p)) | ||||
| } | ||||
|  | ||||
| // -------------------------- | ||||
| func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	// if urv.flag&unsafeFlagIndir != 0 { | ||||
| 	// 	urv.ptr = *(*unsafe.Pointer)(urv.ptr) | ||||
| 	// } | ||||
| 	*(*[]byte)(urv.ptr) = d.rawBytes() | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kString(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*string)(urv.ptr) = d.d.DecodeString() | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*bool)(urv.ptr) = d.d.DecodeBool() | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*float32)(urv.ptr) = float32(d.d.DecodeFloat(true)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*float64)(urv.ptr) = d.d.DecodeFloat(false) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*int)(urv.ptr) = int(d.d.DecodeInt(intBitsize)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*int8)(urv.ptr) = int8(d.d.DecodeInt(8)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*int16)(urv.ptr) = int16(d.d.DecodeInt(16)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*int32)(urv.ptr) = int32(d.d.DecodeInt(32)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*int64)(urv.ptr) = d.d.DecodeInt(64) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*uint)(urv.ptr) = uint(d.d.DecodeUint(uintBitsize)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*uintptr)(urv.ptr) = uintptr(d.d.DecodeUint(uintBitsize)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*uint8)(urv.ptr) = uint8(d.d.DecodeUint(8)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*uint16)(urv.ptr) = uint16(d.d.DecodeUint(16)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*uint32)(urv.ptr) = uint32(d.d.DecodeUint(32)) | ||||
| } | ||||
|  | ||||
| func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) { | ||||
| 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| 	*(*uint64)(urv.ptr) = d.d.DecodeUint(64) | ||||
| } | ||||
|  | ||||
| // ------------ | ||||
|  | ||||
| // func rt2id(rt reflect.Type) uintptr { | ||||
| // 	return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word) | ||||
| // 	// var i interface{} = rt | ||||
| // 	// // ui := (*unsafeIntf)(unsafe.Pointer(&i)) | ||||
| // 	// return ((*unsafeIntf)(unsafe.Pointer(&i))).word | ||||
| // } | ||||
|  | ||||
| // func rv2i(rv reflect.Value) interface{} { | ||||
| // 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| // 	// non-reference type: already indir | ||||
| // 	// reference type: depend on flagIndir property ('cos maybe was double-referenced) | ||||
| // 	// const (unsafeRvFlagKindMask    = 1<<5 - 1 , unsafeRvFlagIndir       = 1 << 7 ) | ||||
| // 	// rvk := reflect.Kind(urv.flag & (1<<5 - 1)) | ||||
| // 	// if (rvk == reflect.Chan || | ||||
| // 	// 	rvk == reflect.Func || | ||||
| // 	// 	rvk == reflect.Interface || | ||||
| // 	// 	rvk == reflect.Map || | ||||
| // 	// 	rvk == reflect.Ptr || | ||||
| // 	// 	rvk == reflect.UnsafePointer) && urv.flag&(1<<8) != 0 { | ||||
| // 	// 	fmt.Printf(">>>>> ---- double indirect reference: %v, %v\n", rvk, rv.Type()) | ||||
| // 	// 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ})) | ||||
| // 	// } | ||||
| // 	if urv.flag&(1<<5-1) == uintptr(reflect.Map) && urv.flag&(1<<7) != 0 { | ||||
| // 		// fmt.Printf(">>>>> ---- double indirect reference: %v, %v\n", rvk, rv.Type()) | ||||
| // 		return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ})) | ||||
| // 	} | ||||
| // 	// fmt.Printf(">>>>> ++++ direct reference: %v, %v\n", rvk, rv.Type()) | ||||
| // 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ})) | ||||
| // } | ||||
|  | ||||
| // const ( | ||||
| // 	unsafeRvFlagKindMask    = 1<<5 - 1 | ||||
| // 	unsafeRvKindDirectIface = 1 << 5 | ||||
| // 	unsafeRvFlagIndir       = 1 << 7 | ||||
| // 	unsafeRvFlagAddr        = 1 << 8 | ||||
| // 	unsafeRvFlagMethod      = 1 << 9 | ||||
|  | ||||
| // 	_USE_RV_INTERFACE bool = false | ||||
| // 	_UNSAFE_RV_DEBUG       = true | ||||
| // ) | ||||
|  | ||||
| // type unsafeRtype struct { | ||||
| // 	_    [2]uintptr | ||||
| // 	_    uint32 | ||||
| // 	_    uint8 | ||||
| // 	_    uint8 | ||||
| // 	_    uint8 | ||||
| // 	kind uint8 | ||||
| // 	_    [2]uintptr | ||||
| // 	_    int32 | ||||
| // } | ||||
|  | ||||
| // func _rv2i(rv reflect.Value) interface{} { | ||||
| // 	// Note: From use, | ||||
| // 	//   - it's never an interface | ||||
| // 	//   - the only calls here are for ifaceIndir types. | ||||
| // 	//     (though that conditional is wrong) | ||||
| // 	//     To know for sure, we need the value of t.kind (which is not exposed). | ||||
| // 	// | ||||
| // 	// Need to validate the path: type is indirect ==> only value is indirect ==> default (value is direct) | ||||
| // 	//    - Type indirect, Value indirect: ==> numbers, boolean, slice, struct, array, string | ||||
| // 	//    - Type Direct,   Value indirect: ==> map??? | ||||
| // 	//    - Type Direct,   Value direct:   ==> pointers, unsafe.Pointer, func, chan, map | ||||
| // 	// | ||||
| // 	// TRANSLATES TO: | ||||
| // 	//    if typeIndirect { } else if valueIndirect { } else { } | ||||
| // 	// | ||||
| // 	// Since we don't deal with funcs, then "flagNethod" is unset, and can be ignored. | ||||
|  | ||||
| // 	if _USE_RV_INTERFACE { | ||||
| // 		return rv.Interface() | ||||
| // 	} | ||||
| // 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
|  | ||||
| // 	// if urv.flag&unsafeRvFlagMethod != 0 || urv.flag&unsafeRvFlagKindMask == uintptr(reflect.Interface) { | ||||
| // 	// 	println("***** IS flag method or interface: delegating to rv.Interface()") | ||||
| // 	// 	return rv.Interface() | ||||
| // 	// } | ||||
|  | ||||
| // 	// if urv.flag&unsafeRvFlagKindMask == uintptr(reflect.Interface) { | ||||
| // 	// 	println("***** IS Interface: delegate to rv.Interface") | ||||
| // 	// 	return rv.Interface() | ||||
| // 	// } | ||||
| // 	// if urv.flag&unsafeRvFlagKindMask&unsafeRvKindDirectIface == 0 { | ||||
| // 	// 	if urv.flag&unsafeRvFlagAddr == 0 { | ||||
| // 	// 		println("***** IS ifaceIndir typ") | ||||
| // 	// 		// ui := unsafeIntf{word: urv.ptr, typ: urv.typ} | ||||
| // 	// 		// return *(*interface{})(unsafe.Pointer(&ui)) | ||||
| // 	// 		// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ})) | ||||
| // 	// 	} | ||||
| // 	// } else if urv.flag&unsafeRvFlagIndir != 0 { | ||||
| // 	// 	println("***** IS flagindir") | ||||
| // 	// 	// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ})) | ||||
| // 	// } else { | ||||
| // 	// 	println("***** NOT flagindir") | ||||
| // 	// 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ})) | ||||
| // 	// } | ||||
| // 	// println("***** default: delegate to rv.Interface") | ||||
|  | ||||
| // 	urt := (*unsafeRtype)(unsafe.Pointer(urv.typ)) | ||||
| // 	if _UNSAFE_RV_DEBUG { | ||||
| // 		fmt.Printf(">>>> start: %v: ", rv.Type()) | ||||
| // 		fmt.Printf("%v - %v\n", *urv, *urt) | ||||
| // 	} | ||||
| // 	if urt.kind&unsafeRvKindDirectIface == 0 { | ||||
| // 		if _UNSAFE_RV_DEBUG { | ||||
| // 			fmt.Printf("**** +ifaceIndir type: %v\n", rv.Type()) | ||||
| // 		} | ||||
| // 		// println("***** IS ifaceIndir typ") | ||||
| // 		// if true || urv.flag&unsafeRvFlagAddr == 0 { | ||||
| // 		// 	// println("    ***** IS NOT addr") | ||||
| // 		return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ})) | ||||
| // 		// } | ||||
| // 	} else if urv.flag&unsafeRvFlagIndir != 0 { | ||||
| // 		if _UNSAFE_RV_DEBUG { | ||||
| // 			fmt.Printf("**** +flagIndir type: %v\n", rv.Type()) | ||||
| // 		} | ||||
| // 		// println("***** IS flagindir") | ||||
| // 		return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ})) | ||||
| // 	} else { | ||||
| // 		if _UNSAFE_RV_DEBUG { | ||||
| // 			fmt.Printf("**** -flagIndir type: %v\n", rv.Type()) | ||||
| // 		} | ||||
| // 		// println("***** NOT flagindir") | ||||
| // 		return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ})) | ||||
| // 	} | ||||
| // 	// println("***** default: delegating to rv.Interface()") | ||||
| // 	// return rv.Interface() | ||||
| // } | ||||
|  | ||||
| // var staticM0 = make(map[string]uint64) | ||||
| // var staticI0 = (int32)(-5) | ||||
|  | ||||
| // func staticRv2iTest() { | ||||
| // 	i0 := (int32)(-5) | ||||
| // 	m0 := make(map[string]uint16) | ||||
| // 	m0["1"] = 1 | ||||
| // 	for _, i := range []interface{}{ | ||||
| // 		(int)(7), | ||||
| // 		(uint)(8), | ||||
| // 		(int16)(-9), | ||||
| // 		(uint16)(19), | ||||
| // 		(uintptr)(77), | ||||
| // 		(bool)(true), | ||||
| // 		float32(-32.7), | ||||
| // 		float64(64.9), | ||||
| // 		complex(float32(19), 5), | ||||
| // 		complex(float64(-32), 7), | ||||
| // 		[4]uint64{1, 2, 3, 4}, | ||||
| // 		(chan<- int)(nil), // chan, | ||||
| // 		rv2i,              // func | ||||
| // 		io.Writer(ioutil.Discard), | ||||
| // 		make(map[string]uint), | ||||
| // 		(map[string]uint)(nil), | ||||
| // 		staticM0, | ||||
| // 		m0, | ||||
| // 		&m0, | ||||
| // 		i0, | ||||
| // 		&i0, | ||||
| // 		&staticI0, | ||||
| // 		&staticM0, | ||||
| // 		[]uint32{6, 7, 8}, | ||||
| // 		"abc", | ||||
| // 		Raw{}, | ||||
| // 		RawExt{}, | ||||
| // 		&Raw{}, | ||||
| // 		&RawExt{}, | ||||
| // 		unsafe.Pointer(&i0), | ||||
| // 	} { | ||||
| // 		i2 := rv2i(reflect.ValueOf(i)) | ||||
| // 		eq := reflect.DeepEqual(i, i2) | ||||
| // 		fmt.Printf(">>>> %v == %v? %v\n", i, i2, eq) | ||||
| // 	} | ||||
| // 	// os.Exit(0) | ||||
| // } | ||||
|  | ||||
| // func init() { | ||||
| // 	staticRv2iTest() | ||||
| // } | ||||
|  | ||||
| // func rv2i(rv reflect.Value) interface{} { | ||||
| // 	if _USE_RV_INTERFACE || rv.Kind() == reflect.Interface || rv.CanAddr() { | ||||
| // 		return rv.Interface() | ||||
| // 	} | ||||
| // 	// var i interface{} | ||||
| // 	// ui := (*unsafeIntf)(unsafe.Pointer(&i)) | ||||
| // 	var ui unsafeIntf | ||||
| // 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv)) | ||||
| // 	// fmt.Printf("urv: flag: %b, typ: %b, ptr: %b\n", urv.flag, uintptr(urv.typ), uintptr(urv.ptr)) | ||||
| // 	if (urv.flag&unsafeRvFlagKindMask)&unsafeRvKindDirectIface == 0 { | ||||
| // 		if urv.flag&unsafeRvFlagAddr != 0 { | ||||
| // 			println("***** indirect and addressable! Needs typed move - delegate to rv.Interface()") | ||||
| // 			return rv.Interface() | ||||
| // 		} | ||||
| // 		println("****** indirect type/kind") | ||||
| // 		ui.word = urv.ptr | ||||
| // 	} else if urv.flag&unsafeRvFlagIndir != 0 { | ||||
| // 		println("****** unsafe rv flag indir") | ||||
| // 		ui.word = *(*unsafe.Pointer)(urv.ptr) | ||||
| // 	} else { | ||||
| // 		println("****** default: assign prt to word directly") | ||||
| // 		ui.word = urv.ptr | ||||
| // 	} | ||||
| // 	// ui.word = urv.ptr | ||||
| // 	ui.typ = urv.typ | ||||
| // 	// fmt.Printf("(pointers) ui.typ: %p, word: %p\n", ui.typ, ui.word) | ||||
| // 	// fmt.Printf("(binary)   ui.typ: %b, word: %b\n", uintptr(ui.typ), uintptr(ui.word)) | ||||
| // 	return *(*interface{})(unsafe.Pointer(&ui)) | ||||
| // 	// return i | ||||
| // } | ||||
							
								
								
									
										1172
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/json.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1172
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/json.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										899
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/msgpack.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										899
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/msgpack.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,899 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| /* | ||||
| MSGPACK | ||||
|  | ||||
| Msgpack-c implementation powers the c, c++, python, ruby, etc libraries. | ||||
| We need to maintain compatibility with it and how it encodes integer values | ||||
| without caring about the type. | ||||
|  | ||||
| For compatibility with behaviour of msgpack-c reference implementation: | ||||
|   - Go intX (>0) and uintX | ||||
|        IS ENCODED AS | ||||
|     msgpack +ve fixnum, unsigned | ||||
|   - Go intX (<0) | ||||
|        IS ENCODED AS | ||||
|     msgpack -ve fixnum, signed | ||||
|  | ||||
| */ | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math" | ||||
| 	"net/rpc" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	mpPosFixNumMin byte = 0x00 | ||||
| 	mpPosFixNumMax      = 0x7f | ||||
| 	mpFixMapMin         = 0x80 | ||||
| 	mpFixMapMax         = 0x8f | ||||
| 	mpFixArrayMin       = 0x90 | ||||
| 	mpFixArrayMax       = 0x9f | ||||
| 	mpFixStrMin         = 0xa0 | ||||
| 	mpFixStrMax         = 0xbf | ||||
| 	mpNil               = 0xc0 | ||||
| 	_                   = 0xc1 | ||||
| 	mpFalse             = 0xc2 | ||||
| 	mpTrue              = 0xc3 | ||||
| 	mpFloat             = 0xca | ||||
| 	mpDouble            = 0xcb | ||||
| 	mpUint8             = 0xcc | ||||
| 	mpUint16            = 0xcd | ||||
| 	mpUint32            = 0xce | ||||
| 	mpUint64            = 0xcf | ||||
| 	mpInt8              = 0xd0 | ||||
| 	mpInt16             = 0xd1 | ||||
| 	mpInt32             = 0xd2 | ||||
| 	mpInt64             = 0xd3 | ||||
|  | ||||
| 	// extensions below | ||||
| 	mpBin8     = 0xc4 | ||||
| 	mpBin16    = 0xc5 | ||||
| 	mpBin32    = 0xc6 | ||||
| 	mpExt8     = 0xc7 | ||||
| 	mpExt16    = 0xc8 | ||||
| 	mpExt32    = 0xc9 | ||||
| 	mpFixExt1  = 0xd4 | ||||
| 	mpFixExt2  = 0xd5 | ||||
| 	mpFixExt4  = 0xd6 | ||||
| 	mpFixExt8  = 0xd7 | ||||
| 	mpFixExt16 = 0xd8 | ||||
|  | ||||
| 	mpStr8  = 0xd9 // new | ||||
| 	mpStr16 = 0xda | ||||
| 	mpStr32 = 0xdb | ||||
|  | ||||
| 	mpArray16 = 0xdc | ||||
| 	mpArray32 = 0xdd | ||||
|  | ||||
| 	mpMap16 = 0xde | ||||
| 	mpMap32 = 0xdf | ||||
|  | ||||
| 	mpNegFixNumMin = 0xe0 | ||||
| 	mpNegFixNumMax = 0xff | ||||
| ) | ||||
|  | ||||
| // MsgpackSpecRpcMultiArgs is a special type which signifies to the MsgpackSpecRpcCodec | ||||
| // that the backend RPC service takes multiple arguments, which have been arranged | ||||
| // in sequence in the slice. | ||||
| // | ||||
| // The Codec then passes it AS-IS to the rpc service (without wrapping it in an | ||||
| // array of 1 element). | ||||
| type MsgpackSpecRpcMultiArgs []interface{} | ||||
|  | ||||
| // A MsgpackContainer type specifies the different types of msgpackContainers. | ||||
| type msgpackContainerType struct { | ||||
| 	fixCutoff                   int | ||||
| 	bFixMin, b8, b16, b32       byte | ||||
| 	hasFixMin, has8, has8Always bool | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	msgpackContainerStr  = msgpackContainerType{32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, false} | ||||
| 	msgpackContainerBin  = msgpackContainerType{0, 0, mpBin8, mpBin16, mpBin32, false, true, true} | ||||
| 	msgpackContainerList = msgpackContainerType{16, mpFixArrayMin, 0, mpArray16, mpArray32, true, false, false} | ||||
| 	msgpackContainerMap  = msgpackContainerType{16, mpFixMapMin, 0, mpMap16, mpMap32, true, false, false} | ||||
| ) | ||||
|  | ||||
| //--------------------------------------------- | ||||
|  | ||||
| type msgpackEncDriver struct { | ||||
| 	noBuiltInTypes | ||||
| 	encDriverNoopContainerWriter | ||||
| 	// encNoSeparator | ||||
| 	e *Encoder | ||||
| 	w encWriter | ||||
| 	h *MsgpackHandle | ||||
| 	x [8]byte | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeNil() { | ||||
| 	e.w.writen1(mpNil) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeInt(i int64) { | ||||
| 	if i >= 0 { | ||||
| 		e.EncodeUint(uint64(i)) | ||||
| 	} else if i >= -32 { | ||||
| 		e.w.writen1(byte(i)) | ||||
| 	} else if i >= math.MinInt8 { | ||||
| 		e.w.writen2(mpInt8, byte(i)) | ||||
| 	} else if i >= math.MinInt16 { | ||||
| 		e.w.writen1(mpInt16) | ||||
| 		bigenHelper{e.x[:2], e.w}.writeUint16(uint16(i)) | ||||
| 	} else if i >= math.MinInt32 { | ||||
| 		e.w.writen1(mpInt32) | ||||
| 		bigenHelper{e.x[:4], e.w}.writeUint32(uint32(i)) | ||||
| 	} else { | ||||
| 		e.w.writen1(mpInt64) | ||||
| 		bigenHelper{e.x[:8], e.w}.writeUint64(uint64(i)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeUint(i uint64) { | ||||
| 	if i <= math.MaxInt8 { | ||||
| 		e.w.writen1(byte(i)) | ||||
| 	} else if i <= math.MaxUint8 { | ||||
| 		e.w.writen2(mpUint8, byte(i)) | ||||
| 	} else if i <= math.MaxUint16 { | ||||
| 		e.w.writen1(mpUint16) | ||||
| 		bigenHelper{e.x[:2], e.w}.writeUint16(uint16(i)) | ||||
| 	} else if i <= math.MaxUint32 { | ||||
| 		e.w.writen1(mpUint32) | ||||
| 		bigenHelper{e.x[:4], e.w}.writeUint32(uint32(i)) | ||||
| 	} else { | ||||
| 		e.w.writen1(mpUint64) | ||||
| 		bigenHelper{e.x[:8], e.w}.writeUint64(uint64(i)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeBool(b bool) { | ||||
| 	if b { | ||||
| 		e.w.writen1(mpTrue) | ||||
| 	} else { | ||||
| 		e.w.writen1(mpFalse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeFloat32(f float32) { | ||||
| 	e.w.writen1(mpFloat) | ||||
| 	bigenHelper{e.x[:4], e.w}.writeUint32(math.Float32bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeFloat64(f float64) { | ||||
| 	e.w.writen1(mpDouble) | ||||
| 	bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeExt(v interface{}, xtag uint64, ext Ext, _ *Encoder) { | ||||
| 	bs := ext.WriteExt(v) | ||||
| 	if bs == nil { | ||||
| 		e.EncodeNil() | ||||
| 		return | ||||
| 	} | ||||
| 	if e.h.WriteExt { | ||||
| 		e.encodeExtPreamble(uint8(xtag), len(bs)) | ||||
| 		e.w.writeb(bs) | ||||
| 	} else { | ||||
| 		e.EncodeStringBytes(c_RAW, bs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) { | ||||
| 	e.encodeExtPreamble(uint8(re.Tag), len(re.Data)) | ||||
| 	e.w.writeb(re.Data) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) encodeExtPreamble(xtag byte, l int) { | ||||
| 	if l == 1 { | ||||
| 		e.w.writen2(mpFixExt1, xtag) | ||||
| 	} else if l == 2 { | ||||
| 		e.w.writen2(mpFixExt2, xtag) | ||||
| 	} else if l == 4 { | ||||
| 		e.w.writen2(mpFixExt4, xtag) | ||||
| 	} else if l == 8 { | ||||
| 		e.w.writen2(mpFixExt8, xtag) | ||||
| 	} else if l == 16 { | ||||
| 		e.w.writen2(mpFixExt16, xtag) | ||||
| 	} else if l < 256 { | ||||
| 		e.w.writen2(mpExt8, byte(l)) | ||||
| 		e.w.writen1(xtag) | ||||
| 	} else if l < 65536 { | ||||
| 		e.w.writen1(mpExt16) | ||||
| 		bigenHelper{e.x[:2], e.w}.writeUint16(uint16(l)) | ||||
| 		e.w.writen1(xtag) | ||||
| 	} else { | ||||
| 		e.w.writen1(mpExt32) | ||||
| 		bigenHelper{e.x[:4], e.w}.writeUint32(uint32(l)) | ||||
| 		e.w.writen1(xtag) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) WriteArrayStart(length int) { | ||||
| 	e.writeContainerLen(msgpackContainerList, length) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) WriteMapStart(length int) { | ||||
| 	e.writeContainerLen(msgpackContainerMap, length) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeString(c charEncoding, s string) { | ||||
| 	slen := len(s) | ||||
| 	if c == c_RAW && e.h.WriteExt { | ||||
| 		e.writeContainerLen(msgpackContainerBin, slen) | ||||
| 	} else { | ||||
| 		e.writeContainerLen(msgpackContainerStr, slen) | ||||
| 	} | ||||
| 	if slen > 0 { | ||||
| 		e.w.writestr(s) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeSymbol(v string) { | ||||
| 	e.EncodeString(c_UTF8, v) | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) EncodeStringBytes(c charEncoding, bs []byte) { | ||||
| 	slen := len(bs) | ||||
| 	if c == c_RAW && e.h.WriteExt { | ||||
| 		e.writeContainerLen(msgpackContainerBin, slen) | ||||
| 	} else { | ||||
| 		e.writeContainerLen(msgpackContainerStr, slen) | ||||
| 	} | ||||
| 	if slen > 0 { | ||||
| 		e.w.writeb(bs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) writeContainerLen(ct msgpackContainerType, l int) { | ||||
| 	if ct.hasFixMin && l < ct.fixCutoff { | ||||
| 		e.w.writen1(ct.bFixMin | byte(l)) | ||||
| 	} else if ct.has8 && l < 256 && (ct.has8Always || e.h.WriteExt) { | ||||
| 		e.w.writen2(ct.b8, uint8(l)) | ||||
| 	} else if l < 65536 { | ||||
| 		e.w.writen1(ct.b16) | ||||
| 		bigenHelper{e.x[:2], e.w}.writeUint16(uint16(l)) | ||||
| 	} else { | ||||
| 		e.w.writen1(ct.b32) | ||||
| 		bigenHelper{e.x[:4], e.w}.writeUint32(uint32(l)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| //--------------------------------------------- | ||||
|  | ||||
| type msgpackDecDriver struct { | ||||
| 	d      *Decoder | ||||
| 	r      decReader // *Decoder decReader decReaderT | ||||
| 	h      *MsgpackHandle | ||||
| 	b      [scratchByteArrayLen]byte | ||||
| 	bd     byte | ||||
| 	bdRead bool | ||||
| 	br     bool // bytes reader | ||||
| 	noBuiltInTypes | ||||
| 	// noStreamingCodec | ||||
| 	// decNoSeparator | ||||
| 	decDriverNoopContainerReader | ||||
| } | ||||
|  | ||||
| // Note: This returns either a primitive (int, bool, etc) for non-containers, | ||||
| // or a containerType, or a specific type denoting nil or extension. | ||||
| // It is called when a nil interface{} is passed, leaving it up to the DecDriver | ||||
| // to introspect the stream and decide how best to decode. | ||||
| // It deciphers the value by looking at the stream first. | ||||
| func (d *msgpackDecDriver) DecodeNaked() { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	bd := d.bd | ||||
| 	n := d.d.n | ||||
| 	var decodeFurther bool | ||||
|  | ||||
| 	switch bd { | ||||
| 	case mpNil: | ||||
| 		n.v = valueTypeNil | ||||
| 		d.bdRead = false | ||||
| 	case mpFalse: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = false | ||||
| 	case mpTrue: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = true | ||||
|  | ||||
| 	case mpFloat: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4)))) | ||||
| 	case mpDouble: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) | ||||
|  | ||||
| 	case mpUint8: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = uint64(d.r.readn1()) | ||||
| 	case mpUint16: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = uint64(bigen.Uint16(d.r.readx(2))) | ||||
| 	case mpUint32: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 	case mpUint64: | ||||
| 		n.v = valueTypeUint | ||||
| 		n.u = uint64(bigen.Uint64(d.r.readx(8))) | ||||
|  | ||||
| 	case mpInt8: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(int8(d.r.readn1())) | ||||
| 	case mpInt16: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(int16(bigen.Uint16(d.r.readx(2)))) | ||||
| 	case mpInt32: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(int32(bigen.Uint32(d.r.readx(4)))) | ||||
| 	case mpInt64: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(int64(bigen.Uint64(d.r.readx(8)))) | ||||
|  | ||||
| 	default: | ||||
| 		switch { | ||||
| 		case bd >= mpPosFixNumMin && bd <= mpPosFixNumMax: | ||||
| 			// positive fixnum (always signed) | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = int64(int8(bd)) | ||||
| 		case bd >= mpNegFixNumMin && bd <= mpNegFixNumMax: | ||||
| 			// negative fixnum | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = int64(int8(bd)) | ||||
| 		case bd == mpStr8, bd == mpStr16, bd == mpStr32, bd >= mpFixStrMin && bd <= mpFixStrMax: | ||||
| 			if d.h.RawToString { | ||||
| 				n.v = valueTypeString | ||||
| 				n.s = d.DecodeString() | ||||
| 			} else { | ||||
| 				n.v = valueTypeBytes | ||||
| 				n.l = d.DecodeBytes(nil, false) | ||||
| 			} | ||||
| 		case bd == mpBin8, bd == mpBin16, bd == mpBin32: | ||||
| 			n.v = valueTypeBytes | ||||
| 			n.l = d.DecodeBytes(nil, false) | ||||
| 		case bd == mpArray16, bd == mpArray32, bd >= mpFixArrayMin && bd <= mpFixArrayMax: | ||||
| 			n.v = valueTypeArray | ||||
| 			decodeFurther = true | ||||
| 		case bd == mpMap16, bd == mpMap32, bd >= mpFixMapMin && bd <= mpFixMapMax: | ||||
| 			n.v = valueTypeMap | ||||
| 			decodeFurther = true | ||||
| 		case bd >= mpFixExt1 && bd <= mpFixExt16, bd >= mpExt8 && bd <= mpExt32: | ||||
| 			n.v = valueTypeExt | ||||
| 			clen := d.readExtLen() | ||||
| 			n.u = uint64(d.r.readn1()) | ||||
| 			n.l = d.r.readx(clen) | ||||
| 		default: | ||||
| 			d.d.errorf("Nil-Deciphered DecodeValue: %s: hex: %x, dec: %d", msgBadDesc, bd, bd) | ||||
| 		} | ||||
| 	} | ||||
| 	if !decodeFurther { | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	if n.v == valueTypeUint && d.h.SignedInteger { | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = int64(n.u) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // int can be decoded from msgpack type: intXXX or uintXXX | ||||
| func (d *msgpackDecDriver) DecodeInt(bitsize uint8) (i int64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	switch d.bd { | ||||
| 	case mpUint8: | ||||
| 		i = int64(uint64(d.r.readn1())) | ||||
| 	case mpUint16: | ||||
| 		i = int64(uint64(bigen.Uint16(d.r.readx(2)))) | ||||
| 	case mpUint32: | ||||
| 		i = int64(uint64(bigen.Uint32(d.r.readx(4)))) | ||||
| 	case mpUint64: | ||||
| 		i = int64(bigen.Uint64(d.r.readx(8))) | ||||
| 	case mpInt8: | ||||
| 		i = int64(int8(d.r.readn1())) | ||||
| 	case mpInt16: | ||||
| 		i = int64(int16(bigen.Uint16(d.r.readx(2)))) | ||||
| 	case mpInt32: | ||||
| 		i = int64(int32(bigen.Uint32(d.r.readx(4)))) | ||||
| 	case mpInt64: | ||||
| 		i = int64(bigen.Uint64(d.r.readx(8))) | ||||
| 	default: | ||||
| 		switch { | ||||
| 		case d.bd >= mpPosFixNumMin && d.bd <= mpPosFixNumMax: | ||||
| 			i = int64(int8(d.bd)) | ||||
| 		case d.bd >= mpNegFixNumMin && d.bd <= mpNegFixNumMax: | ||||
| 			i = int64(int8(d.bd)) | ||||
| 		default: | ||||
| 			d.d.errorf("Unhandled single-byte unsigned integer value: %s: %x", msgBadDesc, d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	// check overflow (logic adapted from std pkg reflect/value.go OverflowUint() | ||||
| 	if bitsize > 0 { | ||||
| 		if trunc := (i << (64 - bitsize)) >> (64 - bitsize); i != trunc { | ||||
| 			d.d.errorf("Overflow int value: %v", i) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // uint can be decoded from msgpack type: intXXX or uintXXX | ||||
| func (d *msgpackDecDriver) DecodeUint(bitsize uint8) (ui uint64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	switch d.bd { | ||||
| 	case mpUint8: | ||||
| 		ui = uint64(d.r.readn1()) | ||||
| 	case mpUint16: | ||||
| 		ui = uint64(bigen.Uint16(d.r.readx(2))) | ||||
| 	case mpUint32: | ||||
| 		ui = uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 	case mpUint64: | ||||
| 		ui = bigen.Uint64(d.r.readx(8)) | ||||
| 	case mpInt8: | ||||
| 		if i := int64(int8(d.r.readn1())); i >= 0 { | ||||
| 			ui = uint64(i) | ||||
| 		} else { | ||||
| 			d.d.errorf("Assigning negative signed value: %v, to unsigned type", i) | ||||
| 			return | ||||
| 		} | ||||
| 	case mpInt16: | ||||
| 		if i := int64(int16(bigen.Uint16(d.r.readx(2)))); i >= 0 { | ||||
| 			ui = uint64(i) | ||||
| 		} else { | ||||
| 			d.d.errorf("Assigning negative signed value: %v, to unsigned type", i) | ||||
| 			return | ||||
| 		} | ||||
| 	case mpInt32: | ||||
| 		if i := int64(int32(bigen.Uint32(d.r.readx(4)))); i >= 0 { | ||||
| 			ui = uint64(i) | ||||
| 		} else { | ||||
| 			d.d.errorf("Assigning negative signed value: %v, to unsigned type", i) | ||||
| 			return | ||||
| 		} | ||||
| 	case mpInt64: | ||||
| 		if i := int64(bigen.Uint64(d.r.readx(8))); i >= 0 { | ||||
| 			ui = uint64(i) | ||||
| 		} else { | ||||
| 			d.d.errorf("Assigning negative signed value: %v, to unsigned type", i) | ||||
| 			return | ||||
| 		} | ||||
| 	default: | ||||
| 		switch { | ||||
| 		case d.bd >= mpPosFixNumMin && d.bd <= mpPosFixNumMax: | ||||
| 			ui = uint64(d.bd) | ||||
| 		case d.bd >= mpNegFixNumMin && d.bd <= mpNegFixNumMax: | ||||
| 			d.d.errorf("Assigning negative signed value: %v, to unsigned type", int(d.bd)) | ||||
| 			return | ||||
| 		default: | ||||
| 			d.d.errorf("Unhandled single-byte unsigned integer value: %s: %x", msgBadDesc, d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	// check overflow (logic adapted from std pkg reflect/value.go OverflowUint() | ||||
| 	if bitsize > 0 { | ||||
| 		if trunc := (ui << (64 - bitsize)) >> (64 - bitsize); ui != trunc { | ||||
| 			d.d.errorf("Overflow uint value: %v", ui) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // float can either be decoded from msgpack type: float, double or intX | ||||
| func (d *msgpackDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == mpFloat { | ||||
| 		f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4)))) | ||||
| 	} else if d.bd == mpDouble { | ||||
| 		f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) | ||||
| 	} else { | ||||
| 		f = float64(d.DecodeInt(0)) | ||||
| 	} | ||||
| 	if chkOverflow32 && chkOvf.Float32(f) { | ||||
| 		d.d.errorf("msgpack: float32 overflow: %v", f) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // bool can be decoded from bool, fixnum 0 or 1. | ||||
| func (d *msgpackDecDriver) DecodeBool() (b bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == mpFalse || d.bd == 0 { | ||||
| 		// b = false | ||||
| 	} else if d.bd == mpTrue || d.bd == 1 { | ||||
| 		b = true | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
|  | ||||
| 	// DecodeBytes could be from: bin str fixstr fixarray array ... | ||||
| 	var clen int | ||||
| 	vt := d.ContainerType() | ||||
| 	switch vt { | ||||
| 	case valueTypeBytes: | ||||
| 		// valueTypeBytes may be a mpBin or an mpStr container | ||||
| 		if bd := d.bd; bd == mpBin8 || bd == mpBin16 || bd == mpBin32 { | ||||
| 			clen = d.readContainerLen(msgpackContainerBin) | ||||
| 		} else { | ||||
| 			clen = d.readContainerLen(msgpackContainerStr) | ||||
| 		} | ||||
| 	case valueTypeString: | ||||
| 		clen = d.readContainerLen(msgpackContainerStr) | ||||
| 	case valueTypeArray: | ||||
| 		clen = d.readContainerLen(msgpackContainerList) | ||||
| 		// ensure everything after is one byte each | ||||
| 		for i := 0; i < clen; i++ { | ||||
| 			d.readNextBd() | ||||
| 			if d.bd == mpNil { | ||||
| 				bs = append(bs, 0) | ||||
| 			} else if d.bd == mpUint8 { | ||||
| 				bs = append(bs, d.r.readn1()) | ||||
| 			} else { | ||||
| 				d.d.errorf("cannot read non-byte into a byte array") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		d.bdRead = false | ||||
| 		return bs | ||||
| 	default: | ||||
| 		d.d.errorf("invalid container type: expecting bin|str|array") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// these are (bin|str)(8|16|32) | ||||
| 	// println("DecodeBytes: clen: ", clen) | ||||
| 	d.bdRead = false | ||||
| 	// bytes may be nil, so handle it. if nil, clen=-1. | ||||
| 	if clen < 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if zerocopy { | ||||
| 		if d.br { | ||||
| 			return d.r.readx(clen) | ||||
| 		} else if len(bs) == 0 { | ||||
| 			bs = d.b[:] | ||||
| 		} | ||||
| 	} | ||||
| 	return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) DecodeString() (s string) { | ||||
| 	return string(d.DecodeBytes(d.b[:], true)) | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) DecodeStringAsBytes() (s []byte) { | ||||
| 	return d.DecodeBytes(d.b[:], true) | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) readNextBd() { | ||||
| 	d.bd = d.r.readn1() | ||||
| 	d.bdRead = true | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) uncacheRead() { | ||||
| 	if d.bdRead { | ||||
| 		d.r.unreadn1() | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) ContainerType() (vt valueType) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	bd := d.bd | ||||
| 	if bd == mpNil { | ||||
| 		return valueTypeNil | ||||
| 	} else if bd == mpBin8 || bd == mpBin16 || bd == mpBin32 || | ||||
| 		(!d.h.RawToString && | ||||
| 			(bd == mpStr8 || bd == mpStr16 || bd == mpStr32 || (bd >= mpFixStrMin && bd <= mpFixStrMax))) { | ||||
| 		return valueTypeBytes | ||||
| 	} else if d.h.RawToString && | ||||
| 		(bd == mpStr8 || bd == mpStr16 || bd == mpStr32 || (bd >= mpFixStrMin && bd <= mpFixStrMax)) { | ||||
| 		return valueTypeString | ||||
| 	} else if bd == mpArray16 || bd == mpArray32 || (bd >= mpFixArrayMin && bd <= mpFixArrayMax) { | ||||
| 		return valueTypeArray | ||||
| 	} else if bd == mpMap16 || bd == mpMap32 || (bd >= mpFixMapMin && bd <= mpFixMapMax) { | ||||
| 		return valueTypeMap | ||||
| 	} else { | ||||
| 		// d.d.errorf("isContainerType: unsupported parameter: %v", vt) | ||||
| 	} | ||||
| 	return valueTypeUnset | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) TryDecodeAsNil() (v bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == mpNil { | ||||
| 		d.bdRead = false | ||||
| 		v = true | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) readContainerLen(ct msgpackContainerType) (clen int) { | ||||
| 	bd := d.bd | ||||
| 	if bd == mpNil { | ||||
| 		clen = -1 // to represent nil | ||||
| 	} else if bd == ct.b8 { | ||||
| 		clen = int(d.r.readn1()) | ||||
| 	} else if bd == ct.b16 { | ||||
| 		clen = int(bigen.Uint16(d.r.readx(2))) | ||||
| 	} else if bd == ct.b32 { | ||||
| 		clen = int(bigen.Uint32(d.r.readx(4))) | ||||
| 	} else if (ct.bFixMin & bd) == ct.bFixMin { | ||||
| 		clen = int(ct.bFixMin ^ bd) | ||||
| 	} else { | ||||
| 		d.d.errorf("readContainerLen: %s: hex: %x, decimal: %d", msgBadDesc, bd, bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) ReadMapStart() int { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	return d.readContainerLen(msgpackContainerMap) | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) ReadArrayStart() int { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	return d.readContainerLen(msgpackContainerList) | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) readExtLen() (clen int) { | ||||
| 	switch d.bd { | ||||
| 	case mpNil: | ||||
| 		clen = -1 // to represent nil | ||||
| 	case mpFixExt1: | ||||
| 		clen = 1 | ||||
| 	case mpFixExt2: | ||||
| 		clen = 2 | ||||
| 	case mpFixExt4: | ||||
| 		clen = 4 | ||||
| 	case mpFixExt8: | ||||
| 		clen = 8 | ||||
| 	case mpFixExt16: | ||||
| 		clen = 16 | ||||
| 	case mpExt8: | ||||
| 		clen = int(d.r.readn1()) | ||||
| 	case mpExt16: | ||||
| 		clen = int(bigen.Uint16(d.r.readx(2))) | ||||
| 	case mpExt32: | ||||
| 		clen = int(bigen.Uint32(d.r.readx(4))) | ||||
| 	default: | ||||
| 		d.d.errorf("decoding ext bytes: found unexpected byte: %x", d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { | ||||
| 	if xtag > 0xff { | ||||
| 		d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag) | ||||
| 		return | ||||
| 	} | ||||
| 	realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag)) | ||||
| 	realxtag = uint64(realxtag1) | ||||
| 	if ext == nil { | ||||
| 		re := rv.(*RawExt) | ||||
| 		re.Tag = realxtag | ||||
| 		re.Data = detachZeroCopyBytes(d.br, re.Data, xbs) | ||||
| 	} else { | ||||
| 		ext.ReadExt(rv, xbs) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	xbd := d.bd | ||||
| 	if xbd == mpBin8 || xbd == mpBin16 || xbd == mpBin32 { | ||||
| 		xbs = d.DecodeBytes(nil, true) | ||||
| 	} else if xbd == mpStr8 || xbd == mpStr16 || xbd == mpStr32 || | ||||
| 		(xbd >= mpFixStrMin && xbd <= mpFixStrMax) { | ||||
| 		xbs = d.DecodeStringAsBytes() | ||||
| 	} else { | ||||
| 		clen := d.readExtLen() | ||||
| 		xtag = d.r.readn1() | ||||
| 		if verifyTag && xtag != tag { | ||||
| 			d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag) | ||||
| 			return | ||||
| 		} | ||||
| 		xbs = d.r.readx(clen) | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| //-------------------------------------------------- | ||||
|  | ||||
| //MsgpackHandle is a Handle for the Msgpack Schema-Free Encoding Format. | ||||
| type MsgpackHandle struct { | ||||
| 	BasicHandle | ||||
|  | ||||
| 	// RawToString controls how raw bytes are decoded into a nil interface{}. | ||||
| 	RawToString bool | ||||
|  | ||||
| 	// WriteExt flag supports encoding configured extensions with extension tags. | ||||
| 	// It also controls whether other elements of the new spec are encoded (ie Str8). | ||||
| 	// | ||||
| 	// With WriteExt=false, configured extensions are serialized as raw bytes | ||||
| 	// and Str8 is not encoded. | ||||
| 	// | ||||
| 	// A stream can still be decoded into a typed value, provided an appropriate value | ||||
| 	// is provided, but the type cannot be inferred from the stream. If no appropriate | ||||
| 	// type is provided (e.g. decoding into a nil interface{}), you get back | ||||
| 	// a []byte or string based on the setting of RawToString. | ||||
| 	WriteExt bool | ||||
| 	binaryEncodingType | ||||
| 	noElemSeparators | ||||
| } | ||||
|  | ||||
| func (h *MsgpackHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { | ||||
| 	return h.SetExt(rt, tag, &setExtWrapper{b: ext}) | ||||
| } | ||||
|  | ||||
| func (h *MsgpackHandle) newEncDriver(e *Encoder) encDriver { | ||||
| 	return &msgpackEncDriver{e: e, w: e.w, h: h} | ||||
| } | ||||
|  | ||||
| func (h *MsgpackHandle) newDecDriver(d *Decoder) decDriver { | ||||
| 	return &msgpackDecDriver{d: d, h: h, r: d.r, br: d.bytes} | ||||
| } | ||||
|  | ||||
| func (e *msgpackEncDriver) reset() { | ||||
| 	e.w = e.e.w | ||||
| } | ||||
|  | ||||
| func (d *msgpackDecDriver) reset() { | ||||
| 	d.r, d.br = d.d.r, d.d.bytes | ||||
| 	d.bd, d.bdRead = 0, false | ||||
| } | ||||
|  | ||||
| //-------------------------------------------------- | ||||
|  | ||||
| type msgpackSpecRpcCodec struct { | ||||
| 	rpcCodec | ||||
| } | ||||
|  | ||||
| // /////////////// Spec RPC Codec /////////////////// | ||||
| func (c *msgpackSpecRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error { | ||||
| 	// WriteRequest can write to both a Go service, and other services that do | ||||
| 	// not abide by the 1 argument rule of a Go service. | ||||
| 	// We discriminate based on if the body is a MsgpackSpecRpcMultiArgs | ||||
| 	var bodyArr []interface{} | ||||
| 	if m, ok := body.(MsgpackSpecRpcMultiArgs); ok { | ||||
| 		bodyArr = ([]interface{})(m) | ||||
| 	} else { | ||||
| 		bodyArr = []interface{}{body} | ||||
| 	} | ||||
| 	r2 := []interface{}{0, uint32(r.Seq), r.ServiceMethod, bodyArr} | ||||
| 	return c.write(r2, nil, false, true) | ||||
| } | ||||
|  | ||||
| func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error { | ||||
| 	var moe interface{} | ||||
| 	if r.Error != "" { | ||||
| 		moe = r.Error | ||||
| 	} | ||||
| 	if moe != nil && body != nil { | ||||
| 		body = nil | ||||
| 	} | ||||
| 	r2 := []interface{}{1, uint32(r.Seq), moe, body} | ||||
| 	return c.write(r2, nil, false, true) | ||||
| } | ||||
|  | ||||
| func (c *msgpackSpecRpcCodec) ReadResponseHeader(r *rpc.Response) error { | ||||
| 	return c.parseCustomHeader(1, &r.Seq, &r.Error) | ||||
| } | ||||
|  | ||||
| func (c *msgpackSpecRpcCodec) ReadRequestHeader(r *rpc.Request) error { | ||||
| 	return c.parseCustomHeader(0, &r.Seq, &r.ServiceMethod) | ||||
| } | ||||
|  | ||||
| func (c *msgpackSpecRpcCodec) ReadRequestBody(body interface{}) error { | ||||
| 	if body == nil { // read and discard | ||||
| 		return c.read(nil) | ||||
| 	} | ||||
| 	bodyArr := []interface{}{body} | ||||
| 	return c.read(&bodyArr) | ||||
| } | ||||
|  | ||||
| func (c *msgpackSpecRpcCodec) parseCustomHeader(expectTypeByte byte, msgid *uint64, methodOrError *string) (err error) { | ||||
|  | ||||
| 	if c.isClosed() { | ||||
| 		return io.EOF | ||||
| 	} | ||||
|  | ||||
| 	// We read the response header by hand | ||||
| 	// so that the body can be decoded on its own from the stream at a later time. | ||||
|  | ||||
| 	const fia byte = 0x94 //four item array descriptor value | ||||
| 	// Not sure why the panic of EOF is swallowed above. | ||||
| 	// if bs1 := c.dec.r.readn1(); bs1 != fia { | ||||
| 	// 	err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, bs1) | ||||
| 	// 	return | ||||
| 	// } | ||||
| 	var b byte | ||||
| 	b, err = c.br.ReadByte() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if b != fia { | ||||
| 		err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, b) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err = c.read(&b); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if b != expectTypeByte { | ||||
| 		err = fmt.Errorf("Unexpected byte descriptor in header. Expecting %v. Received %v", expectTypeByte, b) | ||||
| 		return | ||||
| 	} | ||||
| 	if err = c.read(msgid); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = c.read(methodOrError); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| //-------------------------------------------------- | ||||
|  | ||||
| // msgpackSpecRpc is the implementation of Rpc that uses custom communication protocol | ||||
| // as defined in the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md | ||||
| type msgpackSpecRpc struct{} | ||||
|  | ||||
| // MsgpackSpecRpc implements Rpc using the communication protocol defined in | ||||
| // the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md . | ||||
| // Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered. | ||||
| var MsgpackSpecRpc msgpackSpecRpc | ||||
|  | ||||
| func (x msgpackSpecRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec { | ||||
| 	return &msgpackSpecRpcCodec{newRPCCodec(conn, h)} | ||||
| } | ||||
|  | ||||
| func (x msgpackSpecRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec { | ||||
| 	return &msgpackSpecRpcCodec{newRPCCodec(conn, h)} | ||||
| } | ||||
|  | ||||
| var _ decDriver = (*msgpackDecDriver)(nil) | ||||
| var _ encDriver = (*msgpackEncDriver)(nil) | ||||
							
								
								
									
										214
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/noop.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/noop.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| // +build ignore | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // NoopHandle returns a no-op handle. It basically does nothing. | ||||
| // It is only useful for benchmarking, as it gives an idea of the | ||||
| // overhead from the codec framework. | ||||
| // | ||||
| // LIBRARY USERS: *** DO NOT USE *** | ||||
| func NoopHandle(slen int) *noopHandle { | ||||
| 	h := noopHandle{} | ||||
| 	h.rand = rand.New(rand.NewSource(time.Now().UnixNano())) | ||||
| 	h.B = make([][]byte, slen) | ||||
| 	h.S = make([]string, slen) | ||||
| 	for i := 0; i < len(h.S); i++ { | ||||
| 		b := make([]byte, i+1) | ||||
| 		for j := 0; j < len(b); j++ { | ||||
| 			b[j] = 'a' + byte(i) | ||||
| 		} | ||||
| 		h.B[i] = b | ||||
| 		h.S[i] = string(b) | ||||
| 	} | ||||
| 	return &h | ||||
| } | ||||
|  | ||||
| // noopHandle does nothing. | ||||
| // It is used to simulate the overhead of the codec framework. | ||||
| type noopHandle struct { | ||||
| 	BasicHandle | ||||
| 	binaryEncodingType | ||||
| 	noopDrv // noopDrv is unexported here, so we can get a copy of it when needed. | ||||
| } | ||||
|  | ||||
| type noopDrv struct { | ||||
| 	d    *Decoder | ||||
| 	e    *Encoder | ||||
| 	i    int | ||||
| 	S    []string | ||||
| 	B    [][]byte | ||||
| 	mks  []bool    // stack. if map (true), else if array (false) | ||||
| 	mk   bool      // top of stack. what container are we on? map or array? | ||||
| 	ct   valueType // last response for IsContainerType. | ||||
| 	cb   int       // counter for ContainerType | ||||
| 	rand *rand.Rand | ||||
| } | ||||
|  | ||||
| func (h *noopDrv) r(v int) int { return h.rand.Intn(v) } | ||||
| func (h *noopDrv) m(v int) int { h.i++; return h.i % v } | ||||
|  | ||||
| func (h *noopDrv) newEncDriver(e *Encoder) encDriver { h.e = e; return h } | ||||
| func (h *noopDrv) newDecDriver(d *Decoder) decDriver { h.d = d; return h } | ||||
|  | ||||
| func (h *noopDrv) reset()       {} | ||||
| func (h *noopDrv) uncacheRead() {} | ||||
|  | ||||
| // --- encDriver | ||||
|  | ||||
| // stack functions (for map and array) | ||||
| func (h *noopDrv) start(b bool) { | ||||
| 	// println("start", len(h.mks)+1) | ||||
| 	h.mks = append(h.mks, b) | ||||
| 	h.mk = b | ||||
| } | ||||
| func (h *noopDrv) end() { | ||||
| 	// println("end: ", len(h.mks)-1) | ||||
| 	h.mks = h.mks[:len(h.mks)-1] | ||||
| 	if len(h.mks) > 0 { | ||||
| 		h.mk = h.mks[len(h.mks)-1] | ||||
| 	} else { | ||||
| 		h.mk = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *noopDrv) EncodeBuiltin(rt uintptr, v interface{}) {} | ||||
| func (h *noopDrv) EncodeNil()                              {} | ||||
| func (h *noopDrv) EncodeInt(i int64)                       {} | ||||
| func (h *noopDrv) EncodeUint(i uint64)                     {} | ||||
| func (h *noopDrv) EncodeBool(b bool)                       {} | ||||
| func (h *noopDrv) EncodeFloat32(f float32)                 {} | ||||
| func (h *noopDrv) EncodeFloat64(f float64)                 {} | ||||
| func (h *noopDrv) EncodeRawExt(re *RawExt, e *Encoder)     {} | ||||
| func (h *noopDrv) EncodeArrayStart(length int)             { h.start(true) } | ||||
| func (h *noopDrv) EncodeMapStart(length int)               { h.start(false) } | ||||
| func (h *noopDrv) EncodeEnd()                              { h.end() } | ||||
|  | ||||
| func (h *noopDrv) EncodeString(c charEncoding, v string)      {} | ||||
| func (h *noopDrv) EncodeSymbol(v string)                      {} | ||||
| func (h *noopDrv) EncodeStringBytes(c charEncoding, v []byte) {} | ||||
|  | ||||
| func (h *noopDrv) EncodeExt(rv interface{}, xtag uint64, ext Ext, e *Encoder) {} | ||||
|  | ||||
| // ---- decDriver | ||||
| func (h *noopDrv) initReadNext()                              {} | ||||
| func (h *noopDrv) CheckBreak() bool                           { return false } | ||||
| func (h *noopDrv) IsBuiltinType(rt uintptr) bool              { return false } | ||||
| func (h *noopDrv) DecodeBuiltin(rt uintptr, v interface{})    {} | ||||
| func (h *noopDrv) DecodeInt(bitsize uint8) (i int64)          { return int64(h.m(15)) } | ||||
| func (h *noopDrv) DecodeUint(bitsize uint8) (ui uint64)       { return uint64(h.m(35)) } | ||||
| func (h *noopDrv) DecodeFloat(chkOverflow32 bool) (f float64) { return float64(h.m(95)) } | ||||
| func (h *noopDrv) DecodeBool() (b bool)                       { return h.m(2) == 0 } | ||||
| func (h *noopDrv) DecodeString() (s string)                   { return h.S[h.m(8)] } | ||||
| func (h *noopDrv) DecodeStringAsBytes() []byte                { return h.DecodeBytes(nil, true) } | ||||
|  | ||||
| func (h *noopDrv) DecodeBytes(bs []byte, zerocopy bool) []byte { return h.B[h.m(len(h.B))] } | ||||
|  | ||||
| func (h *noopDrv) ReadEnd() { h.end() } | ||||
|  | ||||
| // toggle map/slice | ||||
| func (h *noopDrv) ReadMapStart() int   { h.start(true); return h.m(10) } | ||||
| func (h *noopDrv) ReadArrayStart() int { h.start(false); return h.m(10) } | ||||
|  | ||||
| func (h *noopDrv) ContainerType() (vt valueType) { | ||||
| 	// return h.m(2) == 0 | ||||
| 	// handle kStruct, which will bomb is it calls this and doesn't get back a map or array. | ||||
| 	// consequently, if the return value is not map or array, reset it to one of them based on h.m(7) % 2 | ||||
| 	// for kstruct: at least one out of every 2 times, return one of valueTypeMap or Array (else kstruct bombs) | ||||
| 	// however, every 10th time it is called, we just return something else. | ||||
| 	var vals = [...]valueType{valueTypeArray, valueTypeMap} | ||||
| 	//  ------------ TAKE ------------ | ||||
| 	// if h.cb%2 == 0 { | ||||
| 	// 	if h.ct == valueTypeMap || h.ct == valueTypeArray { | ||||
| 	// 	} else { | ||||
| 	// 		h.ct = vals[h.m(2)] | ||||
| 	// 	} | ||||
| 	// } else if h.cb%5 == 0 { | ||||
| 	// 	h.ct = valueType(h.m(8)) | ||||
| 	// } else { | ||||
| 	// 	h.ct = vals[h.m(2)] | ||||
| 	// } | ||||
| 	//  ------------ TAKE ------------ | ||||
| 	// if h.cb%16 == 0 { | ||||
| 	// 	h.ct = valueType(h.cb % 8) | ||||
| 	// } else { | ||||
| 	// 	h.ct = vals[h.cb%2] | ||||
| 	// } | ||||
| 	h.ct = vals[h.cb%2] | ||||
| 	h.cb++ | ||||
| 	return h.ct | ||||
|  | ||||
| 	// if h.ct == valueTypeNil || h.ct == valueTypeString || h.ct == valueTypeBytes { | ||||
| 	// 	return h.ct | ||||
| 	// } | ||||
| 	// return valueTypeUnset | ||||
| 	// TODO: may need to tweak this so it works. | ||||
| 	// if h.ct == valueTypeMap && vt == valueTypeArray || h.ct == valueTypeArray && vt == valueTypeMap { | ||||
| 	// 	h.cb = !h.cb | ||||
| 	// 	h.ct = vt | ||||
| 	// 	return h.cb | ||||
| 	// } | ||||
| 	// // go in a loop and check it. | ||||
| 	// h.ct = vt | ||||
| 	// h.cb = h.m(7) == 0 | ||||
| 	// return h.cb | ||||
| } | ||||
| func (h *noopDrv) TryDecodeAsNil() bool { | ||||
| 	if h.mk { | ||||
| 		return false | ||||
| 	} else { | ||||
| 		return h.m(8) == 0 | ||||
| 	} | ||||
| } | ||||
| func (h *noopDrv) DecodeExt(rv interface{}, xtag uint64, ext Ext) uint64 { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (h *noopDrv) DecodeNaked() { | ||||
| 	// use h.r (random) not h.m() because h.m() could cause the same value to be given. | ||||
| 	var sk int | ||||
| 	if h.mk { | ||||
| 		// if mapkey, do not support values of nil OR bytes, array, map or rawext | ||||
| 		sk = h.r(7) + 1 | ||||
| 	} else { | ||||
| 		sk = h.r(12) | ||||
| 	} | ||||
| 	n := &h.d.n | ||||
| 	switch sk { | ||||
| 	case 0: | ||||
| 		n.v = valueTypeNil | ||||
| 	case 1: | ||||
| 		n.v, n.b = valueTypeBool, false | ||||
| 	case 2: | ||||
| 		n.v, n.b = valueTypeBool, true | ||||
| 	case 3: | ||||
| 		n.v, n.i = valueTypeInt, h.DecodeInt(64) | ||||
| 	case 4: | ||||
| 		n.v, n.u = valueTypeUint, h.DecodeUint(64) | ||||
| 	case 5: | ||||
| 		n.v, n.f = valueTypeFloat, h.DecodeFloat(true) | ||||
| 	case 6: | ||||
| 		n.v, n.f = valueTypeFloat, h.DecodeFloat(false) | ||||
| 	case 7: | ||||
| 		n.v, n.s = valueTypeString, h.DecodeString() | ||||
| 	case 8: | ||||
| 		n.v, n.l = valueTypeBytes, h.B[h.m(len(h.B))] | ||||
| 	case 9: | ||||
| 		n.v = valueTypeArray | ||||
| 	case 10: | ||||
| 		n.v = valueTypeMap | ||||
| 	default: | ||||
| 		n.v = valueTypeExt | ||||
| 		n.u = h.DecodeUint(64) | ||||
| 		n.l = h.B[h.m(len(h.B))] | ||||
| 	} | ||||
| 	h.ct = n.v | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										187
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/rpc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/rpc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"net/rpc" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // // rpcEncodeTerminator allows a handler specify a []byte terminator to send after each Encode. | ||||
| // // | ||||
| // // Some codecs like json need to put a space after each encoded value, to serve as a | ||||
| // // delimiter for things like numbers (else json codec will continue reading till EOF). | ||||
| // type rpcEncodeTerminator interface { | ||||
| // 	rpcEncodeTerminate() []byte | ||||
| // } | ||||
|  | ||||
| // Rpc provides a rpc Server or Client Codec for rpc communication. | ||||
| type Rpc interface { | ||||
| 	ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec | ||||
| 	ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec | ||||
| } | ||||
|  | ||||
| // RpcCodecBuffered allows access to the underlying bufio.Reader/Writer | ||||
| // used by the rpc connection. It accommodates use-cases where the connection | ||||
| // should be used by rpc and non-rpc functions, e.g. streaming a file after | ||||
| // sending an rpc response. | ||||
| type RpcCodecBuffered interface { | ||||
| 	BufferedReader() *bufio.Reader | ||||
| 	BufferedWriter() *bufio.Writer | ||||
| } | ||||
|  | ||||
| // ------------------------------------- | ||||
|  | ||||
| // rpcCodec defines the struct members and common methods. | ||||
| type rpcCodec struct { | ||||
| 	rwc io.ReadWriteCloser | ||||
| 	dec *Decoder | ||||
| 	enc *Encoder | ||||
| 	bw  *bufio.Writer | ||||
| 	br  *bufio.Reader | ||||
| 	mu  sync.Mutex | ||||
| 	h   Handle | ||||
|  | ||||
| 	cls   bool | ||||
| 	clsmu sync.RWMutex | ||||
| } | ||||
|  | ||||
| func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec { | ||||
| 	bw := bufio.NewWriter(conn) | ||||
| 	br := bufio.NewReader(conn) | ||||
|  | ||||
| 	// defensive: ensure that jsonH has TermWhitespace turned on. | ||||
| 	if jsonH, ok := h.(*JsonHandle); ok && !jsonH.TermWhitespace { | ||||
| 		panic(errors.New("rpc requires a JsonHandle with TermWhitespace set to true")) | ||||
| 	} | ||||
|  | ||||
| 	return rpcCodec{ | ||||
| 		rwc: conn, | ||||
| 		bw:  bw, | ||||
| 		br:  br, | ||||
| 		enc: NewEncoder(bw, h), | ||||
| 		dec: NewDecoder(br, h), | ||||
| 		h:   h, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) BufferedReader() *bufio.Reader { | ||||
| 	return c.br | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) BufferedWriter() *bufio.Writer { | ||||
| 	return c.bw | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) { | ||||
| 	if c.isClosed() { | ||||
| 		return io.EOF | ||||
| 	} | ||||
| 	if err = c.enc.Encode(obj1); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	// t, tOk := c.h.(rpcEncodeTerminator) | ||||
| 	// if tOk { | ||||
| 	// 	c.bw.Write(t.rpcEncodeTerminate()) | ||||
| 	// } | ||||
| 	if writeObj2 { | ||||
| 		if err = c.enc.Encode(obj2); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		// if tOk { | ||||
| 		// 	c.bw.Write(t.rpcEncodeTerminate()) | ||||
| 		// } | ||||
| 	} | ||||
| 	if doFlush { | ||||
| 		return c.bw.Flush() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) read(obj interface{}) (err error) { | ||||
| 	if c.isClosed() { | ||||
| 		return io.EOF | ||||
| 	} | ||||
| 	//If nil is passed in, we should still attempt to read content to nowhere. | ||||
| 	if obj == nil { | ||||
| 		var obj2 interface{} | ||||
| 		return c.dec.Decode(&obj2) | ||||
| 	} | ||||
| 	return c.dec.Decode(obj) | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) isClosed() bool { | ||||
| 	c.clsmu.RLock() | ||||
| 	x := c.cls | ||||
| 	c.clsmu.RUnlock() | ||||
| 	return x | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) Close() error { | ||||
| 	if c.isClosed() { | ||||
| 		return io.EOF | ||||
| 	} | ||||
| 	c.clsmu.Lock() | ||||
| 	c.cls = true | ||||
| 	c.clsmu.Unlock() | ||||
| 	return c.rwc.Close() | ||||
| } | ||||
|  | ||||
| func (c *rpcCodec) ReadResponseBody(body interface{}) error { | ||||
| 	return c.read(body) | ||||
| } | ||||
|  | ||||
| // ------------------------------------- | ||||
|  | ||||
| type goRpcCodec struct { | ||||
| 	rpcCodec | ||||
| } | ||||
|  | ||||
| func (c *goRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error { | ||||
| 	// Must protect for concurrent access as per API | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	return c.write(r, body, true, true) | ||||
| } | ||||
|  | ||||
| func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error { | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	return c.write(r, body, true, true) | ||||
| } | ||||
|  | ||||
| func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error { | ||||
| 	return c.read(r) | ||||
| } | ||||
|  | ||||
| func (c *goRpcCodec) ReadRequestHeader(r *rpc.Request) error { | ||||
| 	return c.read(r) | ||||
| } | ||||
|  | ||||
| func (c *goRpcCodec) ReadRequestBody(body interface{}) error { | ||||
| 	return c.read(body) | ||||
| } | ||||
|  | ||||
| // ------------------------------------- | ||||
|  | ||||
| // goRpc is the implementation of Rpc that uses the communication protocol | ||||
| // as defined in net/rpc package. | ||||
| type goRpc struct{} | ||||
|  | ||||
| // GoRpc implements Rpc using the communication protocol defined in net/rpc package. | ||||
| // Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered. | ||||
| var GoRpc goRpc | ||||
|  | ||||
| func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec { | ||||
| 	return &goRpcCodec{newRPCCodec(conn, h)} | ||||
| } | ||||
|  | ||||
| func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec { | ||||
| 	return &goRpcCodec{newRPCCodec(conn, h)} | ||||
| } | ||||
|  | ||||
| var _ RpcCodecBuffered = (*rpcCodec)(nil) // ensure *rpcCodec implements RpcCodecBuffered | ||||
							
								
								
									
										541
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/simple.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										541
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/simple.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,541 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	_               uint8 = iota | ||||
| 	simpleVdNil           = 1 | ||||
| 	simpleVdFalse         = 2 | ||||
| 	simpleVdTrue          = 3 | ||||
| 	simpleVdFloat32       = 4 | ||||
| 	simpleVdFloat64       = 5 | ||||
|  | ||||
| 	// each lasts for 4 (ie n, n+1, n+2, n+3) | ||||
| 	simpleVdPosInt = 8 | ||||
| 	simpleVdNegInt = 12 | ||||
|  | ||||
| 	// containers: each lasts for 4 (ie n, n+1, n+2, ... n+7) | ||||
| 	simpleVdString    = 216 | ||||
| 	simpleVdByteArray = 224 | ||||
| 	simpleVdArray     = 232 | ||||
| 	simpleVdMap       = 240 | ||||
| 	simpleVdExt       = 248 | ||||
| ) | ||||
|  | ||||
| type simpleEncDriver struct { | ||||
| 	noBuiltInTypes | ||||
| 	encDriverNoopContainerWriter | ||||
| 	// encNoSeparator | ||||
| 	e *Encoder | ||||
| 	h *SimpleHandle | ||||
| 	w encWriter | ||||
| 	b [8]byte | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeNil() { | ||||
| 	e.w.writen1(simpleVdNil) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeBool(b bool) { | ||||
| 	if b { | ||||
| 		e.w.writen1(simpleVdTrue) | ||||
| 	} else { | ||||
| 		e.w.writen1(simpleVdFalse) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeFloat32(f float32) { | ||||
| 	e.w.writen1(simpleVdFloat32) | ||||
| 	bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeFloat64(f float64) { | ||||
| 	e.w.writen1(simpleVdFloat64) | ||||
| 	bigenHelper{e.b[:8], e.w}.writeUint64(math.Float64bits(f)) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeInt(v int64) { | ||||
| 	if v < 0 { | ||||
| 		e.encUint(uint64(-v), simpleVdNegInt) | ||||
| 	} else { | ||||
| 		e.encUint(uint64(v), simpleVdPosInt) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeUint(v uint64) { | ||||
| 	e.encUint(v, simpleVdPosInt) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) encUint(v uint64, bd uint8) { | ||||
| 	if v <= math.MaxUint8 { | ||||
| 		e.w.writen2(bd, uint8(v)) | ||||
| 	} else if v <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd + 1) | ||||
| 		bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v)) | ||||
| 	} else if v <= math.MaxUint32 { | ||||
| 		e.w.writen1(bd + 2) | ||||
| 		bigenHelper{e.b[:4], e.w}.writeUint32(uint32(v)) | ||||
| 	} else { // if v <= math.MaxUint64 { | ||||
| 		e.w.writen1(bd + 3) | ||||
| 		bigenHelper{e.b[:8], e.w}.writeUint64(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) encLen(bd byte, length int) { | ||||
| 	if length == 0 { | ||||
| 		e.w.writen1(bd) | ||||
| 	} else if length <= math.MaxUint8 { | ||||
| 		e.w.writen1(bd + 1) | ||||
| 		e.w.writen1(uint8(length)) | ||||
| 	} else if length <= math.MaxUint16 { | ||||
| 		e.w.writen1(bd + 2) | ||||
| 		bigenHelper{e.b[:2], e.w}.writeUint16(uint16(length)) | ||||
| 	} else if int64(length) <= math.MaxUint32 { | ||||
| 		e.w.writen1(bd + 3) | ||||
| 		bigenHelper{e.b[:4], e.w}.writeUint32(uint32(length)) | ||||
| 	} else { | ||||
| 		e.w.writen1(bd + 4) | ||||
| 		bigenHelper{e.b[:8], e.w}.writeUint64(uint64(length)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, _ *Encoder) { | ||||
| 	bs := ext.WriteExt(rv) | ||||
| 	if bs == nil { | ||||
| 		e.EncodeNil() | ||||
| 		return | ||||
| 	} | ||||
| 	e.encodeExtPreamble(uint8(xtag), len(bs)) | ||||
| 	e.w.writeb(bs) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) { | ||||
| 	e.encodeExtPreamble(uint8(re.Tag), len(re.Data)) | ||||
| 	e.w.writeb(re.Data) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) encodeExtPreamble(xtag byte, length int) { | ||||
| 	e.encLen(simpleVdExt, length) | ||||
| 	e.w.writen1(xtag) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) WriteArrayStart(length int) { | ||||
| 	e.encLen(simpleVdArray, length) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) WriteMapStart(length int) { | ||||
| 	e.encLen(simpleVdMap, length) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeString(c charEncoding, v string) { | ||||
| 	e.encLen(simpleVdString, len(v)) | ||||
| 	e.w.writestr(v) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeSymbol(v string) { | ||||
| 	e.EncodeString(c_UTF8, v) | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) EncodeStringBytes(c charEncoding, v []byte) { | ||||
| 	e.encLen(simpleVdByteArray, len(v)) | ||||
| 	e.w.writeb(v) | ||||
| } | ||||
|  | ||||
| //------------------------------------ | ||||
|  | ||||
| type simpleDecDriver struct { | ||||
| 	d      *Decoder | ||||
| 	h      *SimpleHandle | ||||
| 	r      decReader | ||||
| 	bdRead bool | ||||
| 	bd     byte | ||||
| 	br     bool // bytes reader | ||||
| 	b      [scratchByteArrayLen]byte | ||||
| 	noBuiltInTypes | ||||
| 	// noStreamingCodec | ||||
| 	decDriverNoopContainerReader | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) readNextBd() { | ||||
| 	d.bd = d.r.readn1() | ||||
| 	d.bdRead = true | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) uncacheRead() { | ||||
| 	if d.bdRead { | ||||
| 		d.r.unreadn1() | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) ContainerType() (vt valueType) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == simpleVdNil { | ||||
| 		return valueTypeNil | ||||
| 	} else if d.bd == simpleVdByteArray || d.bd == simpleVdByteArray+1 || | ||||
| 		d.bd == simpleVdByteArray+2 || d.bd == simpleVdByteArray+3 || d.bd == simpleVdByteArray+4 { | ||||
| 		return valueTypeBytes | ||||
| 	} else if d.bd == simpleVdString || d.bd == simpleVdString+1 || | ||||
| 		d.bd == simpleVdString+2 || d.bd == simpleVdString+3 || d.bd == simpleVdString+4 { | ||||
| 		return valueTypeString | ||||
| 	} else if d.bd == simpleVdArray || d.bd == simpleVdArray+1 || | ||||
| 		d.bd == simpleVdArray+2 || d.bd == simpleVdArray+3 || d.bd == simpleVdArray+4 { | ||||
| 		return valueTypeArray | ||||
| 	} else if d.bd == simpleVdMap || d.bd == simpleVdMap+1 || | ||||
| 		d.bd == simpleVdMap+2 || d.bd == simpleVdMap+3 || d.bd == simpleVdMap+4 { | ||||
| 		return valueTypeMap | ||||
| 	} else { | ||||
| 		// d.d.errorf("isContainerType: unsupported parameter: %v", vt) | ||||
| 	} | ||||
| 	return valueTypeUnset | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) TryDecodeAsNil() bool { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == simpleVdNil { | ||||
| 		d.bdRead = false | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) decCheckInteger() (ui uint64, neg bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	switch d.bd { | ||||
| 	case simpleVdPosInt: | ||||
| 		ui = uint64(d.r.readn1()) | ||||
| 	case simpleVdPosInt + 1: | ||||
| 		ui = uint64(bigen.Uint16(d.r.readx(2))) | ||||
| 	case simpleVdPosInt + 2: | ||||
| 		ui = uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 	case simpleVdPosInt + 3: | ||||
| 		ui = uint64(bigen.Uint64(d.r.readx(8))) | ||||
| 	case simpleVdNegInt: | ||||
| 		ui = uint64(d.r.readn1()) | ||||
| 		neg = true | ||||
| 	case simpleVdNegInt + 1: | ||||
| 		ui = uint64(bigen.Uint16(d.r.readx(2))) | ||||
| 		neg = true | ||||
| 	case simpleVdNegInt + 2: | ||||
| 		ui = uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 		neg = true | ||||
| 	case simpleVdNegInt + 3: | ||||
| 		ui = uint64(bigen.Uint64(d.r.readx(8))) | ||||
| 		neg = true | ||||
| 	default: | ||||
| 		d.d.errorf("decIntAny: Integer only valid from pos/neg integer1..8. Invalid descriptor: %v", d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	// don't do this check, because callers may only want the unsigned value. | ||||
| 	// if ui > math.MaxInt64 { | ||||
| 	// 	d.d.errorf("decIntAny: Integer out of range for signed int64: %v", ui) | ||||
| 	//		return | ||||
| 	// } | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeInt(bitsize uint8) (i int64) { | ||||
| 	ui, neg := d.decCheckInteger() | ||||
| 	i, overflow := chkOvf.SignedInt(ui) | ||||
| 	if overflow { | ||||
| 		d.d.errorf("simple: overflow converting %v to signed integer", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	if neg { | ||||
| 		i = -i | ||||
| 	} | ||||
| 	if chkOvf.Int(i, bitsize) { | ||||
| 		d.d.errorf("simple: overflow integer: %v", i) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeUint(bitsize uint8) (ui uint64) { | ||||
| 	ui, neg := d.decCheckInteger() | ||||
| 	if neg { | ||||
| 		d.d.errorf("Assigning negative signed value to unsigned type") | ||||
| 		return | ||||
| 	} | ||||
| 	if chkOvf.Uint(ui, bitsize) { | ||||
| 		d.d.errorf("simple: overflow integer: %v", ui) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == simpleVdFloat32 { | ||||
| 		f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4)))) | ||||
| 	} else if d.bd == simpleVdFloat64 { | ||||
| 		f = math.Float64frombits(bigen.Uint64(d.r.readx(8))) | ||||
| 	} else { | ||||
| 		if d.bd >= simpleVdPosInt && d.bd <= simpleVdNegInt+3 { | ||||
| 			f = float64(d.DecodeInt(64)) | ||||
| 		} else { | ||||
| 			d.d.errorf("Float only valid from float32/64: Invalid descriptor: %v", d.bd) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if chkOverflow32 && chkOvf.Float32(f) { | ||||
| 		d.d.errorf("msgpack: float32 overflow: %v", f) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // bool can be decoded from bool only (single byte). | ||||
| func (d *simpleDecDriver) DecodeBool() (b bool) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == simpleVdTrue { | ||||
| 		b = true | ||||
| 	} else if d.bd == simpleVdFalse { | ||||
| 	} else { | ||||
| 		d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) ReadMapStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return d.decLen() | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) ReadArrayStart() (length int) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return d.decLen() | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) decLen() int { | ||||
| 	switch d.bd % 8 { | ||||
| 	case 0: | ||||
| 		return 0 | ||||
| 	case 1: | ||||
| 		return int(d.r.readn1()) | ||||
| 	case 2: | ||||
| 		return int(bigen.Uint16(d.r.readx(2))) | ||||
| 	case 3: | ||||
| 		ui := uint64(bigen.Uint32(d.r.readx(4))) | ||||
| 		if chkOvf.Uint(ui, intBitsize) { | ||||
| 			d.d.errorf("simple: overflow integer: %v", ui) | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return int(ui) | ||||
| 	case 4: | ||||
| 		ui := bigen.Uint64(d.r.readx(8)) | ||||
| 		if chkOvf.Uint(ui, intBitsize) { | ||||
| 			d.d.errorf("simple: overflow integer: %v", ui) | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return int(ui) | ||||
| 	} | ||||
| 	d.d.errorf("decLen: Cannot read length: bd%%8 must be in range 0..4. Got: %d", d.bd%8) | ||||
| 	return -1 | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeString() (s string) { | ||||
| 	return string(d.DecodeBytes(d.b[:], true)) | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeStringAsBytes() (s []byte) { | ||||
| 	return d.DecodeBytes(d.b[:], true) | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	if d.bd == simpleVdNil { | ||||
| 		d.bdRead = false | ||||
| 		return | ||||
| 	} | ||||
| 	clen := d.decLen() | ||||
| 	d.bdRead = false | ||||
| 	if zerocopy { | ||||
| 		if d.br { | ||||
| 			return d.r.readx(clen) | ||||
| 		} else if len(bs) == 0 { | ||||
| 			bs = d.b[:] | ||||
| 		} | ||||
| 	} | ||||
| 	return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs) | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) { | ||||
| 	if xtag > 0xff { | ||||
| 		d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag) | ||||
| 		return | ||||
| 	} | ||||
| 	realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag)) | ||||
| 	realxtag = uint64(realxtag1) | ||||
| 	if ext == nil { | ||||
| 		re := rv.(*RawExt) | ||||
| 		re.Tag = realxtag | ||||
| 		re.Data = detachZeroCopyBytes(d.br, re.Data, xbs) | ||||
| 	} else { | ||||
| 		ext.ReadExt(rv, xbs) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
| 	switch d.bd { | ||||
| 	case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4: | ||||
| 		l := d.decLen() | ||||
| 		xtag = d.r.readn1() | ||||
| 		if verifyTag && xtag != tag { | ||||
| 			d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag) | ||||
| 			return | ||||
| 		} | ||||
| 		xbs = d.r.readx(l) | ||||
| 	case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4: | ||||
| 		xbs = d.DecodeBytes(nil, true) | ||||
| 	default: | ||||
| 		d.d.errorf("Invalid d.bd for extensions (Expecting extensions or byte array). Got: 0x%x", d.bd) | ||||
| 		return | ||||
| 	} | ||||
| 	d.bdRead = false | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) DecodeNaked() { | ||||
| 	if !d.bdRead { | ||||
| 		d.readNextBd() | ||||
| 	} | ||||
|  | ||||
| 	n := d.d.n | ||||
| 	var decodeFurther bool | ||||
|  | ||||
| 	switch d.bd { | ||||
| 	case simpleVdNil: | ||||
| 		n.v = valueTypeNil | ||||
| 	case simpleVdFalse: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = false | ||||
| 	case simpleVdTrue: | ||||
| 		n.v = valueTypeBool | ||||
| 		n.b = true | ||||
| 	case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3: | ||||
| 		if d.h.SignedInteger { | ||||
| 			n.v = valueTypeInt | ||||
| 			n.i = d.DecodeInt(64) | ||||
| 		} else { | ||||
| 			n.v = valueTypeUint | ||||
| 			n.u = d.DecodeUint(64) | ||||
| 		} | ||||
| 	case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3: | ||||
| 		n.v = valueTypeInt | ||||
| 		n.i = d.DecodeInt(64) | ||||
| 	case simpleVdFloat32: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.DecodeFloat(true) | ||||
| 	case simpleVdFloat64: | ||||
| 		n.v = valueTypeFloat | ||||
| 		n.f = d.DecodeFloat(false) | ||||
| 	case simpleVdString, simpleVdString + 1, simpleVdString + 2, simpleVdString + 3, simpleVdString + 4: | ||||
| 		n.v = valueTypeString | ||||
| 		n.s = d.DecodeString() | ||||
| 	case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4: | ||||
| 		n.v = valueTypeBytes | ||||
| 		n.l = d.DecodeBytes(nil, false) | ||||
| 	case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4: | ||||
| 		n.v = valueTypeExt | ||||
| 		l := d.decLen() | ||||
| 		n.u = uint64(d.r.readn1()) | ||||
| 		n.l = d.r.readx(l) | ||||
| 	case simpleVdArray, simpleVdArray + 1, simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4: | ||||
| 		n.v = valueTypeArray | ||||
| 		decodeFurther = true | ||||
| 	case simpleVdMap, simpleVdMap + 1, simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4: | ||||
| 		n.v = valueTypeMap | ||||
| 		decodeFurther = true | ||||
| 	default: | ||||
| 		d.d.errorf("decodeNaked: Unrecognized d.bd: 0x%x", d.bd) | ||||
| 	} | ||||
|  | ||||
| 	if !decodeFurther { | ||||
| 		d.bdRead = false | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| //------------------------------------ | ||||
|  | ||||
| // SimpleHandle is a Handle for a very simple encoding format. | ||||
| // | ||||
| // simple is a simplistic codec similar to binc, but not as compact. | ||||
| //   - Encoding of a value is always preceded by the descriptor byte (bd) | ||||
| //   - True, false, nil are encoded fully in 1 byte (the descriptor) | ||||
| //   - Integers (intXXX, uintXXX) are encoded in 1, 2, 4 or 8 bytes (plus a descriptor byte). | ||||
| //     There are positive (uintXXX and intXXX >= 0) and negative (intXXX < 0) integers. | ||||
| //   - Floats are encoded in 4 or 8 bytes (plus a descriptor byte) | ||||
| //   - Lenght of containers (strings, bytes, array, map, extensions) | ||||
| //     are encoded in 0, 1, 2, 4 or 8 bytes. | ||||
| //     Zero-length containers have no length encoded. | ||||
| //     For others, the number of bytes is given by pow(2, bd%3) | ||||
| //   - maps are encoded as [bd] [length] [[key][value]]... | ||||
| //   - arrays are encoded as [bd] [length] [value]... | ||||
| //   - extensions are encoded as [bd] [length] [tag] [byte]... | ||||
| //   - strings/bytearrays are encoded as [bd] [length] [byte]... | ||||
| // | ||||
| // The full spec will be published soon. | ||||
| type SimpleHandle struct { | ||||
| 	BasicHandle | ||||
| 	binaryEncodingType | ||||
| 	noElemSeparators | ||||
| } | ||||
|  | ||||
| func (h *SimpleHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) { | ||||
| 	return h.SetExt(rt, tag, &setExtWrapper{b: ext}) | ||||
| } | ||||
|  | ||||
| func (h *SimpleHandle) newEncDriver(e *Encoder) encDriver { | ||||
| 	return &simpleEncDriver{e: e, w: e.w, h: h} | ||||
| } | ||||
|  | ||||
| func (h *SimpleHandle) newDecDriver(d *Decoder) decDriver { | ||||
| 	return &simpleDecDriver{d: d, h: h, r: d.r, br: d.bytes} | ||||
| } | ||||
|  | ||||
| func (e *simpleEncDriver) reset() { | ||||
| 	e.w = e.e.w | ||||
| } | ||||
|  | ||||
| func (d *simpleDecDriver) reset() { | ||||
| 	d.r, d.br = d.d.r, d.d.bytes | ||||
| 	d.bd, d.bdRead = 0, false | ||||
| } | ||||
|  | ||||
| var _ decDriver = (*simpleDecDriver)(nil) | ||||
| var _ encDriver = (*simpleEncDriver)(nil) | ||||
							
								
								
									
										220
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/time.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/time.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,220 @@ | ||||
| // Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved. | ||||
| // Use of this source code is governed by a MIT license found in the LICENSE file. | ||||
|  | ||||
| package codec | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var timeDigits = [...]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} | ||||
|  | ||||
| type timeExt struct{} | ||||
|  | ||||
| func (x timeExt) WriteExt(v interface{}) (bs []byte) { | ||||
| 	switch v2 := v.(type) { | ||||
| 	case time.Time: | ||||
| 		bs = encodeTime(v2) | ||||
| 	case *time.Time: | ||||
| 		bs = encodeTime(*v2) | ||||
| 	default: | ||||
| 		panic(fmt.Errorf("unsupported format for time conversion: expecting time.Time; got %T", v2)) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| func (x timeExt) ReadExt(v interface{}, bs []byte) { | ||||
| 	tt, err := decodeTime(bs) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	*(v.(*time.Time)) = tt | ||||
| } | ||||
|  | ||||
| func (x timeExt) ConvertExt(v interface{}) interface{} { | ||||
| 	return x.WriteExt(v) | ||||
| } | ||||
| func (x timeExt) UpdateExt(v interface{}, src interface{}) { | ||||
| 	x.ReadExt(v, src.([]byte)) | ||||
| } | ||||
|  | ||||
| // EncodeTime encodes a time.Time as a []byte, including | ||||
| // information on the instant in time and UTC offset. | ||||
| // | ||||
| // Format Description | ||||
| // | ||||
| //   A timestamp is composed of 3 components: | ||||
| // | ||||
| //   - secs: signed integer representing seconds since unix epoch | ||||
| //   - nsces: unsigned integer representing fractional seconds as a | ||||
| //     nanosecond offset within secs, in the range 0 <= nsecs < 1e9 | ||||
| //   - tz: signed integer representing timezone offset in minutes east of UTC, | ||||
| //     and a dst (daylight savings time) flag | ||||
| // | ||||
| //   When encoding a timestamp, the first byte is the descriptor, which | ||||
| //   defines which components are encoded and how many bytes are used to | ||||
| //   encode secs and nsecs components. *If secs/nsecs is 0 or tz is UTC, it | ||||
| //   is not encoded in the byte array explicitly*. | ||||
| // | ||||
| //       Descriptor 8 bits are of the form `A B C DDD EE`: | ||||
| //           A:   Is secs component encoded? 1 = true | ||||
| //           B:   Is nsecs component encoded? 1 = true | ||||
| //           C:   Is tz component encoded? 1 = true | ||||
| //           DDD: Number of extra bytes for secs (range 0-7). | ||||
| //                If A = 1, secs encoded in DDD+1 bytes. | ||||
| //                    If A = 0, secs is not encoded, and is assumed to be 0. | ||||
| //                    If A = 1, then we need at least 1 byte to encode secs. | ||||
| //                    DDD says the number of extra bytes beyond that 1. | ||||
| //                    E.g. if DDD=0, then secs is represented in 1 byte. | ||||
| //                         if DDD=2, then secs is represented in 3 bytes. | ||||
| //           EE:  Number of extra bytes for nsecs (range 0-3). | ||||
| //                If B = 1, nsecs encoded in EE+1 bytes (similar to secs/DDD above) | ||||
| // | ||||
| //   Following the descriptor bytes, subsequent bytes are: | ||||
| // | ||||
| //       secs component encoded in `DDD + 1` bytes (if A == 1) | ||||
| //       nsecs component encoded in `EE + 1` bytes (if B == 1) | ||||
| //       tz component encoded in 2 bytes (if C == 1) | ||||
| // | ||||
| //   secs and nsecs components are integers encoded in a BigEndian | ||||
| //   2-complement encoding format. | ||||
| // | ||||
| //   tz component is encoded as 2 bytes (16 bits). Most significant bit 15 to | ||||
| //   Least significant bit 0 are described below: | ||||
| // | ||||
| //       Timezone offset has a range of -12:00 to +14:00 (ie -720 to +840 minutes). | ||||
| //       Bit 15 = have\_dst: set to 1 if we set the dst flag. | ||||
| //       Bit 14 = dst\_on: set to 1 if dst is in effect at the time, or 0 if not. | ||||
| //       Bits 13..0 = timezone offset in minutes. It is a signed integer in Big Endian format. | ||||
| // | ||||
| func encodeTime(t time.Time) []byte { | ||||
| 	//t := rv.Interface().(time.Time) | ||||
| 	tsecs, tnsecs := t.Unix(), t.Nanosecond() | ||||
| 	var ( | ||||
| 		bd   byte | ||||
| 		btmp [8]byte | ||||
| 		bs   [16]byte | ||||
| 		i    int = 1 | ||||
| 	) | ||||
| 	l := t.Location() | ||||
| 	if l == time.UTC { | ||||
| 		l = nil | ||||
| 	} | ||||
| 	if tsecs != 0 { | ||||
| 		bd = bd | 0x80 | ||||
| 		bigen.PutUint64(btmp[:], uint64(tsecs)) | ||||
| 		f := pruneSignExt(btmp[:], tsecs >= 0) | ||||
| 		bd = bd | (byte(7-f) << 2) | ||||
| 		copy(bs[i:], btmp[f:]) | ||||
| 		i = i + (8 - f) | ||||
| 	} | ||||
| 	if tnsecs != 0 { | ||||
| 		bd = bd | 0x40 | ||||
| 		bigen.PutUint32(btmp[:4], uint32(tnsecs)) | ||||
| 		f := pruneSignExt(btmp[:4], true) | ||||
| 		bd = bd | byte(3-f) | ||||
| 		copy(bs[i:], btmp[f:4]) | ||||
| 		i = i + (4 - f) | ||||
| 	} | ||||
| 	if l != nil { | ||||
| 		bd = bd | 0x20 | ||||
| 		// Note that Go Libs do not give access to dst flag. | ||||
| 		_, zoneOffset := t.Zone() | ||||
| 		//zoneName, zoneOffset := t.Zone() | ||||
| 		zoneOffset /= 60 | ||||
| 		z := uint16(zoneOffset) | ||||
| 		bigen.PutUint16(btmp[:2], z) | ||||
| 		// clear dst flags | ||||
| 		bs[i] = btmp[0] & 0x3f | ||||
| 		bs[i+1] = btmp[1] | ||||
| 		i = i + 2 | ||||
| 	} | ||||
| 	bs[0] = bd | ||||
| 	return bs[0:i] | ||||
| } | ||||
|  | ||||
| // DecodeTime decodes a []byte into a time.Time. | ||||
| func decodeTime(bs []byte) (tt time.Time, err error) { | ||||
| 	bd := bs[0] | ||||
| 	var ( | ||||
| 		tsec  int64 | ||||
| 		tnsec uint32 | ||||
| 		tz    uint16 | ||||
| 		i     byte = 1 | ||||
| 		i2    byte | ||||
| 		n     byte | ||||
| 	) | ||||
| 	if bd&(1<<7) != 0 { | ||||
| 		var btmp [8]byte | ||||
| 		n = ((bd >> 2) & 0x7) + 1 | ||||
| 		i2 = i + n | ||||
| 		copy(btmp[8-n:], bs[i:i2]) | ||||
| 		//if first bit of bs[i] is set, then fill btmp[0..8-n] with 0xff (ie sign extend it) | ||||
| 		if bs[i]&(1<<7) != 0 { | ||||
| 			copy(btmp[0:8-n], bsAll0xff) | ||||
| 			//for j,k := byte(0), 8-n; j < k; j++ {	btmp[j] = 0xff } | ||||
| 		} | ||||
| 		i = i2 | ||||
| 		tsec = int64(bigen.Uint64(btmp[:])) | ||||
| 	} | ||||
| 	if bd&(1<<6) != 0 { | ||||
| 		var btmp [4]byte | ||||
| 		n = (bd & 0x3) + 1 | ||||
| 		i2 = i + n | ||||
| 		copy(btmp[4-n:], bs[i:i2]) | ||||
| 		i = i2 | ||||
| 		tnsec = bigen.Uint32(btmp[:]) | ||||
| 	} | ||||
| 	if bd&(1<<5) == 0 { | ||||
| 		tt = time.Unix(tsec, int64(tnsec)).UTC() | ||||
| 		return | ||||
| 	} | ||||
| 	// In stdlib time.Parse, when a date is parsed without a zone name, it uses "" as zone name. | ||||
| 	// However, we need name here, so it can be shown when time is printed. | ||||
| 	// Zone name is in form: UTC-08:00. | ||||
| 	// Note that Go Libs do not give access to dst flag, so we ignore dst bits | ||||
|  | ||||
| 	i2 = i + 2 | ||||
| 	tz = bigen.Uint16(bs[i:i2]) | ||||
| 	i = i2 | ||||
| 	// sign extend sign bit into top 2 MSB (which were dst bits): | ||||
| 	if tz&(1<<13) == 0 { // positive | ||||
| 		tz = tz & 0x3fff //clear 2 MSBs: dst bits | ||||
| 	} else { // negative | ||||
| 		tz = tz | 0xc000 //set 2 MSBs: dst bits | ||||
| 		//tzname[3] = '-' (TODO: verify. this works here) | ||||
| 	} | ||||
| 	tzint := int16(tz) | ||||
| 	if tzint == 0 { | ||||
| 		tt = time.Unix(tsec, int64(tnsec)).UTC() | ||||
| 	} else { | ||||
| 		// For Go Time, do not use a descriptive timezone. | ||||
| 		// It's unnecessary, and makes it harder to do a reflect.DeepEqual. | ||||
| 		// The Offset already tells what the offset should be, if not on UTC and unknown zone name. | ||||
| 		// var zoneName = timeLocUTCName(tzint) | ||||
| 		tt = time.Unix(tsec, int64(tnsec)).In(time.FixedZone("", int(tzint)*60)) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // func timeLocUTCName(tzint int16) string { | ||||
| // 	if tzint == 0 { | ||||
| // 		return "UTC" | ||||
| // 	} | ||||
| // 	var tzname = []byte("UTC+00:00") | ||||
| // 	//tzname := fmt.Sprintf("UTC%s%02d:%02d", tzsign, tz/60, tz%60) //perf issue using Sprintf. inline below. | ||||
| // 	//tzhr, tzmin := tz/60, tz%60 //faster if u convert to int first | ||||
| // 	var tzhr, tzmin int16 | ||||
| // 	if tzint < 0 { | ||||
| // 		tzname[3] = '-' // (TODO: verify. this works here) | ||||
| // 		tzhr, tzmin = -tzint/60, (-tzint)%60 | ||||
| // 	} else { | ||||
| // 		tzhr, tzmin = tzint/60, tzint%60 | ||||
| // 	} | ||||
| // 	tzname[4] = timeDigits[tzhr/10] | ||||
| // 	tzname[5] = timeDigits[tzhr%10] | ||||
| // 	tzname[7] = timeDigits[tzmin/10] | ||||
| // 	tzname[8] = timeDigits[tzmin%10] | ||||
| // 	return string(tzname) | ||||
| // 	//return time.FixedZone(string(tzname), int(tzint)*60) | ||||
| // } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user