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 build
|
||||
- make embedmd
|
||||
- make test-vendor
|
||||
- coverage all
|
||||
# send coverage report
|
||||
- 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)
|
||||
TAGS ?=
|
||||
LDFLAGS ?= -X 'main.Version=$(VERSION)'
|
||||
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')
|
||||
|
||||
ifneq ($(shell uname), Darwin)
|
||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||
|
@ -90,6 +91,19 @@ $(EXECUTABLE): $(SOURCES)
|
|||
test: fmt-check
|
||||
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
|
||||
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
|
||||
|
||||
* 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 [YAML](https://github.com/go-yaml/yaml) configuration.
|
||||
* Support command line to send single Android or iOS notification.
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/edganiukov/fcm"
|
||||
"github.com/appleboy/go-fcm"
|
||||
apns "github.com/sideshow/apns2"
|
||||
"github.com/sideshow/apns2/certificate"
|
||||
"github.com/sideshow/apns2/payload"
|
||||
|
@ -131,7 +131,7 @@ func CheckMessage(req PushNotification) error {
|
|||
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) {
|
||||
msg = "the message's TimeToLive field must be an integer " +
|
||||
"between 0 and 2419200 (4 weeks)"
|
||||
|
@ -456,7 +456,7 @@ Retry:
|
|||
|
||||
// GetAndroidNotification use for define Android notification.
|
||||
// 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 {
|
||||
notification := &fcm.Message{
|
||||
To: req.To,
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/appleboy/go-fcm"
|
||||
"github.com/appleboy/gorush/config"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/edganiukov/fcm"
|
||||
"github.com/sideshow/apns2"
|
||||
"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
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/edganiukov/fcm?status.svg)](https://godoc.org/github.com/edganiukov/fcm)
|
||||
[![Build Status](https://travis-ci.org/edganiukov/fcm.svg?branch=master)](https://travis-ci.org/edganiukov/fcm)
|
||||
[![Go Report Card](https://goreportcard.com/badge/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).
|
||||
|
||||
More information on [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/)
|
||||
|
||||
### Getting Started
|
||||
-------------------
|
||||
## Getting Started
|
||||
|
||||
To install fcm, use `go get`:
|
||||
|
||||
```bash
|
||||
go get github.com/edganiukov/fcm
|
||||
go get github.com/appleboy/go-fcm
|
||||
```
|
||||
|
||||
or `govendor`:
|
||||
|
||||
```bash
|
||||
govendor fetch github.com/edganiukov/fcm
|
||||
govendor fetch github.com/appleboy/go-fcm
|
||||
```
|
||||
|
||||
or other tool for vendoring.
|
||||
|
||||
### Sample Usage
|
||||
----------------
|
||||
## Sample Usage
|
||||
|
||||
Here is a simple example illustrating how to use FCM library:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
|
@ -65,5 +65,13 @@ func (msg *Message) Validate() error {
|
|||
if msg.To == "" && (msg.Condition == "" || opCnt > 2) && len(msg.RegistrationIDs) == 0 {
|
||||
return ErrInvalidTarget
|
||||
}
|
||||
|
||||
if len(msg.RegistrationIDs) > 1000 {
|
||||
return ErrToManyRegIDs
|
||||
}
|
||||
|
||||
if msg.TimeToLive != nil && *msg.TimeToLive > uint(2419200) {
|
||||
return ErrInvalidTimeToLive
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
# Backoff
|
||||
|
||||
A simple exponential backoff counter in Go (Golang)
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/jpillora/backoff?status.svg)](https://godoc.org/github.com/jpillora/backoff) [![Circle CI](https://circleci.com/gh/jpillora/backoff.svg?style=shield)](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 [![GoDoc](https://godoc.org/github.com/manucorporat/sse?status.svg)](https://godoc.org/github.com/manucorporat/sse) [![Build Status](https://travis-ci.org/manucorporat/sse.svg)](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 ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master)
|
||||
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
|
||||
[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](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",
|
||||
"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=",
|
||||
"path": "github.com/asdine/storm",
|
||||
|
@ -68,12 +74,6 @@
|
|||
"revision": "346938d642f2ec3594ed81d874461961cd0faa76",
|
||||
"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=",
|
||||
"path": "github.com/emirpasic/gods/containers",
|
||||
|
@ -164,42 +164,18 @@
|
|||
"revision": "553a641470496b2327abcac10b36396bd98e45c9",
|
||||
"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=",
|
||||
"path": "github.com/mattn/go-isatty",
|
||||
"revision": "dda3de49cbfcec471bd7a70e6cc01fcc3ff90109",
|
||||
"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=",
|
||||
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
||||
"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
|
||||
"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=",
|
||||
"path": "github.com/pmezard/go-difflib/difflib",
|
||||
|
|
Loading…
Reference in New Issue