feat: check unused package (#232)
* feat: check unused package update edganiukov/fcm to appleboy/go-fcm * update readme Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> * update comment Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
parent
77bce18c9f
commit
14dc899b02
|
@ -20,6 +20,7 @@ pipeline:
|
||||||
- make lint
|
- make lint
|
||||||
- make build
|
- make build
|
||||||
- make embedmd
|
- make embedmd
|
||||||
|
- make test-vendor
|
||||||
- coverage all
|
- coverage all
|
||||||
# send coverage report
|
# send coverage report
|
||||||
- make coverage
|
- make coverage
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -14,6 +14,7 @@ GOFILES := find . -name "*.go" -type f -not -path "./vendor/*"
|
||||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||||
TAGS ?=
|
TAGS ?=
|
||||||
LDFLAGS ?= -X 'main.Version=$(VERSION)'
|
LDFLAGS ?= -X 'main.Version=$(VERSION)'
|
||||||
|
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')
|
||||||
|
|
||||||
ifneq ($(shell uname), Darwin)
|
ifneq ($(shell uname), Darwin)
|
||||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||||
|
@ -90,6 +91,19 @@ $(EXECUTABLE): $(SOURCES)
|
||||||
test: fmt-check
|
test: fmt-check
|
||||||
for PKG in $(PACKAGES); do go test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
for PKG in $(PACKAGES); do go test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
||||||
|
|
||||||
|
.PHONY: test-vendor
|
||||||
|
test-vendor:
|
||||||
|
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get -u github.com/kardianos/govendor; \
|
||||||
|
fi
|
||||||
|
govendor list +unused | tee "$(TMPDIR)/wc-gitea-unused"
|
||||||
|
[ $$(cat "$(TMPDIR)/wc-gitea-unused" | wc -l) -eq 0 ] || echo "Warning: /!\\ Some vendor are not used /!\\"
|
||||||
|
|
||||||
|
govendor list +outside | tee "$(TMPDIR)/wc-gitea-outside"
|
||||||
|
[ $$(cat "$(TMPDIR)/wc-gitea-outside" | wc -l) -eq 0 ] || exit 1
|
||||||
|
|
||||||
|
govendor status || exit 1
|
||||||
|
|
||||||
redis_test: init
|
redis_test: init
|
||||||
go test -v -cover ./storage/redis/...
|
go test -v -cover ./storage/redis/...
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ A push notification micro server using [Gin](https://github.com/gin-gonic/gin) f
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Support [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) using [fcm](https://github.com/edganiukov/fcm) library for Android.
|
* Support [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) using [go-fcm](https://github.com/appleboy/go-fcm) library for Android.
|
||||||
* Support [HTTP/2](https://http2.github.io/) Apple Push Notification Service using [apns2](https://github.com/sideshow/apns2) library.
|
* Support [HTTP/2](https://http2.github.io/) Apple Push Notification Service using [apns2](https://github.com/sideshow/apns2) library.
|
||||||
* Support [YAML](https://github.com/go-yaml/yaml) configuration.
|
* Support [YAML](https://github.com/go-yaml/yaml) configuration.
|
||||||
* Support command line to send single Android or iOS notification.
|
* Support command line to send single Android or iOS notification.
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edganiukov/fcm"
|
"github.com/appleboy/go-fcm"
|
||||||
apns "github.com/sideshow/apns2"
|
apns "github.com/sideshow/apns2"
|
||||||
"github.com/sideshow/apns2/certificate"
|
"github.com/sideshow/apns2/certificate"
|
||||||
"github.com/sideshow/apns2/payload"
|
"github.com/sideshow/apns2/payload"
|
||||||
|
@ -131,7 +131,7 @@ func CheckMessage(req PushNotification) error {
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ref: https://developers.google.com/cloud-messaging/http-server-ref
|
// ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref
|
||||||
if req.Platform == PlatFormAndroid && req.TimeToLive != nil && (*req.TimeToLive < uint(0) || uint(2419200) < *req.TimeToLive) {
|
if req.Platform == PlatFormAndroid && req.TimeToLive != nil && (*req.TimeToLive < uint(0) || uint(2419200) < *req.TimeToLive) {
|
||||||
msg = "the message's TimeToLive field must be an integer " +
|
msg = "the message's TimeToLive field must be an integer " +
|
||||||
"between 0 and 2419200 (4 weeks)"
|
"between 0 and 2419200 (4 weeks)"
|
||||||
|
@ -456,7 +456,7 @@ Retry:
|
||||||
|
|
||||||
// GetAndroidNotification use for define Android notification.
|
// GetAndroidNotification use for define Android notification.
|
||||||
// HTTP Connection Server Reference for Android
|
// HTTP Connection Server Reference for Android
|
||||||
// https://developers.google.com/cloud-messaging/http-server-ref
|
// https://firebase.google.com/docs/cloud-messaging/http-server-ref
|
||||||
func GetAndroidNotification(req PushNotification) *fcm.Message {
|
func GetAndroidNotification(req PushNotification) *fcm.Message {
|
||||||
notification := &fcm.Message{
|
notification := &fcm.Message{
|
||||||
To: req.To,
|
To: req.To,
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/appleboy/go-fcm"
|
||||||
"github.com/appleboy/gorush/config"
|
"github.com/appleboy/gorush/config"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/edganiukov/fcm"
|
|
||||||
"github.com/sideshow/apns2"
|
"github.com/sideshow/apns2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
0
vendor/github.com/edganiukov/fcm/LICENSE → vendor/github.com/appleboy/go-fcm/LICENSE
generated
vendored
0
vendor/github.com/edganiukov/fcm/LICENSE → vendor/github.com/appleboy/go-fcm/LICENSE
generated
vendored
20
vendor/github.com/edganiukov/fcm/README.md → vendor/github.com/appleboy/go-fcm/README.md
generated
vendored
20
vendor/github.com/edganiukov/fcm/README.md → vendor/github.com/appleboy/go-fcm/README.md
generated
vendored
|
@ -1,29 +1,35 @@
|
||||||
# fcm
|
# go-fcm
|
||||||
|
|
||||||
[](https://godoc.org/github.com/edganiukov/fcm)
|
[](https://godoc.org/github.com/edganiukov/fcm)
|
||||||
[](https://travis-ci.org/edganiukov/fcm)
|
[](https://travis-ci.org/edganiukov/fcm)
|
||||||
[](https://goreportcard.com/report/github.com/edganiukov/fcm)
|
[](https://goreportcard.com/report/github.com/edganiukov/fcm)
|
||||||
|
|
||||||
|
This project was forked from [github.com/edganiukov/fcmfcm](https://github.com/edganiukov/fcm).
|
||||||
|
|
||||||
Golang client library for Firebase Cloud Messaging. Implemented only [HTTP client](https://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream).
|
Golang client library for Firebase Cloud Messaging. Implemented only [HTTP client](https://firebase.google.com/docs/cloud-messaging/http-server-ref#downstream).
|
||||||
|
|
||||||
More information on [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/)
|
More information on [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/)
|
||||||
|
|
||||||
### Getting Started
|
## Getting Started
|
||||||
-------------------
|
|
||||||
To install fcm, use `go get`:
|
To install fcm, use `go get`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get github.com/edganiukov/fcm
|
go get github.com/appleboy/go-fcm
|
||||||
```
|
```
|
||||||
|
|
||||||
or `govendor`:
|
or `govendor`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
govendor fetch github.com/edganiukov/fcm
|
govendor fetch github.com/appleboy/go-fcm
|
||||||
```
|
```
|
||||||
|
|
||||||
or other tool for vendoring.
|
or other tool for vendoring.
|
||||||
|
|
||||||
### Sample Usage
|
## Sample Usage
|
||||||
----------------
|
|
||||||
Here is a simple example illustrating how to use FCM library:
|
Here is a simple example illustrating how to use FCM library:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
|
@ -65,5 +65,13 @@ func (msg *Message) Validate() error {
|
||||||
if msg.To == "" && (msg.Condition == "" || opCnt > 2) && len(msg.RegistrationIDs) == 0 {
|
if msg.To == "" && (msg.Condition == "" || opCnt > 2) && len(msg.RegistrationIDs) == 0 {
|
||||||
return ErrInvalidTarget
|
return ErrInvalidTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(msg.RegistrationIDs) > 1000 {
|
||||||
|
return ErrToManyRegIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.TimeToLive != nil && *msg.TimeToLive > uint(2419200) {
|
||||||
|
return ErrInvalidTimeToLive
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
|
@ -1,142 +0,0 @@
|
||||||
# Backoff
|
|
||||||
|
|
||||||
A simple exponential backoff counter in Go (Golang)
|
|
||||||
|
|
||||||
[](https://godoc.org/github.com/jpillora/backoff) [](https://circleci.com/gh/jpillora/backoff)
|
|
||||||
|
|
||||||
### Install
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get -v github.com/jpillora/backoff
|
|
||||||
```
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Backoff is a `time.Duration` counter. It starts at `Min`. After every call to `Duration()` it is multiplied by `Factor`. It is capped at `Max`. It returns to `Min` on every call to `Reset()`. `Jitter` adds randomness ([see below](#example-using-jitter)). Used in conjunction with the `time` package.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Simple example
|
|
||||||
|
|
||||||
``` go
|
|
||||||
|
|
||||||
b := &backoff.Backoff{
|
|
||||||
//These are the defaults
|
|
||||||
Min: 100 * time.Millisecond,
|
|
||||||
Max: 10 * time.Second,
|
|
||||||
Factor: 2,
|
|
||||||
Jitter: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
|
|
||||||
fmt.Printf("Reset!\n")
|
|
||||||
b.Reset()
|
|
||||||
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
100ms
|
|
||||||
200ms
|
|
||||||
400ms
|
|
||||||
Reset!
|
|
||||||
100ms
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Example using `net` package
|
|
||||||
|
|
||||||
``` go
|
|
||||||
b := &backoff.Backoff{
|
|
||||||
Max: 5 * time.Minute,
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
conn, err := net.Dial("tcp", "example.com:5309")
|
|
||||||
if err != nil {
|
|
||||||
d := b.Duration()
|
|
||||||
fmt.Printf("%s, reconnecting in %s", err, d)
|
|
||||||
time.Sleep(d)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//connected
|
|
||||||
b.Reset()
|
|
||||||
conn.Write([]byte("hello world!"))
|
|
||||||
// ... Read ... Write ... etc
|
|
||||||
conn.Close()
|
|
||||||
//disconnected
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Example using `Jitter`
|
|
||||||
|
|
||||||
Enabling `Jitter` adds some randomization to the backoff durations. [See Amazon's writeup of performance gains using jitter](http://www.awsarchitectureblog.com/2015/03/backoff.html). Seeding is not necessary but doing so gives repeatable results.
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "math/rand"
|
|
||||||
|
|
||||||
b := &backoff.Backoff{
|
|
||||||
Jitter: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
rand.Seed(42)
|
|
||||||
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
|
|
||||||
fmt.Printf("Reset!\n")
|
|
||||||
b.Reset()
|
|
||||||
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
fmt.Printf("%s\n", b.Duration())
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
100ms
|
|
||||||
106.600049ms
|
|
||||||
281.228155ms
|
|
||||||
Reset!
|
|
||||||
100ms
|
|
||||||
104.381845ms
|
|
||||||
214.957989ms
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Documentation
|
|
||||||
|
|
||||||
https://godoc.org/github.com/jpillora/backoff
|
|
||||||
|
|
||||||
#### Credits
|
|
||||||
|
|
||||||
Ported from some JavaScript written by [@tj](https://github.com/tj)
|
|
||||||
|
|
||||||
#### MIT License
|
|
||||||
|
|
||||||
Copyright © 2015 Jaime Pillora <dev@jpillora.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
'Software'), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
@ -1,81 +0,0 @@
|
||||||
// Package backoff provides an exponential-backoff implementation.
|
|
||||||
package backoff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Backoff is a time.Duration counter, starting at Min. After every call to
|
|
||||||
// the Duration method the current timing is multiplied by Factor, but it
|
|
||||||
// never exceeds Max.
|
|
||||||
//
|
|
||||||
// Backoff is not generally concurrent-safe, but the ForAttempt method can
|
|
||||||
// be used concurrently.
|
|
||||||
type Backoff struct {
|
|
||||||
//Factor is the multiplying factor for each increment step
|
|
||||||
attempt, Factor float64
|
|
||||||
//Jitter eases contention by randomizing backoff steps
|
|
||||||
Jitter bool
|
|
||||||
//Min and Max are the minimum and maximum values of the counter
|
|
||||||
Min, Max time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration returns the duration for the current attempt before incrementing
|
|
||||||
// the attempt counter. See ForAttempt.
|
|
||||||
func (b *Backoff) Duration() time.Duration {
|
|
||||||
d := b.ForAttempt(b.attempt)
|
|
||||||
b.attempt++
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForAttempt returns the duration for a specific attempt. This is useful if
|
|
||||||
// you have a large number of independent Backoffs, but don't want use
|
|
||||||
// unnecessary memory storing the Backoff parameters per Backoff. The first
|
|
||||||
// attempt should be 0.
|
|
||||||
//
|
|
||||||
// ForAttempt is concurrent-safe.
|
|
||||||
func (b *Backoff) ForAttempt(attempt float64) time.Duration {
|
|
||||||
// Zero-values are nonsensical, so we use
|
|
||||||
// them to apply defaults
|
|
||||||
min := b.Min
|
|
||||||
if min <= 0 {
|
|
||||||
min = 100 * time.Millisecond
|
|
||||||
}
|
|
||||||
max := b.Max
|
|
||||||
if max <= 0 {
|
|
||||||
max = 10 * time.Second
|
|
||||||
}
|
|
||||||
if min >= max {
|
|
||||||
// short-circuit
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
factor := b.Factor
|
|
||||||
if factor <= 0 {
|
|
||||||
factor = 2
|
|
||||||
}
|
|
||||||
//calculate this duration
|
|
||||||
minf := float64(min)
|
|
||||||
durf := minf * math.Pow(factor, attempt)
|
|
||||||
if b.Jitter {
|
|
||||||
durf = rand.Float64()*(durf-minf) + minf
|
|
||||||
}
|
|
||||||
dur := time.Duration(durf)
|
|
||||||
if dur > max {
|
|
||||||
//cap!
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return dur
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset restarts the current attempt counter at zero.
|
|
||||||
func (b *Backoff) Reset() {
|
|
||||||
b.attempt = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt returns the current attempt counter value.
|
|
||||||
func (b *Backoff) Attempt() float64 {
|
|
||||||
return b.attempt
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Manuel Martínez-Almeida
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,54 +0,0 @@
|
||||||
#Server-Sent Events [](https://godoc.org/github.com/manucorporat/sse) [](https://travis-ci.org/manucorporat/sse)
|
|
||||||
|
|
||||||
Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is [standardized as part of HTML5[1] by the W3C](http://www.w3.org/TR/2009/WD-eventsource-20091029/).
|
|
||||||
|
|
||||||
- [Real world demostration using Gin](http://sse.getgin.io/)
|
|
||||||
- [Read this great SSE introduction by the HTML5Rocks guys](http://www.html5rocks.com/en/tutorials/eventsource/basics/)
|
|
||||||
- [Browser support](http://caniuse.com/#feat=eventsource)
|
|
||||||
|
|
||||||
##Sample code
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/manucorporat/sse"
|
|
||||||
|
|
||||||
func httpHandler(w http.ResponseWriter, req *http.Request) {
|
|
||||||
// data can be a primitive like a string, an integer or a float
|
|
||||||
sse.Encode(w, sse.Event{
|
|
||||||
Event: "message",
|
|
||||||
Data: "some data\nmore data",
|
|
||||||
})
|
|
||||||
|
|
||||||
// also a complex type, like a map, a struct or a slice
|
|
||||||
sse.Encode(w, sse.Event{
|
|
||||||
Id: "124",
|
|
||||||
Event: "message",
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"user": "manu",
|
|
||||||
"date": time.Now().Unix(),
|
|
||||||
"content": "hi!",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```
|
|
||||||
event: message
|
|
||||||
data: some data\\nmore data
|
|
||||||
|
|
||||||
id: 124
|
|
||||||
event: message
|
|
||||||
data: {"content":"hi!","date":1431540810,"user":"manu"}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
##Content-Type
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Println(sse.ContentType)
|
|
||||||
```
|
|
||||||
```
|
|
||||||
text/event-stream
|
|
||||||
```
|
|
||||||
|
|
||||||
##Decoding support
|
|
||||||
|
|
||||||
There is a client-side implementation of SSE coming soon.
|
|
|
@ -1,116 +0,0 @@
|
||||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type decoder struct {
|
|
||||||
events []Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func Decode(r io.Reader) ([]Event, error) {
|
|
||||||
var dec decoder
|
|
||||||
return dec.decode(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) dispatchEvent(event Event, data string) {
|
|
||||||
dataLength := len(data)
|
|
||||||
if dataLength > 0 {
|
|
||||||
//If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
|
|
||||||
data = data[:dataLength-1]
|
|
||||||
dataLength--
|
|
||||||
}
|
|
||||||
if dataLength == 0 && event.Event == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if event.Event == "" {
|
|
||||||
event.Event = "message"
|
|
||||||
}
|
|
||||||
event.Data = data
|
|
||||||
d.events = append(d.events, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *decoder) decode(r io.Reader) ([]Event, error) {
|
|
||||||
buf, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentEvent Event
|
|
||||||
var dataBuffer *bytes.Buffer = new(bytes.Buffer)
|
|
||||||
// TODO (and unit tests)
|
|
||||||
// Lines must be separated by either a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair,
|
|
||||||
// a single U+000A LINE FEED (LF) character,
|
|
||||||
// or a single U+000D CARRIAGE RETURN (CR) character.
|
|
||||||
lines := bytes.Split(buf, []byte{'\n'})
|
|
||||||
for _, line := range lines {
|
|
||||||
if len(line) == 0 {
|
|
||||||
// If the line is empty (a blank line). Dispatch the event.
|
|
||||||
d.dispatchEvent(currentEvent, dataBuffer.String())
|
|
||||||
|
|
||||||
// reset current event and data buffer
|
|
||||||
currentEvent = Event{}
|
|
||||||
dataBuffer.Reset()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if line[0] == byte(':') {
|
|
||||||
// If the line starts with a U+003A COLON character (:), ignore the line.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var field, value []byte
|
|
||||||
colonIndex := bytes.IndexRune(line, ':')
|
|
||||||
if colonIndex != -1 {
|
|
||||||
// If the line contains a U+003A COLON character character (:)
|
|
||||||
// Collect the characters on the line before the first U+003A COLON character (:),
|
|
||||||
// and let field be that string.
|
|
||||||
field = line[:colonIndex]
|
|
||||||
// Collect the characters on the line after the first U+003A COLON character (:),
|
|
||||||
// and let value be that string.
|
|
||||||
value = line[colonIndex+1:]
|
|
||||||
// If value starts with a single U+0020 SPACE character, remove it from value.
|
|
||||||
if len(value) > 0 && value[0] == ' ' {
|
|
||||||
value = value[1:]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, the string is not empty but does not contain a U+003A COLON character character (:)
|
|
||||||
// Use the whole line as the field name, and the empty string as the field value.
|
|
||||||
field = line
|
|
||||||
value = []byte{}
|
|
||||||
}
|
|
||||||
// The steps to process the field given a field name and a field value depend on the field name,
|
|
||||||
// as given in the following list. Field names must be compared literally,
|
|
||||||
// with no case folding performed.
|
|
||||||
switch string(field) {
|
|
||||||
case "event":
|
|
||||||
// Set the event name buffer to field value.
|
|
||||||
currentEvent.Event = string(value)
|
|
||||||
case "id":
|
|
||||||
// Set the event stream's last event ID to the field value.
|
|
||||||
currentEvent.Id = string(value)
|
|
||||||
case "retry":
|
|
||||||
// If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
|
|
||||||
// then interpret the field value as an integer in base ten, and set the event stream's reconnection time to that integer.
|
|
||||||
// Otherwise, ignore the field.
|
|
||||||
currentEvent.Id = string(value)
|
|
||||||
case "data":
|
|
||||||
// Append the field value to the data buffer,
|
|
||||||
dataBuffer.Write(value)
|
|
||||||
// then append a single U+000A LINE FEED (LF) character to the data buffer.
|
|
||||||
dataBuffer.WriteString("\n")
|
|
||||||
default:
|
|
||||||
//Otherwise. The field is ignored.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Once the end of the file is reached, the user agent must dispatch the event one final time.
|
|
||||||
d.dispatchEvent(currentEvent, dataBuffer.String())
|
|
||||||
|
|
||||||
return d.events, nil
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server-Sent Events
|
|
||||||
// W3C Working Draft 29 October 2009
|
|
||||||
// http://www.w3.org/TR/2009/WD-eventsource-20091029/
|
|
||||||
|
|
||||||
const ContentType = "text/event-stream"
|
|
||||||
|
|
||||||
var contentType = []string{ContentType}
|
|
||||||
var noCache = []string{"no-cache"}
|
|
||||||
|
|
||||||
var fieldReplacer = strings.NewReplacer(
|
|
||||||
"\n", "\\n",
|
|
||||||
"\r", "\\r")
|
|
||||||
|
|
||||||
var dataReplacer = strings.NewReplacer(
|
|
||||||
"\n", "\ndata:",
|
|
||||||
"\r", "\\r")
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
Event string
|
|
||||||
Id string
|
|
||||||
Retry uint
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Encode(writer io.Writer, event Event) error {
|
|
||||||
w := checkWriter(writer)
|
|
||||||
writeId(w, event.Id)
|
|
||||||
writeEvent(w, event.Event)
|
|
||||||
writeRetry(w, event.Retry)
|
|
||||||
return writeData(w, event.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeId(w stringWriter, id string) {
|
|
||||||
if len(id) > 0 {
|
|
||||||
w.WriteString("id:")
|
|
||||||
fieldReplacer.WriteString(w, id)
|
|
||||||
w.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeEvent(w stringWriter, event string) {
|
|
||||||
if len(event) > 0 {
|
|
||||||
w.WriteString("event:")
|
|
||||||
fieldReplacer.WriteString(w, event)
|
|
||||||
w.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeRetry(w stringWriter, retry uint) {
|
|
||||||
if retry > 0 {
|
|
||||||
w.WriteString("retry:")
|
|
||||||
w.WriteString(strconv.FormatUint(uint64(retry), 10))
|
|
||||||
w.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeData(w stringWriter, data interface{}) error {
|
|
||||||
w.WriteString("data:")
|
|
||||||
switch kindOfData(data) {
|
|
||||||
case reflect.Struct, reflect.Slice, reflect.Map:
|
|
||||||
err := json.NewEncoder(w).Encode(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteString("\n")
|
|
||||||
default:
|
|
||||||
dataReplacer.WriteString(w, fmt.Sprint(data))
|
|
||||||
w.WriteString("\n\n")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Event) Render(w http.ResponseWriter) error {
|
|
||||||
header := w.Header()
|
|
||||||
header["Content-Type"] = contentType
|
|
||||||
|
|
||||||
if _, exist := header["Cache-Control"]; !exist {
|
|
||||||
header["Cache-Control"] = noCache
|
|
||||||
}
|
|
||||||
return Encode(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func kindOfData(data interface{}) reflect.Kind {
|
|
||||||
value := reflect.ValueOf(data)
|
|
||||||
valueType := value.Kind()
|
|
||||||
if valueType == reflect.Ptr {
|
|
||||||
valueType = value.Elem().Kind()
|
|
||||||
}
|
|
||||||
return valueType
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package sse
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
type stringWriter interface {
|
|
||||||
io.Writer
|
|
||||||
WriteString(string) (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringWrapper struct {
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w stringWrapper) WriteString(str string) (int, error) {
|
|
||||||
return w.Writer.Write([]byte(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkWriter(writer io.Writer) stringWriter {
|
|
||||||
if w, ok := writer.(stringWriter); ok {
|
|
||||||
return w
|
|
||||||
} else {
|
|
||||||
return stringWrapper{writer}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,4 +0,0 @@
|
||||||
go-xmpp
|
|
||||||
=======
|
|
||||||
|
|
||||||
go xmpp library (original was written by russ cox )
|
|
|
@ -1,929 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// TODO(rsc):
|
|
||||||
// More precise error handling.
|
|
||||||
// Presence functionality.
|
|
||||||
// TODO(mattn):
|
|
||||||
// Add proxy authentication.
|
|
||||||
|
|
||||||
// Package xmpp implements a simple Google Talk client
|
|
||||||
// using the XMPP protocol described in RFC 3920 and RFC 3921.
|
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/xml"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nsStream = "http://etherx.jabber.org/streams"
|
|
||||||
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
|
|
||||||
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
|
|
||||||
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
|
|
||||||
nsClient = "jabber:client"
|
|
||||||
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default TLS configuration options
|
|
||||||
var DefaultConfig tls.Config
|
|
||||||
|
|
||||||
// Cookie is a unique XMPP session identifier
|
|
||||||
type Cookie uint64
|
|
||||||
|
|
||||||
func getCookie() Cookie {
|
|
||||||
var buf [8]byte
|
|
||||||
if _, err := rand.Reader.Read(buf[:]); err != nil {
|
|
||||||
panic("Failed to read random bytes: " + err.Error())
|
|
||||||
}
|
|
||||||
return Cookie(binary.LittleEndian.Uint64(buf[:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client holds XMPP connection opitons
|
|
||||||
type Client struct {
|
|
||||||
conn net.Conn // connection to server
|
|
||||||
jid string // Jabber ID for our connection
|
|
||||||
domain string
|
|
||||||
p *xml.Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) JID() string {
|
|
||||||
return c.jid
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(host, user, passwd string) (net.Conn, error) {
|
|
||||||
addr := host
|
|
||||||
|
|
||||||
if strings.TrimSpace(host) == "" {
|
|
||||||
a := strings.SplitN(user, "@", 2)
|
|
||||||
if len(a) == 2 {
|
|
||||||
addr = a[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a := strings.SplitN(host, ":", 2)
|
|
||||||
if len(a) == 1 {
|
|
||||||
addr += ":5222"
|
|
||||||
}
|
|
||||||
proxy := os.Getenv("HTTP_PROXY")
|
|
||||||
if proxy == "" {
|
|
||||||
proxy = os.Getenv("http_proxy")
|
|
||||||
}
|
|
||||||
if proxy != "" {
|
|
||||||
url, err := url.Parse(proxy)
|
|
||||||
if err == nil {
|
|
||||||
addr = url.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c, err := net.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxy != "" {
|
|
||||||
fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host)
|
|
||||||
fmt.Fprintf(c, "Host: %s\r\n", host)
|
|
||||||
fmt.Fprintf(c, "\r\n")
|
|
||||||
br := bufio.NewReader(c)
|
|
||||||
req, _ := http.NewRequest("CONNECT", host, nil)
|
|
||||||
resp, err := http.ReadResponse(br, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
f := strings.SplitN(resp.Status, " ", 2)
|
|
||||||
return nil, errors.New(f[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options are used to specify additional options for new clients, such as a Resource.
|
|
||||||
type Options struct {
|
|
||||||
// Host specifies what host to connect to, as either "hostname" or "hostname:port"
|
|
||||||
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
|
|
||||||
// Default the port to 5222.
|
|
||||||
Host string
|
|
||||||
|
|
||||||
// User specifies what user to authenticate to the remote server.
|
|
||||||
User string
|
|
||||||
|
|
||||||
// Password supplies the password to use for authentication with the remote server.
|
|
||||||
Password string
|
|
||||||
|
|
||||||
// Resource specifies an XMPP client resource, like "bot", instead of accepting one
|
|
||||||
// from the server. Use "" to let the server generate one for your client.
|
|
||||||
Resource string
|
|
||||||
|
|
||||||
// OAuthScope provides go-xmpp the required scope for OAuth2 authentication.
|
|
||||||
OAuthScope string
|
|
||||||
|
|
||||||
// OAuthToken provides go-xmpp with the required OAuth2 token used to authenticate
|
|
||||||
OAuthToken string
|
|
||||||
|
|
||||||
// OAuthXmlNs provides go-xmpp with the required namespaced used for OAuth2 authentication. This is
|
|
||||||
// provided to the server as the xmlns:auth attribute of the OAuth2 authentication request.
|
|
||||||
OAuthXmlNs string
|
|
||||||
|
|
||||||
// TLS Config
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
|
|
||||||
// InsecureAllowUnencryptedAuth permits authentication over a TCP connection that has not been promoted to
|
|
||||||
// TLS by STARTTLS; this could leak authentication information over the network, or permit man in the middle
|
|
||||||
// attacks.
|
|
||||||
InsecureAllowUnencryptedAuth bool
|
|
||||||
|
|
||||||
// NoTLS directs go-xmpp to not use TLS initially to contact the server; instead, a plain old unencrypted
|
|
||||||
// TCP connection should be used. (Can be combined with StartTLS to support STARTTLS-based servers.)
|
|
||||||
NoTLS bool
|
|
||||||
|
|
||||||
// StartTLS directs go-xmpp to STARTTLS if the server supports it; go-xmpp will automatically STARTTLS
|
|
||||||
// if the server requires it regardless of this option.
|
|
||||||
StartTLS bool
|
|
||||||
|
|
||||||
// Debug output
|
|
||||||
Debug bool
|
|
||||||
|
|
||||||
// Use server sessions
|
|
||||||
Session bool
|
|
||||||
|
|
||||||
// Presence Status
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Status message
|
|
||||||
StatusMessage string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient establishes a new Client connection based on a set of Options.
|
|
||||||
func (o Options) NewClient() (*Client, error) {
|
|
||||||
host := o.Host
|
|
||||||
c, err := connect(host, o.User, o.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.LastIndex(o.Host, ":") > 0 {
|
|
||||||
host = host[:strings.LastIndex(o.Host, ":")]
|
|
||||||
}
|
|
||||||
|
|
||||||
client := new(Client)
|
|
||||||
if o.NoTLS {
|
|
||||||
client.conn = c
|
|
||||||
} else {
|
|
||||||
var tlsconn *tls.Conn
|
|
||||||
if o.TLSConfig != nil {
|
|
||||||
tlsconn = tls.Client(c, o.TLSConfig)
|
|
||||||
} else {
|
|
||||||
DefaultConfig.ServerName = host
|
|
||||||
tlsconn = tls.Client(c, &DefaultConfig)
|
|
||||||
}
|
|
||||||
if err = tlsconn.Handshake(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
insecureSkipVerify := DefaultConfig.InsecureSkipVerify
|
|
||||||
if o.TLSConfig != nil {
|
|
||||||
insecureSkipVerify = o.TLSConfig.InsecureSkipVerify
|
|
||||||
}
|
|
||||||
if !insecureSkipVerify {
|
|
||||||
if err = tlsconn.VerifyHostname(host); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.conn = tlsconn
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := client.init(&o); err != nil {
|
|
||||||
client.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new connection to a host given as "hostname" or "hostname:port".
|
|
||||||
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
|
|
||||||
// Default the port to 5222.
|
|
||||||
func NewClient(host, user, passwd string, debug bool) (*Client, error) {
|
|
||||||
opts := Options{
|
|
||||||
Host: host,
|
|
||||||
User: user,
|
|
||||||
Password: passwd,
|
|
||||||
Debug: debug,
|
|
||||||
Session: false,
|
|
||||||
}
|
|
||||||
return opts.NewClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientNoTLS creates a new client without TLS
|
|
||||||
func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
|
|
||||||
opts := Options{
|
|
||||||
Host: host,
|
|
||||||
User: user,
|
|
||||||
Password: passwd,
|
|
||||||
NoTLS: true,
|
|
||||||
Debug: debug,
|
|
||||||
Session: false,
|
|
||||||
}
|
|
||||||
return opts.NewClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the XMPP connection
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
if c.conn != (*tls.Conn)(nil) {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string {
|
|
||||||
h := func(text string) []byte {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write([]byte(text))
|
|
||||||
return h.Sum(nil)
|
|
||||||
}
|
|
||||||
hex := func(bytes []byte) string {
|
|
||||||
return fmt.Sprintf("%x", bytes)
|
|
||||||
}
|
|
||||||
kd := func(secret, data string) []byte {
|
|
||||||
return h(secret + ":" + data)
|
|
||||||
}
|
|
||||||
|
|
||||||
a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr
|
|
||||||
a2 := authenticate + ":" + digestURI
|
|
||||||
response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
func cnonce() string {
|
|
||||||
randSize := big.NewInt(0)
|
|
||||||
randSize.Lsh(big.NewInt(1), 64)
|
|
||||||
cn, err := rand.Int(rand.Reader, randSize)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%016x", cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) init(o *Options) error {
|
|
||||||
|
|
||||||
var domain string
|
|
||||||
var user string
|
|
||||||
a := strings.SplitN(o.User, "@", 2)
|
|
||||||
if len(o.User) > 0 {
|
|
||||||
if len(a) != 2 {
|
|
||||||
return errors.New("xmpp: invalid username (want user@domain): " + o.User)
|
|
||||||
}
|
|
||||||
user = a[0]
|
|
||||||
domain = a[1]
|
|
||||||
} // Otherwise, we'll be attempting ANONYMOUS
|
|
||||||
|
|
||||||
// Declare intent to be a jabber client and gather stream features.
|
|
||||||
f, err := c.startStream(o, domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the server requires we STARTTLS, attempt to do so.
|
|
||||||
if f, err = c.startTLSIfRequired(f, o, domain); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.User == "" && o.Password == "" {
|
|
||||||
foundAnonymous := false
|
|
||||||
for _, m := range f.Mechanisms.Mechanism {
|
|
||||||
if m == "ANONYMOUS" {
|
|
||||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
|
|
||||||
foundAnonymous = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundAnonymous {
|
|
||||||
return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Even digest forms of authentication are unsafe if we do not know that the host
|
|
||||||
// we are talking to is the actual server, and not a man in the middle playing
|
|
||||||
// proxy.
|
|
||||||
if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth {
|
|
||||||
return errors.New("refusing to authenticate over unencrypted TCP connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
mechanism := ""
|
|
||||||
for _, m := range f.Mechanisms.Mechanism {
|
|
||||||
if m == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" {
|
|
||||||
mechanism = m
|
|
||||||
// Oauth authentication: send base64-encoded \x00 user \x00 token.
|
|
||||||
raw := "\x00" + user + "\x00" + o.OAuthToken
|
|
||||||
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
|
||||||
base64.StdEncoding.Encode(enc, []byte(raw))
|
|
||||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+
|
|
||||||
"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if m == "PLAIN" {
|
|
||||||
mechanism = m
|
|
||||||
// Plain authentication: send base64-encoded \x00 user \x00 password.
|
|
||||||
raw := "\x00" + user + "\x00" + o.Password
|
|
||||||
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
|
||||||
base64.StdEncoding.Encode(enc, []byte(raw))
|
|
||||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if m == "DIGEST-MD5" {
|
|
||||||
mechanism = m
|
|
||||||
// Digest-MD5 authentication
|
|
||||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
|
|
||||||
var ch saslChallenge
|
|
||||||
if err = c.p.DecodeElement(&ch, nil); err != nil {
|
|
||||||
return errors.New("unmarshal <challenge>: " + err.Error())
|
|
||||||
}
|
|
||||||
b, err := base64.StdEncoding.DecodeString(string(ch))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tokens := map[string]string{}
|
|
||||||
for _, token := range strings.Split(string(b), ",") {
|
|
||||||
kv := strings.SplitN(strings.TrimSpace(token), "=", 2)
|
|
||||||
if len(kv) == 2 {
|
|
||||||
if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' {
|
|
||||||
kv[1] = kv[1][1 : len(kv[1])-1]
|
|
||||||
}
|
|
||||||
tokens[kv[0]] = kv[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
realm, _ := tokens["realm"]
|
|
||||||
nonce, _ := tokens["nonce"]
|
|
||||||
qop, _ := tokens["qop"]
|
|
||||||
charset, _ := tokens["charset"]
|
|
||||||
cnonceStr := cnonce()
|
|
||||||
digestURI := "xmpp/" + domain
|
|
||||||
nonceCount := fmt.Sprintf("%08x", 1)
|
|
||||||
digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount)
|
|
||||||
message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
|
|
||||||
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
|
|
||||||
|
|
||||||
fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
|
|
||||||
|
|
||||||
var rspauth saslRspAuth
|
|
||||||
if err = c.p.DecodeElement(&rspauth, nil); err != nil {
|
|
||||||
return errors.New("unmarshal <challenge>: " + err.Error())
|
|
||||||
}
|
|
||||||
b, err = base64.StdEncoding.DecodeString(string(rspauth))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(c.conn, "<response xmlns='%s'/>\n", nsSASL)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mechanism == "" {
|
|
||||||
return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Next message should be either success or failure.
|
|
||||||
name, val, err := next(c.p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch v := val.(type) {
|
|
||||||
case *saslSuccess:
|
|
||||||
case *saslFailure:
|
|
||||||
errorMessage := v.Text
|
|
||||||
if errorMessage == "" {
|
|
||||||
// v.Any is type of sub-element in failure,
|
|
||||||
// which gives a description of what failed if there was no text element
|
|
||||||
errorMessage = v.Any.Local
|
|
||||||
}
|
|
||||||
return errors.New("auth failure: " + errorMessage)
|
|
||||||
default:
|
|
||||||
return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we're authenticated, we're supposed to start the stream over again.
|
|
||||||
// Declare intent to be a jabber client.
|
|
||||||
if f, err = c.startStream(o, domain); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a unique cookie
|
|
||||||
cookie := getCookie()
|
|
||||||
|
|
||||||
// Send IQ message asking to bind to the local user name.
|
|
||||||
if o.Resource == "" {
|
|
||||||
fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
|
|
||||||
}
|
|
||||||
var iq clientIQ
|
|
||||||
if err = c.p.DecodeElement(&iq, nil); err != nil {
|
|
||||||
return errors.New("unmarshal <iq>: " + err.Error())
|
|
||||||
}
|
|
||||||
if &iq.Bind == nil {
|
|
||||||
return errors.New("<iq> result missing <bind>")
|
|
||||||
}
|
|
||||||
c.jid = iq.Bind.Jid // our local id
|
|
||||||
c.domain = domain
|
|
||||||
|
|
||||||
if o.Session {
|
|
||||||
//if server support session, open it
|
|
||||||
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're connected and can now receive and send messages.
|
|
||||||
fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake.
|
|
||||||
// f will be updated if the handshake completes, as the new stream's features are typically different from the original.
|
|
||||||
func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) {
|
|
||||||
// whether we start tls is a matter of opinion: the server's and the user's.
|
|
||||||
switch {
|
|
||||||
case f.StartTLS == nil:
|
|
||||||
// the server does not support STARTTLS
|
|
||||||
return f, nil
|
|
||||||
case f.StartTLS.Required != nil:
|
|
||||||
// the server requires STARTTLS.
|
|
||||||
case !o.StartTLS:
|
|
||||||
// the user wants STARTTLS and the server supports it.
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
fmt.Fprintf(c.conn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n")
|
|
||||||
var k tlsProceed
|
|
||||||
if err = c.p.DecodeElement(&k, nil); err != nil {
|
|
||||||
return f, errors.New("unmarshal <proceed>: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
tc := o.TLSConfig
|
|
||||||
if tc == nil {
|
|
||||||
tc = new(tls.Config)
|
|
||||||
*tc = DefaultConfig
|
|
||||||
//TODO(scott): we should consider using the server's address or reverse lookup
|
|
||||||
tc.ServerName = domain
|
|
||||||
}
|
|
||||||
t := tls.Client(c.conn, tc)
|
|
||||||
|
|
||||||
if err = t.Handshake(); err != nil {
|
|
||||||
return f, errors.New("starttls handshake: " + err.Error())
|
|
||||||
}
|
|
||||||
c.conn = t
|
|
||||||
|
|
||||||
// restart our declaration of XMPP stream intentions.
|
|
||||||
tf, err := c.startStream(o, domain)
|
|
||||||
if err != nil {
|
|
||||||
return f, err
|
|
||||||
}
|
|
||||||
return tf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has
|
|
||||||
// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr. The features advertised by the server
|
|
||||||
// will be returned.
|
|
||||||
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
|
|
||||||
if o.Debug {
|
|
||||||
c.p = xml.NewDecoder(tee{c.conn, os.Stderr})
|
|
||||||
} else {
|
|
||||||
c.p = xml.NewDecoder(c.conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+
|
|
||||||
"<stream:stream to='%s' xmlns='%s'\n"+
|
|
||||||
" xmlns:stream='%s' version='1.0'>\n",
|
|
||||||
xmlEscape(domain), nsClient, nsStream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We expect the server to start a <stream>.
|
|
||||||
se, err := nextStart(c.p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if se.Name.Space != nsStream || se.Name.Local != "stream" {
|
|
||||||
return nil, fmt.Errorf("expected <stream> but got <%v> in %v", se.Name.Local, se.Name.Space)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we're in the stream and can use Unmarshal.
|
|
||||||
// Next message should be <features> to tell us authentication options.
|
|
||||||
// See section 4.6 in RFC 3920.
|
|
||||||
f := new(streamFeatures)
|
|
||||||
if err = c.p.DecodeElement(f, nil); err != nil {
|
|
||||||
return f, errors.New("unmarshal <features>: " + err.Error())
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEncrypted will return true if the client is connected using a TLS transport, either because it used.
|
|
||||||
// TLS to connect from the outset, or because it successfully used STARTTLS to promote a TCP connection to TLS.
|
|
||||||
func (c *Client) IsEncrypted() bool {
|
|
||||||
_, ok := c.conn.(*tls.Conn)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat is an incoming or outgoing XMPP chat message.
|
|
||||||
type Chat struct {
|
|
||||||
Remote string
|
|
||||||
Type string
|
|
||||||
Text string
|
|
||||||
Roster Roster
|
|
||||||
Other []string
|
|
||||||
OtherElem []XMLElement
|
|
||||||
Stamp time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Roster []Contact
|
|
||||||
|
|
||||||
type Contact struct {
|
|
||||||
Remote string
|
|
||||||
Name string
|
|
||||||
Group []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Presence is an XMPP presence notification.
|
|
||||||
type Presence struct {
|
|
||||||
From string
|
|
||||||
To string
|
|
||||||
Type string
|
|
||||||
Show string
|
|
||||||
Status string
|
|
||||||
}
|
|
||||||
|
|
||||||
type IQ struct {
|
|
||||||
ID string
|
|
||||||
From string
|
|
||||||
To string
|
|
||||||
Type string
|
|
||||||
Query []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recv waits to receive the next XMPP stanza.
|
|
||||||
// Return type is either a presence notification or a chat message.
|
|
||||||
func (c *Client) Recv() (stanza interface{}, err error) {
|
|
||||||
for {
|
|
||||||
_, val, err := next(c.p)
|
|
||||||
if err != nil {
|
|
||||||
return Chat{}, err
|
|
||||||
}
|
|
||||||
switch v := val.(type) {
|
|
||||||
case *clientMessage:
|
|
||||||
stamp, _ := time.Parse(
|
|
||||||
"2006-01-02T15:04:05Z",
|
|
||||||
v.Delay.Stamp,
|
|
||||||
)
|
|
||||||
chat := Chat{
|
|
||||||
Remote: v.From,
|
|
||||||
Type: v.Type,
|
|
||||||
Text: v.Body,
|
|
||||||
Other: v.OtherStrings(),
|
|
||||||
OtherElem: v.Other,
|
|
||||||
Stamp: stamp,
|
|
||||||
}
|
|
||||||
return chat, nil
|
|
||||||
case *clientQuery:
|
|
||||||
var r Roster
|
|
||||||
for _, item := range v.Item {
|
|
||||||
r = append(r, Contact{item.Jid, item.Name, item.Group})
|
|
||||||
}
|
|
||||||
return Chat{Type: "roster", Roster: r}, nil
|
|
||||||
case *clientPresence:
|
|
||||||
return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil
|
|
||||||
case *clientIQ:
|
|
||||||
if bytes.Equal(v.Query, []byte(`<ping xmlns='urn:xmpp:ping'/>`)) {
|
|
||||||
err := c.SendResultPing(v.ID, v.From)
|
|
||||||
if err != nil {
|
|
||||||
return Chat{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send sends the message wrapped inside an XMPP message stanza body.
|
|
||||||
func (c *Client) Send(chat Chat) (n int, err error) {
|
|
||||||
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<body>%s</body></message>",
|
|
||||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendOrg sends the original text without being wrapped in an XMPP message stanza.
|
|
||||||
func (c *Client) SendOrg(org string) (n int, err error) {
|
|
||||||
return fmt.Fprint(c.conn, org)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SendPresence(presence Presence) (n int, err error) {
|
|
||||||
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendHtml sends the message as HTML as defined by XEP-0071
|
|
||||||
func (c *Client) SendHtml(chat Chat) (n int, err error) {
|
|
||||||
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+
|
|
||||||
"<body>%s</body>"+
|
|
||||||
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>",
|
|
||||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roster asks for the chat roster.
|
|
||||||
func (c *Client) Roster() error {
|
|
||||||
fmt.Fprintf(c.conn, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 3920 C.1 Streams name space
|
|
||||||
type streamFeatures struct {
|
|
||||||
XMLName xml.Name `xml:"http://etherx.jabber.org/streams features"`
|
|
||||||
StartTLS *tlsStartTLS
|
|
||||||
Mechanisms saslMechanisms
|
|
||||||
Bind bindBind
|
|
||||||
Session bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type streamError struct {
|
|
||||||
XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
|
|
||||||
Any xml.Name
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 3920 C.3 TLS name space
|
|
||||||
type tlsStartTLS struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
|
|
||||||
Required *string `xml:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type tlsProceed struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type tlsFailure struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 3920 C.4 SASL name space
|
|
||||||
type saslMechanisms struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
|
|
||||||
Mechanism []string `xml:"mechanism"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type saslAuth struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
|
|
||||||
Mechanism string `xml:",attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type saslChallenge string
|
|
||||||
|
|
||||||
type saslRspAuth string
|
|
||||||
|
|
||||||
type saslResponse string
|
|
||||||
|
|
||||||
type saslAbort struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type saslSuccess struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type saslFailure struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
|
|
||||||
Any xml.Name `xml:",any"`
|
|
||||||
Text string `xml:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 3920 C.5 Resource binding name space
|
|
||||||
type bindBind struct {
|
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
|
|
||||||
Resource string
|
|
||||||
Jid string `xml:"jid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 3921 B.1 jabber:client
|
|
||||||
type clientMessage struct {
|
|
||||||
XMLName xml.Name `xml:"jabber:client message"`
|
|
||||||
From string `xml:"from,attr"`
|
|
||||||
ID string `xml:"id,attr"`
|
|
||||||
To string `xml:"to,attr"`
|
|
||||||
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal
|
|
||||||
|
|
||||||
// These should technically be []clientText, but string is much more convenient.
|
|
||||||
Subject string `xml:"subject"`
|
|
||||||
Body string `xml:"body"`
|
|
||||||
Thread string `xml:"thread"`
|
|
||||||
|
|
||||||
// Any hasn't matched element
|
|
||||||
Other []XMLElement `xml:",any"`
|
|
||||||
|
|
||||||
Delay Delay `xml:"delay"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *clientMessage) OtherStrings() []string {
|
|
||||||
a := make([]string, len(m.Other))
|
|
||||||
for i, e := range m.Other {
|
|
||||||
a[i] = e.String()
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
type XMLElement struct {
|
|
||||||
XMLName xml.Name
|
|
||||||
InnerXML string `xml:",innerxml"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *XMLElement) String() string {
|
|
||||||
r := bytes.NewReader([]byte(e.InnerXML))
|
|
||||||
d := xml.NewDecoder(r)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for {
|
|
||||||
tok, err := d.Token()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch v := tok.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
err = d.Skip()
|
|
||||||
case xml.CharData:
|
|
||||||
_, err = buf.Write(v)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Delay struct {
|
|
||||||
Stamp string `xml:"stamp,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientText struct {
|
|
||||||
Lang string `xml:",attr"`
|
|
||||||
Body string `xml:"chardata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientPresence struct {
|
|
||||||
XMLName xml.Name `xml:"jabber:client presence"`
|
|
||||||
From string `xml:"from,attr"`
|
|
||||||
ID string `xml:"id,attr"`
|
|
||||||
To string `xml:"to,attr"`
|
|
||||||
Type string `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed
|
|
||||||
Lang string `xml:"lang,attr"`
|
|
||||||
|
|
||||||
Show string `xml:"show"` // away, chat, dnd, xa
|
|
||||||
Status string `xml:"status"` // sb []clientText
|
|
||||||
Priority string `xml:"priority,attr"`
|
|
||||||
Error *clientError
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientIQ struct { // info/query
|
|
||||||
XMLName xml.Name `xml:"jabber:client iq"`
|
|
||||||
From string `xml:"from,attr"`
|
|
||||||
ID string `xml:"id,attr"`
|
|
||||||
To string `xml:"to,attr"`
|
|
||||||
Type string `xml:"type,attr"` // error, get, result, set
|
|
||||||
Query []byte `xml:",innerxml"`
|
|
||||||
Error clientError
|
|
||||||
Bind bindBind
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientError struct {
|
|
||||||
XMLName xml.Name `xml:"jabber:client error"`
|
|
||||||
Code string `xml:",attr"`
|
|
||||||
Type string `xml:",attr"`
|
|
||||||
Any xml.Name
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientQuery struct {
|
|
||||||
Item []rosterItem
|
|
||||||
}
|
|
||||||
|
|
||||||
type rosterItem struct {
|
|
||||||
XMLName xml.Name `xml:"jabber:iq:roster item"`
|
|
||||||
Jid string `xml:",attr"`
|
|
||||||
Name string `xml:",attr"`
|
|
||||||
Subscription string `xml:",attr"`
|
|
||||||
Group []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan XML token stream to find next StartElement.
|
|
||||||
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
|
|
||||||
for {
|
|
||||||
t, err := p.Token()
|
|
||||||
if err != nil && err != io.EOF || t == nil {
|
|
||||||
return xml.StartElement{}, err
|
|
||||||
}
|
|
||||||
switch t := t.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan XML token stream for next element and save into val.
|
|
||||||
// If val == nil, allocate new element based on proto map.
|
|
||||||
// Either way, return val.
|
|
||||||
func next(p *xml.Decoder) (xml.Name, interface{}, error) {
|
|
||||||
// Read start element to find out what type we want.
|
|
||||||
se, err := nextStart(p)
|
|
||||||
if err != nil {
|
|
||||||
return xml.Name{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put it in an interface and allocate one.
|
|
||||||
var nv interface{}
|
|
||||||
switch se.Name.Space + " " + se.Name.Local {
|
|
||||||
case nsStream + " features":
|
|
||||||
nv = &streamFeatures{}
|
|
||||||
case nsStream + " error":
|
|
||||||
nv = &streamError{}
|
|
||||||
case nsTLS + " starttls":
|
|
||||||
nv = &tlsStartTLS{}
|
|
||||||
case nsTLS + " proceed":
|
|
||||||
nv = &tlsProceed{}
|
|
||||||
case nsTLS + " failure":
|
|
||||||
nv = &tlsFailure{}
|
|
||||||
case nsSASL + " mechanisms":
|
|
||||||
nv = &saslMechanisms{}
|
|
||||||
case nsSASL + " challenge":
|
|
||||||
nv = ""
|
|
||||||
case nsSASL + " response":
|
|
||||||
nv = ""
|
|
||||||
case nsSASL + " abort":
|
|
||||||
nv = &saslAbort{}
|
|
||||||
case nsSASL + " success":
|
|
||||||
nv = &saslSuccess{}
|
|
||||||
case nsSASL + " failure":
|
|
||||||
nv = &saslFailure{}
|
|
||||||
case nsBind + " bind":
|
|
||||||
nv = &bindBind{}
|
|
||||||
case nsClient + " message":
|
|
||||||
nv = &clientMessage{}
|
|
||||||
case nsClient + " presence":
|
|
||||||
nv = &clientPresence{}
|
|
||||||
case nsClient + " iq":
|
|
||||||
nv = &clientIQ{}
|
|
||||||
case nsClient + " error":
|
|
||||||
nv = &clientError{}
|
|
||||||
default:
|
|
||||||
return xml.Name{}, nil, errors.New("unexpected XMPP message " +
|
|
||||||
se.Name.Space + " <" + se.Name.Local + "/>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal into that storage.
|
|
||||||
if err = p.DecodeElement(nv, &se); err != nil {
|
|
||||||
return xml.Name{}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return se.Name, nv, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var xmlSpecial = map[byte]string{
|
|
||||||
'<': "<",
|
|
||||||
'>': ">",
|
|
||||||
'"': """,
|
|
||||||
'\'': "'",
|
|
||||||
'&': "&",
|
|
||||||
}
|
|
||||||
|
|
||||||
func xmlEscape(s string) string {
|
|
||||||
var b bytes.Buffer
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
c := s[i]
|
|
||||||
if s, ok := xmlSpecial[c]; ok {
|
|
||||||
b.WriteString(s)
|
|
||||||
} else {
|
|
||||||
b.WriteByte(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type tee struct {
|
|
||||||
r io.Reader
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t tee) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = t.r.Read(p)
|
|
||||||
if n > 0 {
|
|
||||||
t.w.Write(p[0:n])
|
|
||||||
t.w.Write([]byte("\n"))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
const IQTypeGet = "get"
|
|
||||||
const IQTypeSet = "set"
|
|
||||||
const IQTypeResult = "result"
|
|
||||||
|
|
||||||
func (c *Client) Discovery() (string, error) {
|
|
||||||
const namespace = "http://jabber.org/protocol/disco#items"
|
|
||||||
// use getCookie for a pseudo random id.
|
|
||||||
reqID := strconv.FormatUint(uint64(getCookie()), 10)
|
|
||||||
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawInformationQuery sends an information query request to the server.
|
|
||||||
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
|
||||||
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
|
|
||||||
_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
|
||||||
return id, err
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
// Copyright 2013 Flo Lauber <dev@qatfy.at>. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// TODO(flo):
|
|
||||||
// - support password protected MUC rooms
|
|
||||||
// - cleanup signatures of join/leave functions
|
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nsMUC = "http://jabber.org/protocol/muc"
|
|
||||||
nsMUCUser = "http://jabber.org/protocol/muc#user"
|
|
||||||
NoHistory = 0
|
|
||||||
CharHistory = 1
|
|
||||||
StanzaHistory = 2
|
|
||||||
SecondsHistory = 3
|
|
||||||
SinceHistory = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send sends room topic wrapped inside an XMPP message stanza body.
|
|
||||||
func (c *Client) SendTopic(chat Chat) (n int, err error) {
|
|
||||||
return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
|
|
||||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
|
|
||||||
if nick == "" {
|
|
||||||
nick = c.jid
|
|
||||||
}
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
|
|
||||||
"<x xmlns='%s'>"+
|
|
||||||
"<history maxchars='0'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// xep-0045 7.2
|
|
||||||
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) {
|
|
||||||
if nick == "" {
|
|
||||||
nick = c.jid
|
|
||||||
}
|
|
||||||
switch history_type {
|
|
||||||
case NoHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s' />\n" +
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
|
||||||
case CharHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<history maxchars='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
|
||||||
case StanzaHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<history maxstanzas='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
|
||||||
case SecondsHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<history seconds='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
|
||||||
case SinceHistory:
|
|
||||||
if history_date != nil {
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<history since='%s'/></x>\n" +
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, errors.New("Unknown history option")
|
|
||||||
}
|
|
||||||
|
|
||||||
// xep-0045 7.2.6
|
|
||||||
func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) {
|
|
||||||
if nick == "" {
|
|
||||||
nick = c.jid
|
|
||||||
}
|
|
||||||
switch history_type {
|
|
||||||
case NoHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
|
|
||||||
case CharHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history maxchars='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
|
||||||
case StanzaHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history maxstanzas='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
|
||||||
case SecondsHistory:
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history seconds='%d'/></x>\n"+
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
|
||||||
case SinceHistory:
|
|
||||||
if history_date != nil {
|
|
||||||
return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
|
|
||||||
"<x xmlns='%s'>\n" +
|
|
||||||
"<password>%s</password>\n"+
|
|
||||||
"<history since='%s'/></x>\n" +
|
|
||||||
"</presence>",
|
|
||||||
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, errors.New("Unknown history option")
|
|
||||||
}
|
|
||||||
|
|
||||||
// xep-0045 7.14
|
|
||||||
func (c *Client) LeaveMUC(jid string) (n int, err error) {
|
|
||||||
return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />",
|
|
||||||
c.jid, xmlEscape(jid))
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) PingC2S(jid, server string) error {
|
|
||||||
if jid == "" {
|
|
||||||
jid = c.jid
|
|
||||||
}
|
|
||||||
if server == "" {
|
|
||||||
server = c.domain
|
|
||||||
}
|
|
||||||
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
|
|
||||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
|
||||||
"</iq>",
|
|
||||||
xmlEscape(jid), xmlEscape(server))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) PingS2S(fromServer, toServer string) error {
|
|
||||||
_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
|
|
||||||
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
|
||||||
"</iq>",
|
|
||||||
xmlEscape(fromServer), xmlEscape(toServer))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SendResultPing(id, toServer string) error {
|
|
||||||
_, err := fmt.Fprintf(c.conn, "<iq type='result' to='%s' id='%s'/>",
|
|
||||||
xmlEscape(toServer), xmlEscape(id))
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) ApproveSubscription(jid string) {
|
|
||||||
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RevokeSubscription(jid string) {
|
|
||||||
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) RequestSubscription(jid string) {
|
|
||||||
fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>",
|
|
||||||
xmlEscape(jid))
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
# How to contribute
|
|
||||||
|
|
||||||
We definitely welcome patches and contribution to this project!
|
|
||||||
|
|
||||||
### Legal requirements
|
|
||||||
|
|
||||||
In order to protect both you and ourselves, you will need to sign the
|
|
||||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
|
||||||
|
|
||||||
You may have already signed it for other Google projects.
|
|
|
@ -1 +0,0 @@
|
||||||
Paul Borman <borman@google.com>
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,13 +0,0 @@
|
||||||
This project was automatically exported from code.google.com/p/go-uuid
|
|
||||||
|
|
||||||
# uuid 
|
|
||||||
The uuid package generates and inspects UUIDs based on [RFC 4122](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services.
|
|
||||||
|
|
||||||
###### Install
|
|
||||||
`go get github.com/pborman/uuid`
|
|
||||||
|
|
||||||
###### Documentation
|
|
||||||
[](http://godoc.org/github.com/pborman/uuid)
|
|
||||||
|
|
||||||
Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here:
|
|
||||||
http://godoc.org/github.com/pborman/uuid
|
|
|
@ -1,84 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Domain represents a Version 2 domain
|
|
||||||
type Domain byte
|
|
||||||
|
|
||||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
|
||||||
const (
|
|
||||||
Person = Domain(0)
|
|
||||||
Group = Domain(1)
|
|
||||||
Org = Domain(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
|
||||||
//
|
|
||||||
// The domain should be one of Person, Group or Org.
|
|
||||||
// On a POSIX system the id should be the users UID for the Person
|
|
||||||
// domain and the users GID for the Group. The meaning of id for
|
|
||||||
// the domain Org or on non-POSIX systems is site defined.
|
|
||||||
//
|
|
||||||
// For a given domain/id pair the same token may be returned for up to
|
|
||||||
// 7 minutes and 10 seconds.
|
|
||||||
func NewDCESecurity(domain Domain, id uint32) UUID {
|
|
||||||
uuid := NewUUID()
|
|
||||||
if uuid != nil {
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
|
||||||
uuid[9] = byte(domain)
|
|
||||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
|
||||||
// domain with the id returned by os.Getuid.
|
|
||||||
//
|
|
||||||
// NewDCEPerson(Person, uint32(os.Getuid()))
|
|
||||||
func NewDCEPerson() UUID {
|
|
||||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
|
||||||
// domain with the id returned by os.Getgid.
|
|
||||||
//
|
|
||||||
// NewDCEGroup(Group, uint32(os.Getgid()))
|
|
||||||
func NewDCEGroup() UUID {
|
|
||||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain returns the domain for a Version 2 UUID or false.
|
|
||||||
func (uuid UUID) Domain() (Domain, bool) {
|
|
||||||
if v, _ := uuid.Version(); v != 2 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return Domain(uuid[9]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id returns the id for a Version 2 UUID or false.
|
|
||||||
func (uuid UUID) Id() (uint32, bool) {
|
|
||||||
if v, _ := uuid.Version(); v != 2 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint32(uuid[0:4]), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Domain) String() string {
|
|
||||||
switch d {
|
|
||||||
case Person:
|
|
||||||
return "Person"
|
|
||||||
case Group:
|
|
||||||
return "Group"
|
|
||||||
case Org:
|
|
||||||
return "Org"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Domain%d", int(d))
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// The uuid package generates and inspects UUIDs.
|
|
||||||
//
|
|
||||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
|
|
||||||
package uuid
|
|
|
@ -1,53 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Well known Name Space IDs and UUIDs
|
|
||||||
var (
|
|
||||||
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
|
||||||
NIL = Parse("00000000-0000-0000-0000-000000000000")
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
|
||||||
// data generated by h. The hash should be at least 16 byte in length. The
|
|
||||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
|
||||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
|
||||||
// NewMD5 and NewSHA1.
|
|
||||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(space)
|
|
||||||
h.Write([]byte(data))
|
|
||||||
s := h.Sum(nil)
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
copy(uuid, s)
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
|
||||||
// supplied name space and data.
|
|
||||||
//
|
|
||||||
// NewHash(md5.New(), space, data, 3)
|
|
||||||
func NewMD5(space UUID, data []byte) UUID {
|
|
||||||
return NewHash(md5.New(), space, data, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
|
||||||
// supplied name space and data.
|
|
||||||
//
|
|
||||||
// NewHash(sha1.New(), space, data, 5)
|
|
||||||
func NewSHA1(space UUID, data []byte) UUID {
|
|
||||||
return NewHash(sha1.New(), space, data, 5)
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (u UUID) MarshalText() ([]byte, error) {
|
|
||||||
if len(u) != 16 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var js [36]byte
|
|
||||||
encodeHex(js[:], u)
|
|
||||||
return js[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (u *UUID) UnmarshalText(data []byte) error {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
id := Parse(string(data))
|
|
||||||
if id == nil {
|
|
||||||
return errors.New("invalid UUID")
|
|
||||||
}
|
|
||||||
*u = id
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
|
||||||
func (u UUID) MarshalBinary() ([]byte, error) {
|
|
||||||
return u[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
|
||||||
func (u *UUID) UnmarshalBinary(data []byte) error {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(data) != 16 {
|
|
||||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
|
||||||
}
|
|
||||||
var id [16]byte
|
|
||||||
copy(id[:], data)
|
|
||||||
*u = id[:]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (u Array) MarshalText() ([]byte, error) {
|
|
||||||
var js [36]byte
|
|
||||||
encodeHex(js[:], u[:])
|
|
||||||
return js[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (u *Array) UnmarshalText(data []byte) error {
|
|
||||||
id := Parse(string(data))
|
|
||||||
if id == nil {
|
|
||||||
return errors.New("invalid UUID")
|
|
||||||
}
|
|
||||||
*u = id.Array()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
|
||||||
func (u Array) MarshalBinary() ([]byte, error) {
|
|
||||||
return u[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
|
||||||
func (u *Array) UnmarshalBinary(data []byte) error {
|
|
||||||
if len(data) != 16 {
|
|
||||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
|
||||||
}
|
|
||||||
copy(u[:], data)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
nodeMu sync.Mutex
|
|
||||||
interfaces []net.Interface // cached list of interfaces
|
|
||||||
ifname string // name of interface being used
|
|
||||||
nodeID []byte // hardware for version 1 UUIDs
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeInterface returns the name of the interface from which the NodeID was
|
|
||||||
// derived. The interface "user" is returned if the NodeID was set by
|
|
||||||
// SetNodeID.
|
|
||||||
func NodeInterface() string {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
return ifname
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
|
||||||
// If name is "" then the first usable interface found will be used or a random
|
|
||||||
// Node ID will be generated. If a named interface cannot be found then false
|
|
||||||
// is returned.
|
|
||||||
//
|
|
||||||
// SetNodeInterface never fails when name is "".
|
|
||||||
func SetNodeInterface(name string) bool {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
return setNodeInterface(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setNodeInterface(name string) bool {
|
|
||||||
if interfaces == nil {
|
|
||||||
var err error
|
|
||||||
interfaces, err = net.Interfaces()
|
|
||||||
if err != nil && name != "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ifs := range interfaces {
|
|
||||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
|
||||||
if setNodeID(ifs.HardwareAddr) {
|
|
||||||
ifname = ifs.Name
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We found no interfaces with a valid hardware address. If name
|
|
||||||
// does not specify a specific interface generate a random Node ID
|
|
||||||
// (section 4.1.6)
|
|
||||||
if name == "" {
|
|
||||||
if nodeID == nil {
|
|
||||||
nodeID = make([]byte, 6)
|
|
||||||
}
|
|
||||||
randomBits(nodeID)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
|
||||||
// if not already set.
|
|
||||||
func NodeID() []byte {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
if nodeID == nil {
|
|
||||||
setNodeInterface("")
|
|
||||||
}
|
|
||||||
nid := make([]byte, 6)
|
|
||||||
copy(nid, nodeID)
|
|
||||||
return nid
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
|
||||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
|
||||||
// Node ID is not set.
|
|
||||||
func SetNodeID(id []byte) bool {
|
|
||||||
defer nodeMu.Unlock()
|
|
||||||
nodeMu.Lock()
|
|
||||||
if setNodeID(id) {
|
|
||||||
ifname = "user"
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func setNodeID(id []byte) bool {
|
|
||||||
if len(id) < 6 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if nodeID == nil {
|
|
||||||
nodeID = make([]byte, 6)
|
|
||||||
}
|
|
||||||
copy(nodeID, id)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
|
||||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
|
||||||
func (uuid UUID) NodeID() []byte {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
node := make([]byte, 6)
|
|
||||||
copy(node, uuid[10:])
|
|
||||||
return node
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
// Copyright 2015 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
|
|
||||||
// Currently, database types that map to string and []byte are supported. Please
|
|
||||||
// consult database-specific driver documentation for matching types.
|
|
||||||
func (uuid *UUID) Scan(src interface{}) error {
|
|
||||||
switch src.(type) {
|
|
||||||
case string:
|
|
||||||
// if an empty UUID comes from a table, we return a null UUID
|
|
||||||
if src.(string) == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// see uuid.Parse for required string format
|
|
||||||
parsed := Parse(src.(string))
|
|
||||||
|
|
||||||
if parsed == nil {
|
|
||||||
return errors.New("Scan: invalid UUID format")
|
|
||||||
}
|
|
||||||
|
|
||||||
*uuid = parsed
|
|
||||||
case []byte:
|
|
||||||
b := src.([]byte)
|
|
||||||
|
|
||||||
// if an empty UUID comes from a table, we return a null UUID
|
|
||||||
if len(b) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes a simple slice of bytes if 16 bytes
|
|
||||||
// otherwise attempts to parse
|
|
||||||
if len(b) == 16 {
|
|
||||||
*uuid = UUID(b)
|
|
||||||
} else {
|
|
||||||
u := Parse(string(b))
|
|
||||||
|
|
||||||
if u == nil {
|
|
||||||
return errors.New("Scan: invalid UUID format")
|
|
||||||
}
|
|
||||||
|
|
||||||
*uuid = u
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
|
||||||
// transparently. Currently, UUIDs map to strings. Please consult
|
|
||||||
// database-specific driver documentation for matching types.
|
|
||||||
func (uuid UUID) Value() (driver.Value, error) {
|
|
||||||
return uuid.String(), nil
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
// Copyright 2014 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
|
||||||
// 1582.
|
|
||||||
type Time int64
|
|
||||||
|
|
||||||
const (
|
|
||||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
|
||||||
unix = 2440587 // Julian day of 1 Jan 1970
|
|
||||||
epoch = unix - lillian // Days between epochs
|
|
||||||
g1582 = epoch * 86400 // seconds between epochs
|
|
||||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
timeMu sync.Mutex
|
|
||||||
lasttime uint64 // last time we returned
|
|
||||||
clock_seq uint16 // clock sequence for this run
|
|
||||||
|
|
||||||
timeNow = time.Now // for testing
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
|
||||||
// epoch of 1 Jan 1970.
|
|
||||||
func (t Time) UnixTime() (sec, nsec int64) {
|
|
||||||
sec = int64(t - g1582ns100)
|
|
||||||
nsec = (sec % 10000000) * 100
|
|
||||||
sec /= 10000000
|
|
||||||
return sec, nsec
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
|
||||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
|
||||||
// is returned if the current time cannot be determined.
|
|
||||||
func GetTime() (Time, uint16, error) {
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
timeMu.Lock()
|
|
||||||
return getTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTime() (Time, uint16, error) {
|
|
||||||
t := timeNow()
|
|
||||||
|
|
||||||
// If we don't have a clock sequence already, set one.
|
|
||||||
if clock_seq == 0 {
|
|
||||||
setClockSequence(-1)
|
|
||||||
}
|
|
||||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
|
||||||
|
|
||||||
// If time has gone backwards with this clock sequence then we
|
|
||||||
// increment the clock sequence
|
|
||||||
if now <= lasttime {
|
|
||||||
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
|
|
||||||
}
|
|
||||||
lasttime = now
|
|
||||||
return Time(now), clock_seq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClockSequence returns the current clock sequence, generating one if not
|
|
||||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
|
||||||
//
|
|
||||||
// The uuid package does not use global static storage for the clock sequence or
|
|
||||||
// the last time a UUID was generated. Unless SetClockSequence a new random
|
|
||||||
// clock sequence is generated the first time a clock sequence is requested by
|
|
||||||
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
|
|
||||||
// for
|
|
||||||
func ClockSequence() int {
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
timeMu.Lock()
|
|
||||||
return clockSequence()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clockSequence() int {
|
|
||||||
if clock_seq == 0 {
|
|
||||||
setClockSequence(-1)
|
|
||||||
}
|
|
||||||
return int(clock_seq & 0x3fff)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
|
|
||||||
// -1 causes a new sequence to be generated.
|
|
||||||
func SetClockSequence(seq int) {
|
|
||||||
defer timeMu.Unlock()
|
|
||||||
timeMu.Lock()
|
|
||||||
setClockSequence(seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setClockSequence(seq int) {
|
|
||||||
if seq == -1 {
|
|
||||||
var b [2]byte
|
|
||||||
randomBits(b[:]) // clock sequence
|
|
||||||
seq = int(b[0])<<8 | int(b[1])
|
|
||||||
}
|
|
||||||
old_seq := clock_seq
|
|
||||||
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
|
||||||
if old_seq != clock_seq {
|
|
||||||
lasttime = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
|
||||||
// uuid. It returns false if uuid is not valid. The time is only well defined
|
|
||||||
// for version 1 and 2 UUIDs.
|
|
||||||
func (uuid UUID) Time() (Time, bool) {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
|
||||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
|
||||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
|
||||||
return Time(time), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClockSequence returns the clock sequence encoded in uuid. It returns false
|
|
||||||
// if uuid is not valid. The clock sequence is only well defined for version 1
|
|
||||||
// and 2 UUIDs.
|
|
||||||
func (uuid UUID) ClockSequence() (int, bool) {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// randomBits completely fills slice b with random data.
|
|
||||||
func randomBits(b []byte) {
|
|
||||||
if _, err := io.ReadFull(rander, b); err != nil {
|
|
||||||
panic(err.Error()) // rand should never fail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
|
||||||
var xvalues = [256]byte{
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
}
|
|
||||||
|
|
||||||
// xtob converts the the first two hex bytes of x into a byte.
|
|
||||||
func xtob(x string) (byte, bool) {
|
|
||||||
b1 := xvalues[x[0]]
|
|
||||||
b2 := xvalues[x[1]]
|
|
||||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Array is a pass-by-value UUID that can be used as an effecient key in a map.
|
|
||||||
type Array [16]byte
|
|
||||||
|
|
||||||
// UUID converts uuid into a slice.
|
|
||||||
func (uuid Array) UUID() UUID {
|
|
||||||
return uuid[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of uuid,
|
|
||||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
|
|
||||||
func (uuid Array) String() string {
|
|
||||||
return uuid.UUID().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
|
||||||
// 4122.
|
|
||||||
type UUID []byte
|
|
||||||
|
|
||||||
// A Version represents a UUIDs version.
|
|
||||||
type Version byte
|
|
||||||
|
|
||||||
// A Variant represents a UUIDs variant.
|
|
||||||
type Variant byte
|
|
||||||
|
|
||||||
// Constants returned by Variant.
|
|
||||||
const (
|
|
||||||
Invalid = Variant(iota) // Invalid UUID
|
|
||||||
RFC4122 // The variant specified in RFC4122
|
|
||||||
Reserved // Reserved, NCS backward compatibility.
|
|
||||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
|
||||||
Future // Reserved for future definition.
|
|
||||||
)
|
|
||||||
|
|
||||||
var rander = rand.Reader // random function
|
|
||||||
|
|
||||||
// New returns a new random (version 4) UUID as a string. It is a convenience
|
|
||||||
// function for NewRandom().String().
|
|
||||||
func New() string {
|
|
||||||
return NewRandom().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse decodes s into a UUID or returns nil. Both the UUID form of
|
|
||||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
|
||||||
func Parse(s string) UUID {
|
|
||||||
if len(s) == 36+9 {
|
|
||||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s = s[9:]
|
|
||||||
} else if len(s) != 36 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var uuid [16]byte
|
|
||||||
for i, x := range [16]int{
|
|
||||||
0, 2, 4, 6,
|
|
||||||
9, 11,
|
|
||||||
14, 16,
|
|
||||||
19, 21,
|
|
||||||
24, 26, 28, 30, 32, 34} {
|
|
||||||
if v, ok := xtob(s[x:]); !ok {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
uuid[i] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uuid[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal returns true if uuid1 and uuid2 are equal.
|
|
||||||
func Equal(uuid1, uuid2 UUID) bool {
|
|
||||||
return bytes.Equal(uuid1, uuid2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array returns an array representation of uuid that can be used as a map key.
|
|
||||||
// Array panics if uuid is not valid.
|
|
||||||
func (uuid UUID) Array() Array {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
panic("invalid uuid")
|
|
||||||
}
|
|
||||||
var a Array
|
|
||||||
copy(a[:], uuid)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
||||||
// , or "" if uuid is invalid.
|
|
||||||
func (uuid UUID) String() string {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var buf [36]byte
|
|
||||||
encodeHex(buf[:], uuid)
|
|
||||||
return string(buf[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// URN returns the RFC 2141 URN form of uuid,
|
|
||||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
|
||||||
func (uuid UUID) URN() string {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var buf [36 + 9]byte
|
|
||||||
copy(buf[:], "urn:uuid:")
|
|
||||||
encodeHex(buf[9:], uuid)
|
|
||||||
return string(buf[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeHex(dst []byte, uuid UUID) {
|
|
||||||
hex.Encode(dst[:], uuid[:4])
|
|
||||||
dst[8] = '-'
|
|
||||||
hex.Encode(dst[9:13], uuid[4:6])
|
|
||||||
dst[13] = '-'
|
|
||||||
hex.Encode(dst[14:18], uuid[6:8])
|
|
||||||
dst[18] = '-'
|
|
||||||
hex.Encode(dst[19:23], uuid[8:10])
|
|
||||||
dst[23] = '-'
|
|
||||||
hex.Encode(dst[24:], uuid[10:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant returns the variant encoded in uuid. It returns Invalid if
|
|
||||||
// uuid is invalid.
|
|
||||||
func (uuid UUID) Variant() Variant {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return Invalid
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case (uuid[8] & 0xc0) == 0x80:
|
|
||||||
return RFC4122
|
|
||||||
case (uuid[8] & 0xe0) == 0xc0:
|
|
||||||
return Microsoft
|
|
||||||
case (uuid[8] & 0xe0) == 0xe0:
|
|
||||||
return Future
|
|
||||||
default:
|
|
||||||
return Reserved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the version of uuid. It returns false if uuid is not
|
|
||||||
// valid.
|
|
||||||
func (uuid UUID) Version() (Version, bool) {
|
|
||||||
if len(uuid) != 16 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return Version(uuid[6] >> 4), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
if v > 15 {
|
|
||||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("VERSION_%d", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Variant) String() string {
|
|
||||||
switch v {
|
|
||||||
case RFC4122:
|
|
||||||
return "RFC4122"
|
|
||||||
case Reserved:
|
|
||||||
return "Reserved"
|
|
||||||
case Microsoft:
|
|
||||||
return "Microsoft"
|
|
||||||
case Future:
|
|
||||||
return "Future"
|
|
||||||
case Invalid:
|
|
||||||
return "Invalid"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("BadVariant%d", int(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
|
||||||
// If r.Read returns an error when the package requests random data then
|
|
||||||
// a panic will be issued.
|
|
||||||
//
|
|
||||||
// Calling SetRand with nil sets the random number generator to the default
|
|
||||||
// generator.
|
|
||||||
func SetRand(r io.Reader) {
|
|
||||||
if r == nil {
|
|
||||||
rander = rand.Reader
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rander = r
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
|
||||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
|
||||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
|
||||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
|
||||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
|
||||||
// return the current NewUUID returns nil.
|
|
||||||
func NewUUID() UUID {
|
|
||||||
if nodeID == nil {
|
|
||||||
SetNodeInterface("")
|
|
||||||
}
|
|
||||||
|
|
||||||
now, seq, err := GetTime()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
|
|
||||||
time_low := uint32(now & 0xffffffff)
|
|
||||||
time_mid := uint16((now >> 32) & 0xffff)
|
|
||||||
time_hi := uint16((now >> 48) & 0x0fff)
|
|
||||||
time_hi |= 0x1000 // Version 1
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(uuid[0:], time_low)
|
|
||||||
binary.BigEndian.PutUint16(uuid[4:], time_mid)
|
|
||||||
binary.BigEndian.PutUint16(uuid[6:], time_hi)
|
|
||||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
|
||||||
copy(uuid[10:], nodeID)
|
|
||||||
|
|
||||||
return uuid
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package uuid
|
|
||||||
|
|
||||||
// Random returns a Random (Version 4) UUID or panics.
|
|
||||||
//
|
|
||||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
|
||||||
// package.
|
|
||||||
//
|
|
||||||
// A note about uniqueness derived from from the UUID Wikipedia entry:
|
|
||||||
//
|
|
||||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
|
||||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
|
||||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
|
||||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
|
||||||
// year and having one duplicate.
|
|
||||||
func NewRandom() UUID {
|
|
||||||
uuid := make([]byte, 16)
|
|
||||||
randomBits([]byte(uuid))
|
|
||||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
|
||||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
|
||||||
return uuid
|
|
||||||
}
|
|
|
@ -8,6 +8,12 @@
|
||||||
"revision": "7f4b1adc791766938c29457bed0703fb9134421a",
|
"revision": "7f4b1adc791766938c29457bed0703fb9134421a",
|
||||||
"revisionTime": "2017-02-15T16:43:24Z"
|
"revisionTime": "2017-02-15T16:43:24Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "DuVew6znkXuUG1wjGQF+pUyXrHU=",
|
||||||
|
"path": "github.com/appleboy/go-fcm",
|
||||||
|
"revision": "5f2cb2866531e4e37c7a9ff5fb7a1536e1ffc566",
|
||||||
|
"revisionTime": "2017-06-01T07:42:50Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "Ab7MUtqX0iq2PUzzBxWpgzPSydw=",
|
"checksumSHA1": "Ab7MUtqX0iq2PUzzBxWpgzPSydw=",
|
||||||
"path": "github.com/asdine/storm",
|
"path": "github.com/asdine/storm",
|
||||||
|
@ -68,12 +74,6 @@
|
||||||
"revision": "346938d642f2ec3594ed81d874461961cd0faa76",
|
"revision": "346938d642f2ec3594ed81d874461961cd0faa76",
|
||||||
"revisionTime": "2016-10-29T20:57:26Z"
|
"revisionTime": "2016-10-29T20:57:26Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"checksumSHA1": "Xqz4X1+t8MTsmX2guYh7RUj9ZvI=",
|
|
||||||
"path": "github.com/edganiukov/fcm",
|
|
||||||
"revision": "cbdb173263e8c5ed323ce370b57d1ee5f743b758",
|
|
||||||
"revisionTime": "2017-03-11T15:33:24Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "rRYbBQkVsd/+EIryahsywNi34Lo=",
|
"checksumSHA1": "rRYbBQkVsd/+EIryahsywNi34Lo=",
|
||||||
"path": "github.com/emirpasic/gods/containers",
|
"path": "github.com/emirpasic/gods/containers",
|
||||||
|
@ -164,42 +164,18 @@
|
||||||
"revision": "553a641470496b2327abcac10b36396bd98e45c9",
|
"revision": "553a641470496b2327abcac10b36396bd98e45c9",
|
||||||
"revisionTime": "2017-02-15T23:32:05Z"
|
"revisionTime": "2017-02-15T23:32:05Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"checksumSHA1": "oqpLCsrXQdvns4RtmcO61+4BiQk=",
|
|
||||||
"path": "github.com/jpillora/backoff",
|
|
||||||
"revision": "f24585d1c70490c0920ab34924f54f726e1416c7",
|
|
||||||
"revisionTime": "2016-12-23T01:09:21Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "b0T0Hzd+zYk+OCDTFMps+jwa/nY=",
|
|
||||||
"path": "github.com/manucorporat/sse",
|
|
||||||
"revision": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d",
|
|
||||||
"revisionTime": "2016-01-26T18:01:36Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "Jyv1TH1YHrCVewmVE8FgSh1+92s=",
|
"checksumSHA1": "Jyv1TH1YHrCVewmVE8FgSh1+92s=",
|
||||||
"path": "github.com/mattn/go-isatty",
|
"path": "github.com/mattn/go-isatty",
|
||||||
"revision": "dda3de49cbfcec471bd7a70e6cc01fcc3ff90109",
|
"revision": "dda3de49cbfcec471bd7a70e6cc01fcc3ff90109",
|
||||||
"revisionTime": "2017-02-16T23:59:08Z"
|
"revisionTime": "2017-02-16T23:59:08Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"checksumSHA1": "+ALxEZ4CWoD1Rd6FIN/FFn2NDUU=",
|
|
||||||
"path": "github.com/mattn/go-xmpp",
|
|
||||||
"revision": "325c112042ffd34aa24365590d3a6fdd1a79c011",
|
|
||||||
"revisionTime": "2017-01-28T00:53:20Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=",
|
"checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=",
|
||||||
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
||||||
"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
|
"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
|
||||||
"revisionTime": "2016-04-24T11:30:07Z"
|
"revisionTime": "2016-04-24T11:30:07Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"checksumSHA1": "Se195FlZ160eaEk/uVx4KdTPSxU=",
|
|
||||||
"path": "github.com/pborman/uuid",
|
|
||||||
"revision": "1b00554d822231195d1babd97ff4a781231955c9",
|
|
||||||
"revisionTime": "2017-01-12T15:04:04Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||||
"path": "github.com/pmezard/go-difflib/difflib",
|
"path": "github.com/pmezard/go-difflib/difflib",
|
||||||
|
|
Loading…
Reference in New Issue