Adding all dependencies to Godeps.

This commit is contained in:
Victor Marmol 2014-09-17 21:31:13 -07:00
parent 58e019028d
commit ad47c699bf
99 changed files with 20911 additions and 0 deletions

45
Godeps/Godeps.json generated
View File

@ -1,12 +1,48 @@
{
"ImportPath": "github.com/google/cadvisor",
"GoVersion": "go1.3",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "code.google.com/p/goauth2/oauth",
"Comment": "weekly-56",
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
},
{
"ImportPath": "code.google.com/p/gomock/gomock",
"Rev": "5d0b59c9f277f888d2c70b00908236a30ab90f45"
},
{
"ImportPath": "code.google.com/p/google-api-go-client/bigquery/v2",
"Comment": "release-101",
"Rev": "e1c259484b495133836706f46319f5897f1e9bf6"
},
{
"ImportPath": "code.google.com/p/google-api-go-client/googleapi",
"Comment": "release-101",
"Rev": "e1c259484b495133836706f46319f5897f1e9bf6"
},
{
"ImportPath": "github.com/SeanDolphin/bqschema",
"Rev": "a713d26df274e999ec10e9bbc7e73a1c4791053c"
},
{
"ImportPath": "github.com/coreos/go-systemd/dbus",
"Comment": "v2-27-g97e243d",
"Rev": "97e243d21a8e232e9d8af38ba2366dfcfceebeba"
},
{
"ImportPath": "github.com/docker/docker/pkg/mount",
"Comment": "v1.1.1-402-g487a417",
"Rev": "487a417d9fd074d0e78876072c7d1ebfd398ea7a"
},
{
"ImportPath": "github.com/docker/docker/pkg/symlink",
"Comment": "v1.1.1-402-g487a417",
"Rev": "487a417d9fd074d0e78876072c7d1ebfd398ea7a"
},
{
"ImportPath": "github.com/docker/libcontainer",
"Comment": "v1.2.0-26-g7f9256c",
@ -17,6 +53,15 @@
"Comment": "0.2.1-185-g4fc0e0d",
"Rev": "4fc0e0dbba85f2f799aa77cea594d40267f5bd5a"
},
{
"ImportPath": "github.com/godbus/dbus",
"Comment": "0-7-g939230d",
"Rev": "939230d2086a4f1870e04c52e0a376c25bae0ec4"
},
{
"ImportPath": "github.com/golang/glog",
"Rev": "d1c4472bf2efd3826f2b5bdcc02d8416798d678c"
},
{
"ImportPath": "github.com/influxdb/influxdb/client",
"Comment": "v0.8.0-rc.3-9-g3284662",

View File

@ -0,0 +1,300 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package inotify implements a wrapper for the Linux inotify system.
Example:
watcher, err := inotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
err = watcher.Watch("/tmp")
if err != nil {
log.Fatal(err)
}
for {
select {
case ev := <-watcher.Event:
log.Println("event:", ev)
case err := <-watcher.Error:
log.Println("error:", err)
}
}
*/
package inotify
import (
"errors"
"fmt"
"os"
"strings"
"sync"
"syscall"
"unsafe"
)
type Event struct {
Mask uint32 // Mask of events
Cookie uint32 // Unique cookie associating related events (for rename(2))
Name string // File name (optional)
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
type Watcher struct {
mu sync.Mutex
fd int // File descriptor (as returned by the inotify_init() syscall)
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
Error chan error // Errors are sent on this channel
Event chan *Event // Events are returned on this channel
done chan bool // Channel for sending a "quit message" to the reader goroutine
isClosed bool // Set to true when Close() is first called
}
// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
fd, errno := syscall.InotifyInit()
if fd == -1 {
return nil, os.NewSyscallError("inotify_init", errno)
}
w := &Watcher{
fd: fd,
watches: make(map[string]*watch),
paths: make(map[int]string),
Event: make(chan *Event),
Error: make(chan error),
done: make(chan bool, 1),
}
go w.readEvents()
return w, nil
}
// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
if w.isClosed {
return nil
}
w.isClosed = true
// Send "quit" message to the reader goroutine
w.done <- true
for path := range w.watches {
w.RemoveWatch(path)
}
return nil
}
// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
if w.isClosed {
return errors.New("inotify instance already closed")
}
watchEntry, found := w.watches[path]
if found {
watchEntry.flags |= flags
flags |= syscall.IN_MASK_ADD
}
w.mu.Lock() // synchronize with readEvents goroutine
wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
if err != nil {
w.mu.Unlock()
return &os.PathError{
Op: "inotify_add_watch",
Path: path,
Err: err,
}
}
if !found {
w.watches[path] = &watch{wd: uint32(wd), flags: flags}
w.paths[wd] = path
}
w.mu.Unlock()
return nil
}
// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
return w.AddWatch(path, IN_ALL_EVENTS)
}
// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
watch, ok := w.watches[path]
if !ok {
return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
}
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
if success == -1 {
return os.NewSyscallError("inotify_rm_watch", errno)
}
delete(w.watches, path)
return nil
}
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
var buf [syscall.SizeofInotifyEvent * 4096]byte
for {
n, err := syscall.Read(w.fd, buf[:])
// See if there is a message on the "done" channel
var done bool
select {
case done = <-w.done:
default:
}
// If EOF or a "done" message is received
if n == 0 || done {
// The syscall.Close can be slow. Close
// w.Event first.
close(w.Event)
err := syscall.Close(w.fd)
if err != nil {
w.Error <- os.NewSyscallError("close", err)
}
close(w.Error)
return
}
if n < 0 {
w.Error <- os.NewSyscallError("read", err)
continue
}
if n < syscall.SizeofInotifyEvent {
w.Error <- errors.New("inotify: short read in readEvents()")
continue
}
var offset uint32 = 0
// We don't know how many events we just read into the buffer
// While the offset points to at least one whole event...
for offset <= uint32(n-syscall.SizeofInotifyEvent) {
// Point "raw" to the event in the buffer
raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
event := new(Event)
event.Mask = uint32(raw.Mask)
event.Cookie = uint32(raw.Cookie)
nameLen := uint32(raw.Len)
// If the event happened to the watched directory or the watched file, the kernel
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
w.mu.Lock()
event.Name = w.paths[int(raw.Wd)]
w.mu.Unlock()
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
// The filename is padded with NUL bytes. TrimRight() gets rid of those.
event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
// Send the event on the events channel
w.Event <- event
// Move to the next event in the buffer
offset += syscall.SizeofInotifyEvent + nameLen
}
}
}
// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
var events string = ""
m := e.Mask
for _, b := range eventBits {
if m&b.Value != 0 {
m &^= b.Value
events += "|" + b.Name
}
}
if m != 0 {
events += fmt.Sprintf("|%#x", m)
}
if len(events) > 0 {
events = " == " + events[1:]
}
return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}
const (
// Options for inotify_init() are not exported
// IN_CLOEXEC uint32 = syscall.IN_CLOEXEC
// IN_NONBLOCK uint32 = syscall.IN_NONBLOCK
// Options for AddWatch
IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
IN_ONESHOT uint32 = syscall.IN_ONESHOT
IN_ONLYDIR uint32 = syscall.IN_ONLYDIR
// The "IN_MASK_ADD" option is not exported, as AddWatch
// adds it automatically, if there is already a watch for the given path
// IN_MASK_ADD uint32 = syscall.IN_MASK_ADD
// Events
IN_ACCESS uint32 = syscall.IN_ACCESS
IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS
IN_ATTRIB uint32 = syscall.IN_ATTRIB
IN_CLOSE uint32 = syscall.IN_CLOSE
IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE
IN_CREATE uint32 = syscall.IN_CREATE
IN_DELETE uint32 = syscall.IN_DELETE
IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF
IN_MODIFY uint32 = syscall.IN_MODIFY
IN_MOVE uint32 = syscall.IN_MOVE
IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM
IN_MOVED_TO uint32 = syscall.IN_MOVED_TO
IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF
IN_OPEN uint32 = syscall.IN_OPEN
// Special events
IN_ISDIR uint32 = syscall.IN_ISDIR
IN_IGNORED uint32 = syscall.IN_IGNORED
IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
IN_UNMOUNT uint32 = syscall.IN_UNMOUNT
)
var eventBits = []struct {
Value uint32
Name string
}{
{IN_ACCESS, "IN_ACCESS"},
{IN_ATTRIB, "IN_ATTRIB"},
{IN_CLOSE, "IN_CLOSE"},
{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
{IN_CREATE, "IN_CREATE"},
{IN_DELETE, "IN_DELETE"},
{IN_DELETE_SELF, "IN_DELETE_SELF"},
{IN_MODIFY, "IN_MODIFY"},
{IN_MOVE, "IN_MOVE"},
{IN_MOVED_FROM, "IN_MOVED_FROM"},
{IN_MOVED_TO, "IN_MOVED_TO"},
{IN_MOVE_SELF, "IN_MOVE_SELF"},
{IN_OPEN, "IN_OPEN"},
{IN_ISDIR, "IN_ISDIR"},
{IN_IGNORED, "IN_IGNORED"},
{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
{IN_UNMOUNT, "IN_UNMOUNT"},
}

View File

@ -0,0 +1,107 @@
// Copyright 2010 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.
// +build linux
package inotify
import (
"io/ioutil"
"os"
"sync/atomic"
"testing"
"time"
)
func TestInotifyEvents(t *testing.T) {
// Create an inotify watcher instance and initialize it
watcher, err := NewWatcher()
if err != nil {
t.Fatalf("NewWatcher failed: %s", err)
}
dir, err := ioutil.TempDir("", "inotify")
if err != nil {
t.Fatalf("TempDir failed: %s", err)
}
defer os.RemoveAll(dir)
// Add a watch for "_test"
err = watcher.Watch(dir)
if err != nil {
t.Fatalf("Watch failed: %s", err)
}
// Receive errors on the error channel on a separate goroutine
go func() {
for err := range watcher.Error {
t.Fatalf("error received: %s", err)
}
}()
testFile := dir + "/TestInotifyEvents.testfile"
// Receive events on the event channel on a separate goroutine
eventstream := watcher.Event
var eventsReceived int32 = 0
done := make(chan bool)
go func() {
for event := range eventstream {
// Only count relevant events
if event.Name == testFile {
atomic.AddInt32(&eventsReceived, 1)
t.Logf("event received: %s", event)
} else {
t.Logf("unexpected event received: %s", event)
}
}
done <- true
}()
// Create a file
// This should add at least one event to the inotify event queue
_, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating test file: %s", err)
}
// We expect this event to be received almost immediately, but let's wait 1 s to be sure
time.Sleep(1 * time.Second)
if atomic.AddInt32(&eventsReceived, 0) == 0 {
t.Fatal("inotify event hasn't been received after 1 second")
}
// Try closing the inotify instance
t.Log("calling Close()")
watcher.Close()
t.Log("waiting for the event channel to become closed...")
select {
case <-done:
t.Log("event channel closed")
case <-time.After(1 * time.Second):
t.Fatal("event stream was not closed after 1 second")
}
}
func TestInotifyClose(t *testing.T) {
watcher, _ := NewWatcher()
watcher.Close()
done := make(chan bool)
go func() {
watcher.Close()
done <- true
}()
select {
case <-done:
case <-time.After(50 * time.Millisecond):
t.Fatal("double Close() test failed: second Close() call didn't return")
}
err := watcher.Watch(os.TempDir())
if err == nil {
t.Fatal("expected error on Watch() after Close(), got nil")
}
}

View File

@ -0,0 +1,100 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This program makes a call to the specified API, authenticated with OAuth2.
// a list of example APIs can be found at https://code.google.com/oauthplayground/
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"code.google.com/p/goauth2/oauth"
)
var (
clientId = flag.String("id", "", "Client ID")
clientSecret = flag.String("secret", "", "Client Secret")
scope = flag.String("scope", "https://www.googleapis.com/auth/userinfo.profile", "OAuth scope")
redirectURL = flag.String("redirect_url", "oob", "Redirect URL")
authURL = flag.String("auth_url", "https://accounts.google.com/o/oauth2/auth", "Authentication URL")
tokenURL = flag.String("token_url", "https://accounts.google.com/o/oauth2/token", "Token URL")
requestURL = flag.String("request_url", "https://www.googleapis.com/oauth2/v1/userinfo", "API request")
code = flag.String("code", "", "Authorization Code")
cachefile = flag.String("cache", "cache.json", "Token cache file")
)
const usageMsg = `
To obtain a request token you must specify both -id and -secret.
To obtain Client ID and Secret, see the "OAuth 2 Credentials" section under
the "API Access" tab on this page: https://code.google.com/apis/console/
Once you have completed the OAuth flow, the credentials should be stored inside
the file specified by -cache and you may run without the -id and -secret flags.
`
func main() {
flag.Parse()
// Set up a configuration.
config := &oauth.Config{
ClientId: *clientId,
ClientSecret: *clientSecret,
RedirectURL: *redirectURL,
Scope: *scope,
AuthURL: *authURL,
TokenURL: *tokenURL,
TokenCache: oauth.CacheFile(*cachefile),
}
// Set up a Transport using the config.
transport := &oauth.Transport{Config: config}
// Try to pull the token from the cache; if this fails, we need to get one.
token, err := config.TokenCache.Token()
if err != nil {
if *clientId == "" || *clientSecret == "" {
flag.Usage()
fmt.Fprint(os.Stderr, usageMsg)
os.Exit(2)
}
if *code == "" {
// Get an authorization code from the data provider.
// ("Please ask the user if I can access this resource.")
url := config.AuthCodeURL("")
fmt.Print("Visit this URL to get a code, then run again with -code=YOUR_CODE\n\n")
fmt.Println(url)
return
}
// Exchange the authorization code for an access token.
// ("Here's the code you gave the user, now give me a token!")
token, err = transport.Exchange(*code)
if err != nil {
log.Fatal("Exchange:", err)
}
// (The Exchange method will automatically cache the token.)
fmt.Printf("Token is cached in %v\n", config.TokenCache)
}
// Make the actual request using the cached token to authenticate.
// ("Here's the token, let me in!")
transport.Token = token
// Make the request.
r, err := transport.Client().Get(*requestURL)
if err != nil {
log.Fatal("Get:", err)
}
defer r.Body.Close()
// Write the response to standard output.
io.Copy(os.Stdout, r.Body)
// Send final carriage return, just to be neat.
fmt.Println()
}

View File

@ -0,0 +1 @@
{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"XXXXXXXXXXXX@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/XXXXXXXXXXXX@developer.gserviceaccount.com","client_id":"XXXXXXXXXXXX.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}

View File

@ -0,0 +1,20 @@
Bag Attributes
friendlyName: privatekey
localKeyID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
XXXXxyXXXXXXXxxyxxxX9y0XXYXXXXYXXxXyxxXxXxXXXyXXXXx4yx1xy1xyYxxY
1XxYy38YxXxxxyXxyyxx+xxxxyx1Y1xYx7yx2/Y1XyyXYYYxY5YXxX0xY/Y642yX
zYYxYXzXYxY0Y8y9YxyYXxxX40YyXxxXX4XXxx7XxXxxXyXxYYXxXyxX5XY0Yy2X
1YX0XXyy6YXyXx9XxXxyXX9XXYXxXxXXXXXXxYXYY3Y8Yy311XYYY81XyY14Xyyx
xXyx7xxXXXxxxxyyyX4YYYXyYyYXyxX4XYXYyxXYyx9xy23xXYyXyxYxXxx1XXXY
y98yX6yYxyyyX4Xyx1Xy/0yxxYxXxYYx2xx7yYXXXxYXXXxyXyyYYxx5XX2xxyxy
y6Yyyx0XX3YYYyx9YYXXXX7y0yxXXy+90XYz1y2xyx7yXxX+8X0xYxXXYxxyxYYy
YXx8Yy4yX0Xyxxx6yYX92yxy1YYYzyyyyxy55x/yyXXXYYXYXXzXXxYYxyXY8XXX
+y9+yXxX7XxxyYYxxXYxyY623xxXxYX59x5Y6yYyXYY4YxXXYXXXYxXYxXxXXx6x
YXX7XxXX2X0XY7YXyYy1XXxYXxXxYY1xXXxxxyy+07zXYxYxxXyyxxyxXx1XYy5X
5XYzyxYxXXYyX9XX7xX8xXxx+XXYyYXXXX5YY1x8Yxyx54Xy/1XXyyYXY5YxYyxY
XyyxXyX/YxxXXXxXXYXxyxx63xX/xxyYXXyYzx0XY+YxX5xyYyyxxxXXYX/94XXy
Xx63xYxXyXY3/XXxyyXX15XXXyz08XYY5YYXY/YXy/96x68XyyXXxYyXy4xYXx5x
7yxxyxxYxXxyx3y=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,114 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This program makes a read only call to the Google Cloud Storage API,
// authenticated with OAuth2. A list of example APIs can be found at
// https://code.google.com/oauthplayground/
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"code.google.com/p/goauth2/oauth/jwt"
)
const scope = "https://www.googleapis.com/auth/devstorage.read_only"
var (
secretsFile = flag.String("s", "", "JSON encoded secrets for the service account")
pemFile = flag.String("k", "", "private pem key file for the service account")
)
const usageMsg = `
You must specify -k and -s.
To obtain client secrets and pem, see the "OAuth 2 Credentials" section under
the "API Access" tab on this page: https://code.google.com/apis/console/
Google Cloud Storage must also be turned on in the API console.
`
func main() {
flag.Parse()
if *secretsFile == "" || *pemFile == "" {
flag.Usage()
fmt.Println(usageMsg)
return
}
// Read the secret file bytes into the config.
secretBytes, err := ioutil.ReadFile(*secretsFile)
if err != nil {
log.Fatal("error reading secerets file:", err)
}
var config struct {
Web struct {
ClientEmail string `json:"client_email"`
ClientID string `json:"client_id"`
TokenURI string `json:"token_uri"`
}
}
err = json.Unmarshal(secretBytes, &config)
if err != nil {
log.Fatal("error unmarshalling secerets:", err)
}
// Get the project ID from the client ID.
projectID := strings.SplitN(config.Web.ClientID, "-", 2)[0]
// Read the pem file bytes for the private key.
keyBytes, err := ioutil.ReadFile(*pemFile)
if err != nil {
log.Fatal("error reading private key file:", err)
}
// Craft the ClaimSet and JWT token.
t := jwt.NewToken(config.Web.ClientEmail, scope, keyBytes)
t.ClaimSet.Aud = config.Web.TokenURI
// We need to provide a client.
c := &http.Client{}
// Get the access token.
o, err := t.Assert(c)
if err != nil {
log.Fatal("assertion error:", err)
}
// Refresh token will be missing, but this access_token will be good
// for one hour.
fmt.Printf("access_token = %v\n", o.AccessToken)
fmt.Printf("refresh_token = %v\n", o.RefreshToken)
fmt.Printf("expires %v\n", o.Expiry)
// Form the request to list Google Cloud Storage buckets.
req, err := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
if err != nil {
log.Fatal("http.NewRequest:", err)
}
req.Header.Set("Authorization", "OAuth "+o.AccessToken)
req.Header.Set("x-goog-api-version", "2")
req.Header.Set("x-goog-project-id", projectID)
// Make the request.
r, err := c.Do(req)
if err != nil {
log.Fatal("API request error:", err)
}
defer r.Body.Close()
// Write the response to standard output.
res, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal("error reading API request results:", err)
}
fmt.Printf("\nRESULT:\n%s\n", res)
}

View File

@ -0,0 +1,511 @@
// Copyright 2012 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The jwt package provides support for creating credentials for OAuth2 service
// account requests.
//
// For examples of the package usage please see jwt_test.go.
// Example usage (error handling omitted for brevity):
//
// // Craft the ClaimSet and JWT token.
// iss := "XXXXXXXXXXXX@developer.gserviceaccount.com"
// scope := "https://www.googleapis.com/auth/devstorage.read_only"
// t := jwt.NewToken(iss, scope, pemKeyBytes)
//
// // We need to provide a client.
// c := &http.Client{}
//
// // Get the access token.
// o, _ := t.Assert(c)
//
// // Form the request to the service.
// req, _ := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
// req.Header.Set("Authorization", "OAuth "+o.AccessToken)
// req.Header.Set("x-goog-api-version", "2")
// req.Header.Set("x-goog-project-id", "XXXXXXXXXXXX")
//
// // Make the request.
// result, _ := c.Do(req)
//
// For info on OAuth2 service accounts please see the online documentation.
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
//
package jwt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"code.google.com/p/goauth2/oauth"
)
// These are the default/standard values for this to work for Google service accounts.
const (
stdAlgorithm = "RS256"
stdType = "JWT"
stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
stdGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
stdAud = "https://accounts.google.com/o/oauth2/token"
)
var (
ErrInvalidKey = errors.New("Invalid Key")
)
// base64Encode returns and Base64url encoded version of the input string with any
// trailing "=" stripped.
func base64Encode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// base64Decode decodes the Base64url encoded string
func base64Decode(s string) ([]byte, error) {
// add back missing padding
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.URLEncoding.DecodeString(s)
}
// The JWT claim set contains information about the JWT including the
// permissions being requested (scopes), the target of the token, the issuer,
// the time the token was issued, and the lifetime of the token.
//
// Aud is usually https://accounts.google.com/o/oauth2/token
type ClaimSet struct {
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
Typ string `json:"typ,omitempty"`
Sub string `json:"sub,omitempty"` // Add support for googleapi delegation support
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
PrivateClaims map[string]interface{} `json:"-"`
exp time.Time
iat time.Time
}
// setTimes sets iat and exp to time.Now() and iat.Add(time.Hour) respectively.
//
// Note that these times have nothing to do with the expiration time for the
// access_token returned by the server. These have to do with the lifetime of
// the encoded JWT.
//
// A JWT can be re-used for up to one hour after it was encoded. The access
// token that is granted will also be good for one hour so there is little point
// in trying to use the JWT a second time.
func (c *ClaimSet) setTimes(t time.Time) {
c.iat = t
c.exp = c.iat.Add(time.Hour)
}
var (
jsonStart = []byte{'{'}
jsonEnd = []byte{'}'}
)
// encode returns the Base64url encoded form of the Signature.
func (c *ClaimSet) encode() string {
if c.exp.IsZero() || c.iat.IsZero() {
c.setTimes(time.Now())
}
if c.Aud == "" {
c.Aud = stdAud
}
c.Exp = c.exp.Unix()
c.Iat = c.iat.Unix()
b, err := json.Marshal(c)
if err != nil {
panic(err)
}
if len(c.PrivateClaims) == 0 {
return base64Encode(b)
}
// Marshal private claim set and then append it to b.
prv, err := json.Marshal(c.PrivateClaims)
if err != nil {
panic(fmt.Errorf("Invalid map of private claims %v", c.PrivateClaims))
}
// Concatenate public and private claim JSON objects.
if !bytes.HasSuffix(b, jsonEnd) {
panic(fmt.Errorf("Invalid JSON %s", b))
}
if !bytes.HasPrefix(prv, jsonStart) {
panic(fmt.Errorf("Invalid JSON %s", prv))
}
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
b = append(b, prv[1:]...) // Append private claims.
return base64Encode(b)
}
// Header describes the algorithm and type of token being generated,
// and optionally a KeyID describing additional parameters for the
// signature.
type Header struct {
Algorithm string `json:"alg"`
Type string `json:"typ"`
KeyId string `json:"kid,omitempty"`
}
func (h *Header) encode() string {
b, err := json.Marshal(h)
if err != nil {
panic(err)
}
return base64Encode(b)
}
// A JWT is composed of three parts: a header, a claim set, and a signature.
// The well formed and encoded JWT can then be exchanged for an access token.
//
// The Token is not a JWT, but is is encoded to produce a well formed JWT.
//
// When obtaining a key from the Google API console it will be downloaded in a
// PKCS12 encoding. To use this key you will need to convert it to a PEM file.
// This can be achieved with openssl.
//
// $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out <key.pem>
//
// The contents of this file can then be used as the Key.
type Token struct {
ClaimSet *ClaimSet // claim set used to construct the JWT
Header *Header // header used to construct the JWT
Key []byte // PEM printable encoding of the private key
pKey *rsa.PrivateKey
header string
claim string
sig string
useExternalSigner bool
signer Signer
}
// NewToken returns a filled in *Token based on the standard header,
// and sets the Iat and Exp times based on when the call to Assert is
// made.
func NewToken(iss, scope string, key []byte) *Token {
c := &ClaimSet{
Iss: iss,
Scope: scope,
Aud: stdAud,
}
h := &Header{
Algorithm: stdAlgorithm,
Type: stdType,
}
t := &Token{
ClaimSet: c,
Header: h,
Key: key,
}
return t
}
// Signer is an interface that given a JWT token, returns the header &
// claim (serialized and urlEncoded to a byte slice), along with the
// signature and an error (if any occured). It could modify any data
// to sign (typically the KeyID).
//
// Example usage where a SHA256 hash of the original url-encoded token
// with an added KeyID and secret data is used as a signature:
//
// var privateData = "secret data added to hash, indexed by KeyID"
//
// type SigningService struct{}
//
// func (ss *SigningService) Sign(in *jwt.Token) (newTokenData, sig []byte, err error) {
// in.Header.KeyID = "signing service"
// newTokenData = in.EncodeWithoutSignature()
// dataToSign := fmt.Sprintf("%s.%s", newTokenData, privateData)
// h := sha256.New()
// _, err := h.Write([]byte(dataToSign))
// sig = h.Sum(nil)
// return
// }
type Signer interface {
Sign(in *Token) (tokenData, signature []byte, err error)
}
// NewSignerToken returns a *Token, using an external signer function
func NewSignerToken(iss, scope string, signer Signer) *Token {
t := NewToken(iss, scope, nil)
t.useExternalSigner = true
t.signer = signer
return t
}
// Expired returns a boolean value letting us know if the token has expired.
func (t *Token) Expired() bool {
return t.ClaimSet.exp.Before(time.Now())
}
// Encode constructs and signs a Token returning a JWT ready to use for
// requesting an access token.
func (t *Token) Encode() (string, error) {
var tok string
t.header = t.Header.encode()
t.claim = t.ClaimSet.encode()
err := t.sign()
if err != nil {
return tok, err
}
tok = fmt.Sprintf("%s.%s.%s", t.header, t.claim, t.sig)
return tok, nil
}
// EncodeWithoutSignature returns the url-encoded value of the Token
// before signing has occured (typically for use by external signers).
func (t *Token) EncodeWithoutSignature() string {
t.header = t.Header.encode()
t.claim = t.ClaimSet.encode()
return fmt.Sprintf("%s.%s", t.header, t.claim)
}
// sign computes the signature for a Token. The details for this can be found
// in the OAuth2 Service Account documentation.
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
func (t *Token) sign() error {
if t.useExternalSigner {
fulldata, sig, err := t.signer.Sign(t)
if err != nil {
return err
}
split := strings.Split(string(fulldata), ".")
if len(split) != 2 {
return errors.New("no token returned")
}
t.header = split[0]
t.claim = split[1]
t.sig = base64Encode(sig)
return err
}
ss := fmt.Sprintf("%s.%s", t.header, t.claim)
if t.pKey == nil {
err := t.parsePrivateKey()
if err != nil {
return err
}
}
h := sha256.New()
h.Write([]byte(ss))
b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil))
t.sig = base64Encode(b)
return err
}
// parsePrivateKey converts the Token's Key ([]byte) into a parsed
// rsa.PrivateKey. If the key is not well formed this method will return an
// ErrInvalidKey error.
func (t *Token) parsePrivateKey() error {
block, _ := pem.Decode(t.Key)
if block == nil {
return ErrInvalidKey
}
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
}
var ok bool
t.pKey, ok = parsedKey.(*rsa.PrivateKey)
if !ok {
return ErrInvalidKey
}
return nil
}
// Assert obtains an *oauth.Token from the remote server by encoding and sending
// a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
// refreshed (no refresh_token is returned with the response). Once this token
// expires call this method again to get a fresh one.
func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
var o *oauth.Token
t.ClaimSet.setTimes(time.Now())
u, v, err := t.buildRequest()
if err != nil {
return o, err
}
resp, err := c.PostForm(u, v)
if err != nil {
return o, err
}
o, err = handleResponse(resp)
return o, err
}
// buildRequest sets up the URL values and the proper URL string for making our
// access_token request.
func (t *Token) buildRequest() (string, url.Values, error) {
v := url.Values{}
j, err := t.Encode()
if err != nil {
return t.ClaimSet.Aud, v, err
}
v.Set("grant_type", stdGrantType)
v.Set("assertion", j)
return t.ClaimSet.Aud, v, nil
}
// Used for decoding the response body.
type respBody struct {
IdToken string `json:"id_token"`
Access string `json:"access_token"`
Type string `json:"token_type"`
ExpiresIn time.Duration `json:"expires_in"`
}
// handleResponse returns a filled in *oauth.Token given the *http.Response from
// a *http.Request created by buildRequest.
func handleResponse(r *http.Response) (*oauth.Token, error) {
o := &oauth.Token{}
defer r.Body.Close()
if r.StatusCode != 200 {
return o, errors.New("invalid response: " + r.Status)
}
b := &respBody{}
err := json.NewDecoder(r.Body).Decode(b)
if err != nil {
return o, err
}
o.AccessToken = b.Access
if b.IdToken != "" {
// decode returned id token to get expiry
o.AccessToken = b.IdToken
s := strings.Split(b.IdToken, ".")
if len(s) < 2 {
return nil, errors.New("invalid token received")
}
d, err := base64Decode(s[1])
if err != nil {
return o, err
}
c := &ClaimSet{}
err = json.NewDecoder(bytes.NewBuffer(d)).Decode(c)
if err != nil {
return o, err
}
o.Expiry = time.Unix(c.Exp, 0)
return o, nil
}
o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
return o, nil
}
// Transport implements http.RoundTripper. When configured with a valid
// JWT and OAuth tokens it can be used to make authenticated HTTP requests.
//
// t := &jwt.Transport{jwtToken, oauthToken}
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the OAuth token if it can, updating in place.
type Transport struct {
JWTToken *Token
OAuthToken *oauth.Token
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// Creates a new authenticated transport.
func NewTransport(token *Token) (*Transport, error) {
oa, err := token.Assert(new(http.Client))
if err != nil {
return nil, err
}
return &Transport{
JWTToken: token,
OAuthToken: oa,
}, nil
}
// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
return &http.Client{Transport: t}
}
// Fetches the internal transport.
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// RoundTrip executes a single HTTP transaction using the Transport's
// OAuthToken as authorization headers.
//
// This method will attempt to renew the token if it has expired and may return
// an error related to that token renewal before attempting the client request.
// If the token cannot be renewed a non-nil os.Error value will be returned.
// If the token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
// Sanity check the two tokens
if t.JWTToken == nil {
return nil, fmt.Errorf("no JWT token supplied")
}
if t.OAuthToken == nil {
return nil, fmt.Errorf("no OAuth token supplied")
}
// Refresh the OAuth token if it has expired
if t.OAuthToken.Expired() {
if oa, err := t.JWTToken.Assert(new(http.Client)); err != nil {
return nil, err
} else {
t.OAuthToken = oa
}
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+t.OAuthToken.AccessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}

View File

@ -0,0 +1,486 @@
// Copyright 2012 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// For package documentation please see jwt.go.
//
package jwt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/json"
"encoding/pem"
"io/ioutil"
"net/http"
"testing"
"time"
)
const (
stdHeaderStr = `{"alg":"RS256","typ":"JWT"}`
iss = "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com"
scope = "https://www.googleapis.com/auth/prediction"
exp = 1328554385
iat = 1328550785 // exp + 1 hour
)
// Base64url encoded Header
const headerEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
// Base64url encoded ClaimSet
const claimSetEnc = "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ"
// Base64url encoded Signature
const sigEnc = "olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
// Base64url encoded Token
const tokEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
// Private key for testing
const privateKeyPem = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj
7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/
xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs
SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18
pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk
SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk
nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq
HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y
nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9
IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2
YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU
Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ
vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP
B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl
aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2
eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI
aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk
klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ
CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu
UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg
soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28
bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH
504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL
YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx
BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==
-----END RSA PRIVATE KEY-----`
// Public key to go with the private key for testing
const publicKeyPem = `-----BEGIN CERTIFICATE-----
MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV
MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM
7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer
uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp
gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4
+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3
ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O
gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh
GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr
odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk
+JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9
ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql
ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT
cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB
-----END CERTIFICATE-----`
var (
privateKeyPemBytes = []byte(privateKeyPem)
publicKeyPemBytes = []byte(publicKeyPem)
stdHeader = &Header{Algorithm: stdAlgorithm, Type: stdType}
)
// Testing the urlEncode function.
func TestUrlEncode(t *testing.T) {
enc := base64Encode([]byte(stdHeaderStr))
b := []byte(enc)
if b[len(b)-1] == 61 {
t.Error("TestUrlEncode: last chat == \"=\"")
}
if enc != headerEnc {
t.Error("TestUrlEncode: enc != headerEnc")
t.Errorf(" enc = %s", enc)
t.Errorf(" headerEnc = %s", headerEnc)
}
}
// Test that the times are set properly.
func TestClaimSetSetTimes(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
}
iat := time.Unix(iat, 0)
c.setTimes(iat)
if c.exp.Unix() != exp {
t.Error("TestClaimSetSetTimes: c.exp != exp")
t.Errorf(" c.Exp = %d", c.exp.Unix())
t.Errorf(" exp = %d", exp)
}
}
// Given a well formed ClaimSet, test for proper encoding.
func TestClaimSetEncode(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
enc := c.encode()
re, err := base64Decode(enc)
if err != nil {
t.Fatalf("error decoding encoded claim set: %v", err)
}
wa, err := base64Decode(claimSetEnc)
if err != nil {
t.Fatalf("error decoding encoded expected claim set: %v", err)
}
if enc != claimSetEnc {
t.Error("TestClaimSetEncode: enc != claimSetEnc")
t.Errorf(" enc = %s", string(re))
t.Errorf(" claimSetEnc = %s", string(wa))
}
}
// Test that claim sets with private claim names are encoded correctly.
func TestClaimSetWithPrivateNameEncode(t *testing.T) {
iatT := time.Unix(iat, 0)
expT := time.Unix(exp, 0)
i, err := json.Marshal(iatT.Unix())
if err != nil {
t.Fatalf("error marshaling iatT value of %v: %v", iatT.Unix(), err)
}
iatStr := string(i)
e, err := json.Marshal(expT.Unix())
if err != nil {
t.Fatalf("error marshaling expT value of %v: %v", expT.Unix(), err)
}
expStr := string(e)
testCases := []struct {
desc string
input map[string]interface{}
want string
}{
// Test a simple int field.
{
"single simple field",
map[string]interface{}{"amount": 22},
`{` +
`"iss":"` + iss + `",` +
`"scope":"` + scope + `",` +
`"aud":"` + stdAud + `",` +
`"exp":` + expStr + `,` +
`"iat":` + iatStr + `,` +
`"amount":22` +
`}`,
},
{
"multiple simple fields",
map[string]interface{}{"tracking_code": "axZf", "amount": 22},
`{` +
`"iss":"` + iss + `",` +
`"scope":"` + scope + `",` +
`"aud":"` + stdAud + `",` +
`"exp":` + expStr + `,` +
`"iat":` + iatStr + `,` +
`"amount":22,` +
`"tracking_code":"axZf"` +
`}`,
},
{
"nested struct fields",
map[string]interface{}{
"tracking_code": "axZf",
"purchase": struct {
Description string `json:"desc"`
Quantity int32 `json:"q"`
Time int64 `json:"t"`
}{
"toaster",
5,
iat,
},
},
`{` +
`"iss":"` + iss + `",` +
`"scope":"` + scope + `",` +
`"aud":"` + stdAud + `",` +
`"exp":` + expStr + `,` +
`"iat":` + iatStr + `,` +
`"purchase":{"desc":"toaster","q":5,"t":` + iatStr + `},` +
`"tracking_code":"axZf"` +
`}`,
},
}
for _, testCase := range testCases {
c := &ClaimSet{
Iss: iss,
Scope: scope,
Aud: stdAud,
iat: iatT,
exp: expT,
PrivateClaims: testCase.input,
}
cJSON, err := base64Decode(c.encode())
if err != nil {
t.Fatalf("error decoding claim set: %v", err)
}
if string(cJSON) != testCase.want {
t.Errorf("TestClaimSetWithPrivateNameEncode: enc != want in case %s", testCase.desc)
t.Errorf(" enc = %s", cJSON)
t.Errorf(" want = %s", testCase.want)
}
}
}
// Test the NewToken constructor.
func TestNewToken(t *testing.T) {
tok := NewToken(iss, scope, privateKeyPemBytes)
if tok.ClaimSet.Iss != iss {
t.Error("TestNewToken: tok.ClaimSet.Iss != iss")
t.Errorf(" tok.ClaimSet.Iss = %s", tok.ClaimSet.Iss)
t.Errorf(" iss = %s", iss)
}
if tok.ClaimSet.Scope != scope {
t.Error("TestNewToken: tok.ClaimSet.Scope != scope")
t.Errorf(" tok.ClaimSet.Scope = %s", tok.ClaimSet.Scope)
t.Errorf(" scope = %s", scope)
}
if tok.ClaimSet.Aud != stdAud {
t.Error("TestNewToken: tok.ClaimSet.Aud != stdAud")
t.Errorf(" tok.ClaimSet.Aud = %s", tok.ClaimSet.Aud)
t.Errorf(" stdAud = %s", stdAud)
}
if !bytes.Equal(tok.Key, privateKeyPemBytes) {
t.Error("TestNewToken: tok.Key != privateKeyPemBytes")
t.Errorf(" tok.Key = %s", tok.Key)
t.Errorf(" privateKeyPemBytes = %s", privateKeyPemBytes)
}
}
// Make sure the private key parsing functions work.
func TestParsePrivateKey(t *testing.T) {
tok := &Token{
Key: privateKeyPemBytes,
}
err := tok.parsePrivateKey()
if err != nil {
t.Errorf("TestParsePrivateKey:tok.parsePrivateKey: %v", err)
}
}
// Test that the token signature generated matches the golden standard.
func TestTokenSign(t *testing.T) {
tok := &Token{
Key: privateKeyPemBytes,
claim: claimSetEnc,
header: headerEnc,
}
err := tok.parsePrivateKey()
if err != nil {
t.Errorf("TestTokenSign:tok.parsePrivateKey: %v", err)
}
err = tok.sign()
if err != nil {
t.Errorf("TestTokenSign:tok.sign: %v", err)
}
if tok.sig != sigEnc {
t.Error("TestTokenSign: tok.sig != sigEnc")
t.Errorf(" tok.sig = %s", tok.sig)
t.Errorf(" sigEnc = %s", sigEnc)
}
}
// Test that the token expiration function is working.
func TestTokenExpired(t *testing.T) {
c := &ClaimSet{}
tok := &Token{
ClaimSet: c,
}
now := time.Now()
c.setTimes(now)
if tok.Expired() != false {
t.Error("TestTokenExpired: tok.Expired != false")
}
// Set the times as if they were set 2 hours ago.
c.setTimes(now.Add(-2 * time.Hour))
if tok.Expired() != true {
t.Error("TestTokenExpired: tok.Expired != true")
}
}
// Given a well formed Token, test for proper encoding.
func TestTokenEncode(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
tok := &Token{
ClaimSet: c,
Header: stdHeader,
Key: privateKeyPemBytes,
}
enc, err := tok.Encode()
if err != nil {
t.Errorf("TestTokenEncode:tok.Assertion: %v", err)
}
if enc != tokEnc {
t.Error("TestTokenEncode: enc != tokEnc")
t.Errorf(" enc = %s", enc)
t.Errorf(" tokEnc = %s", tokEnc)
}
}
// Given a well formed Token we should get back a well formed request.
func TestBuildRequest(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
tok := &Token{
ClaimSet: c,
Header: stdHeader,
Key: privateKeyPemBytes,
}
u, v, err := tok.buildRequest()
if err != nil {
t.Errorf("TestBuildRequest:BuildRequest: %v", err)
}
if u != c.Aud {
t.Error("TestBuildRequest: u != c.Aud")
t.Errorf(" u = %s", u)
t.Errorf(" c.Aud = %s", c.Aud)
}
if v.Get("grant_type") != stdGrantType {
t.Error("TestBuildRequest: grant_type != stdGrantType")
t.Errorf(" grant_type = %s", v.Get("grant_type"))
t.Errorf(" stdGrantType = %s", stdGrantType)
}
if v.Get("assertion") != tokEnc {
t.Error("TestBuildRequest: assertion != tokEnc")
t.Errorf(" assertion = %s", v.Get("assertion"))
t.Errorf(" tokEnc = %s", tokEnc)
}
}
// Given a well formed access request response we should get back a oauth.Token.
func TestHandleResponse(t *testing.T) {
rb := &respBody{
Access: "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
Type: "Bearer",
ExpiresIn: 3600,
}
b, err := json.Marshal(rb)
if err != nil {
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
}
r := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}
o, err := handleResponse(r)
if err != nil {
t.Errorf("TestHandleResponse:handleResponse: %v", err)
}
if o.AccessToken != rb.Access {
t.Error("TestHandleResponse: o.AccessToken != rb.Access")
t.Errorf(" o.AccessToken = %s", o.AccessToken)
t.Errorf(" rb.Access = %s", rb.Access)
}
if o.Expired() {
t.Error("TestHandleResponse: o.Expired == true")
}
}
// passthrough signature for test
type FakeSigner struct{}
func (f FakeSigner) Sign(tok *Token) ([]byte, []byte, error) {
block, _ := pem.Decode(privateKeyPemBytes)
pKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
ss := headerEnc + "." + claimSetEnc
h := sha256.New()
h.Write([]byte(ss))
b, _ := rsa.SignPKCS1v15(rand.Reader, pKey, crypto.SHA256, h.Sum(nil))
return []byte(ss), b, nil
}
// Given an external signer, get back a valid and signed JWT
func TestExternalSigner(t *testing.T) {
tok := NewSignerToken(iss, scope, FakeSigner{})
enc, _ := tok.Encode()
if enc != tokEnc {
t.Errorf("TestExternalSigner: enc != tokEnc")
t.Errorf(" enc = %s", enc)
t.Errorf(" tokEnc = %s", tokEnc)
}
}
func TestHandleResponseWithNewExpiry(t *testing.T) {
rb := &respBody{
IdToken: tokEnc,
}
b, err := json.Marshal(rb)
if err != nil {
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
}
r := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}
o, err := handleResponse(r)
if err != nil {
t.Errorf("TestHandleResponse:handleResponse: %v", err)
}
if o.Expiry != time.Unix(exp, 0) {
t.Error("TestHandleResponse: o.Expiry != exp")
t.Errorf(" o.Expiry = %s", o.Expiry)
t.Errorf(" exp = %s", time.Unix(exp, 0))
}
}
// Placeholder for future Assert tests.
func TestAssert(t *testing.T) {
// Since this method makes a call to BuildRequest, an htttp.Client, and
// finally HandleResponse there is not much more to test. This is here
// as a placeholder if that changes.
}
// Benchmark for the end-to-end encoding of a well formed token.
func BenchmarkTokenEncode(b *testing.B) {
b.StopTimer()
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
tok := &Token{
ClaimSet: c,
Key: privateKeyPemBytes,
}
b.StartTimer()
for i := 0; i < b.N; i++ {
tok.Encode()
}
}

View File

@ -0,0 +1,476 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package oauth supports making OAuth2-authenticated HTTP requests.
//
// Example usage:
//
// // Specify your configuration. (typically as a global variable)
// var config = &oauth.Config{
// ClientId: YOUR_CLIENT_ID,
// ClientSecret: YOUR_CLIENT_SECRET,
// Scope: "https://www.googleapis.com/auth/buzz",
// AuthURL: "https://accounts.google.com/o/oauth2/auth",
// TokenURL: "https://accounts.google.com/o/oauth2/token",
// RedirectURL: "http://you.example.org/handler",
// }
//
// // A landing page redirects to the OAuth provider to get the auth code.
// func landing(w http.ResponseWriter, r *http.Request) {
// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
// }
//
// // The user will be redirected back to this handler, that takes the
// // "code" query parameter and Exchanges it for an access token.
// func handler(w http.ResponseWriter, r *http.Request) {
// t := &oauth.Transport{Config: config}
// t.Exchange(r.FormValue("code"))
// // The Transport now has a valid Token. Create an *http.Client
// // with which we can make authenticated API requests.
// c := t.Client()
// c.Post(...)
// // ...
// // btw, r.FormValue("state") == "foo"
// }
//
package oauth
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
// OAuthError is the error type returned by many operations.
//
// In retrospect it should not exist. Don't depend on it.
type OAuthError struct {
prefix string
msg string
}
func (oe OAuthError) Error() string {
return "OAuthError: " + oe.prefix + ": " + oe.msg
}
// Cache specifies the methods that implement a Token cache.
type Cache interface {
Token() (*Token, error)
PutToken(*Token) error
}
// CacheFile implements Cache. Its value is the name of the file in which
// the Token is stored in JSON format.
type CacheFile string
func (f CacheFile) Token() (*Token, error) {
file, err := os.Open(string(f))
if err != nil {
return nil, OAuthError{"CacheFile.Token", err.Error()}
}
defer file.Close()
tok := &Token{}
if err := json.NewDecoder(file).Decode(tok); err != nil {
return nil, OAuthError{"CacheFile.Token", err.Error()}
}
return tok, nil
}
func (f CacheFile) PutToken(tok *Token) error {
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return OAuthError{"CacheFile.PutToken", err.Error()}
}
if err := json.NewEncoder(file).Encode(tok); err != nil {
file.Close()
return OAuthError{"CacheFile.PutToken", err.Error()}
}
if err := file.Close(); err != nil {
return OAuthError{"CacheFile.PutToken", err.Error()}
}
return nil
}
// Config is the configuration of an OAuth consumer.
type Config struct {
// ClientId is the OAuth client identifier used when communicating with
// the configured OAuth provider.
ClientId string
// ClientSecret is the OAuth client secret used when communicating with
// the configured OAuth provider.
ClientSecret string
// Scope identifies the level of access being requested. Multiple scope
// values should be provided as a space-delimited string.
Scope string
// AuthURL is the URL the user will be directed to in order to grant
// access.
AuthURL string
// TokenURL is the URL used to retrieve OAuth tokens.
TokenURL string
// RedirectURL is the URL to which the user will be returned after
// granting (or denying) access.
RedirectURL string
// TokenCache allows tokens to be cached for subsequent requests.
TokenCache Cache
// AccessType is an OAuth extension that gets sent as the
// "access_type" field in the URL from AuthCodeURL.
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
// It may be "online" (the default) or "offline".
// If your application needs to refresh access tokens when the
// user is not present at the browser, then use offline. This
// will result in your application obtaining a refresh token
// the first time your application exchanges an authorization
// code for a user.
AccessType string
// ApprovalPrompt indicates whether the user should be
// re-prompted for consent. If set to "auto" (default) the
// user will be prompted only if they haven't previously
// granted consent and the code can only be exchanged for an
// access token.
// If set to "force" the user will always be prompted, and the
// code can be exchanged for a refresh token.
ApprovalPrompt string
}
// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
type Token struct {
AccessToken string
RefreshToken string
Expiry time.Time // If zero the token has no (known) expiry time.
// Extra optionally contains extra metadata from the server
// when updating a token. The only current key that may be
// populated is "id_token". It may be nil and will be
// initialized as needed.
Extra map[string]string
}
// Expired reports whether the token has expired or is invalid.
func (t *Token) Expired() bool {
if t.AccessToken == "" {
return true
}
if t.Expiry.IsZero() {
return false
}
return t.Expiry.Before(time.Now())
}
// Transport implements http.RoundTripper. When configured with a valid
// Config and Token it can be used to make authenticated HTTP requests.
//
// t := &oauth.Transport{config}
// t.Exchange(code)
// // t now contains a valid Token
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the Token if it can,
// updating the supplied Token in place.
type Transport struct {
*Config
*Token
// mu guards modifying the token.
mu sync.Mutex
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
// (It should never be an oauth.Transport.)
Transport http.RoundTripper
}
// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
return &http.Client{Transport: t}
}
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// AuthCodeURL returns a URL that the end-user should be redirected to,
// so that they may obtain an authorization code.
func (c *Config) AuthCodeURL(state string) string {
url_, err := url.Parse(c.AuthURL)
if err != nil {
panic("AuthURL malformed: " + err.Error())
}
q := url.Values{
"response_type": {"code"},
"client_id": {c.ClientId},
"state": condVal(state),
"scope": condVal(c.Scope),
"redirect_uri": condVal(c.RedirectURL),
"access_type": condVal(c.AccessType),
"approval_prompt": condVal(c.ApprovalPrompt),
}.Encode()
if url_.RawQuery == "" {
url_.RawQuery = q
} else {
url_.RawQuery += "&" + q
}
return url_.String()
}
func condVal(v string) []string {
if v == "" {
return nil
}
return []string{v}
}
// Exchange takes a code and gets access Token from the remote server.
func (t *Transport) Exchange(code string) (*Token, error) {
if t.Config == nil {
return nil, OAuthError{"Exchange", "no Config supplied"}
}
// If the transport or the cache already has a token, it is
// passed to `updateToken` to preserve existing refresh token.
tok := t.Token
if tok == nil && t.TokenCache != nil {
tok, _ = t.TokenCache.Token()
}
if tok == nil {
tok = new(Token)
}
err := t.updateToken(tok, url.Values{
"grant_type": {"authorization_code"},
"redirect_uri": {t.RedirectURL},
"scope": {t.Scope},
"code": {code},
})
if err != nil {
return nil, err
}
t.Token = tok
if t.TokenCache != nil {
return tok, t.TokenCache.PutToken(tok)
}
return tok, nil
}
// RoundTrip executes a single HTTP transaction using the Transport's
// Token as authorization headers.
//
// This method will attempt to renew the Token if it has expired and may return
// an error related to that Token renewal before attempting the client request.
// If the Token cannot be renewed a non-nil os.Error value will be returned.
// If the Token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
accessToken, err := t.getAccessToken()
if err != nil {
return nil, err
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+accessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
}
func (t *Transport) getAccessToken() (string, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.Token == nil {
if t.Config == nil {
return "", OAuthError{"RoundTrip", "no Config supplied"}
}
if t.TokenCache == nil {
return "", OAuthError{"RoundTrip", "no Token supplied"}
}
var err error
t.Token, err = t.TokenCache.Token()
if err != nil {
return "", err
}
}
// Refresh the Token if it has expired.
if t.Expired() {
if err := t.Refresh(); err != nil {
return "", err
}
}
if t.AccessToken == "" {
return "", errors.New("no access token obtained from refresh")
}
return t.AccessToken, nil
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}
// Refresh renews the Transport's AccessToken using its RefreshToken.
func (t *Transport) Refresh() error {
if t.Token == nil {
return OAuthError{"Refresh", "no existing Token"}
}
if t.RefreshToken == "" {
return OAuthError{"Refresh", "Token expired; no Refresh Token"}
}
if t.Config == nil {
return OAuthError{"Refresh", "no Config supplied"}
}
err := t.updateToken(t.Token, url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {t.RefreshToken},
})
if err != nil {
return err
}
if t.TokenCache != nil {
return t.TokenCache.PutToken(t.Token)
}
return nil
}
// AuthenticateClient gets an access Token using the client_credentials grant
// type.
func (t *Transport) AuthenticateClient() error {
if t.Config == nil {
return OAuthError{"Exchange", "no Config supplied"}
}
if t.Token == nil {
t.Token = &Token{}
}
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
}
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
func providerAuthHeaderWorks(tokenURL string) bool {
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
strings.HasPrefix(tokenURL, "https://github.com/") ||
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
strings.HasPrefix(tokenURL, "https://www.douban.com/") {
// Some sites fail to implement the OAuth2 spec fully.
return false
}
// Assume the provider implements the spec properly
// otherwise. We can add more exceptions as they're
// discovered. We will _not_ be adding configurable hooks
// to this package to let users select server bugs.
return true
}
// updateToken mutates both tok and v.
func (t *Transport) updateToken(tok *Token, v url.Values) error {
v.Set("client_id", t.ClientId)
bustedAuth := !providerAuthHeaderWorks(t.TokenURL)
if bustedAuth {
v.Set("client_secret", t.ClientSecret)
}
client := &http.Client{Transport: t.transport()}
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if !bustedAuth {
req.SetBasicAuth(t.ClientId, t.ClientSecret)
}
r, err := client.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != 200 {
return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status}
}
var b struct {
Access string `json:"access_token"`
Refresh string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"` // seconds
Id string `json:"id_token"`
}
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
return err
}
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
switch content {
case "application/x-www-form-urlencoded", "text/plain":
vals, err := url.ParseQuery(string(body))
if err != nil {
return err
}
b.Access = vals.Get("access_token")
b.Refresh = vals.Get("refresh_token")
b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
b.Id = vals.Get("id_token")
default:
if err = json.Unmarshal(body, &b); err != nil {
return fmt.Errorf("got bad response from server: %q", body)
}
}
if b.Access == "" {
return errors.New("received empty access token from authorization server")
}
tok.AccessToken = b.Access
// Don't overwrite `RefreshToken` with an empty value
if b.Refresh != "" {
tok.RefreshToken = b.Refresh
}
if b.ExpiresIn == 0 {
tok.Expiry = time.Time{}
} else {
tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
}
if b.Id != "" {
if tok.Extra == nil {
tok.Extra = make(map[string]string)
}
tok.Extra["id_token"] = b.Id
}
return nil
}

View File

@ -0,0 +1,236 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package oauth
import (
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)
var requests = []struct {
path, query, auth string // request
contenttype, body string // response
}{
{
path: "/token",
query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token1",
"refresh_token":"refreshtoken1",
"id_token":"idtoken1",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token1", body: "first payload"},
{
path: "/token",
query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token2",
"refresh_token":"refreshtoken2",
"id_token":"idtoken2",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token2", body: "second payload"},
{
path: "/token",
query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d",
contenttype: "application/x-www-form-urlencoded",
body: "access_token=token3&refresh_token=refreshtoken3&id_token=idtoken3&expires_in=3600",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
},
{path: "/secure", auth: "Bearer token3", body: "third payload"},
{
path: "/token",
query: "grant_type=client_credentials&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token4",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token4", body: "fourth payload"},
}
func TestOAuth(t *testing.T) {
// Set up test server.
n := 0
handler := func(w http.ResponseWriter, r *http.Request) {
if n >= len(requests) {
t.Errorf("too many requests: %d", n)
return
}
req := requests[n]
n++
// Check request.
if g, w := r.URL.Path, req.path; g != w {
t.Errorf("request[%d] got path %s, want %s", n, g, w)
}
want, _ := url.ParseQuery(req.query)
for k := range want {
if g, w := r.FormValue(k), want.Get(k); g != w {
t.Errorf("query[%s] = %s, want %s", k, g, w)
}
}
if g, w := r.Header.Get("Authorization"), req.auth; w != "" && g != w {
t.Errorf("Authorization: %v, want %v", g, w)
}
// Send response.
w.Header().Set("Content-Type", req.contenttype)
io.WriteString(w, req.body)
}
server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()
config := &Config{
ClientId: "cl13nt1d",
ClientSecret: "s3cr3t",
Scope: "https://example.net/scope",
AuthURL: server.URL + "/auth",
TokenURL: server.URL + "/token",
}
// TODO(adg): test AuthCodeURL
transport := &Transport{Config: config}
_, err := transport.Exchange("c0d3")
if err != nil {
t.Fatalf("Exchange: %v", err)
}
checkToken(t, transport.Token, "token1", "refreshtoken1", "idtoken1")
c := transport.Client()
resp, err := c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "first payload")
// test automatic refresh
transport.Expiry = time.Now().Add(-time.Hour)
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "second payload")
checkToken(t, transport.Token, "token2", "refreshtoken2", "idtoken2")
// refresh one more time, but get URL-encoded token instead of JSON
transport.Expiry = time.Now().Add(-time.Hour)
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "third payload")
checkToken(t, transport.Token, "token3", "refreshtoken3", "idtoken3")
transport.Token = &Token{}
err = transport.AuthenticateClient()
if err != nil {
t.Fatalf("AuthenticateClient: %v", err)
}
checkToken(t, transport.Token, "token4", "", "")
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "fourth payload")
}
func checkToken(t *testing.T, tok *Token, access, refresh, id string) {
if g, w := tok.AccessToken, access; g != w {
t.Errorf("AccessToken = %q, want %q", g, w)
}
if g, w := tok.RefreshToken, refresh; g != w {
t.Errorf("RefreshToken = %q, want %q", g, w)
}
if g, w := tok.Extra["id_token"], id; g != w {
t.Errorf("Extra['id_token'] = %q, want %q", g, w)
}
if tok.Expiry.IsZero() {
t.Errorf("Expiry is zero; want ~1 hour")
} else {
exp := tok.Expiry.Sub(time.Now())
const slop = 3 * time.Second // time moving during test
if (time.Hour-slop) > exp || exp > time.Hour {
t.Errorf("Expiry = %v, want ~1 hour", exp)
}
}
}
func checkBody(t *testing.T, r *http.Response, body string) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("reading reponse body: %v, want %q", err, body)
}
if g, w := string(b), body; g != w {
t.Errorf("request body mismatch: got %q, want %q", g, w)
}
}
func TestCachePermissions(t *testing.T) {
if runtime.GOOS == "windows" {
// Windows doesn't support file mode bits.
return
}
td, err := ioutil.TempDir("", "oauth-test")
if err != nil {
t.Fatalf("ioutil.TempDir: %v", err)
}
defer os.RemoveAll(td)
tempFile := filepath.Join(td, "cache-file")
cf := CacheFile(tempFile)
if err := cf.PutToken(new(Token)); err != nil {
t.Fatalf("PutToken: %v", err)
}
fi, err := os.Stat(tempFile)
if err != nil {
t.Fatalf("os.Stat: %v", err)
}
if fi.Mode()&0077 != 0 {
t.Errorf("Created cache file has mode %#o, want non-accessible to group+other", fi.Mode())
}
}
func TestTokenExpired(t *testing.T) {
tests := []struct {
token Token
expired bool
}{
{Token{AccessToken: "foo"}, false},
{Token{AccessToken: ""}, true},
{Token{AccessToken: "foo", Expiry: time.Now().Add(-1 * time.Hour)}, true},
{Token{AccessToken: "foo", Expiry: time.Now().Add(1 * time.Hour)}, false},
}
for _, tt := range tests {
if got := tt.token.Expired(); got != tt.expired {
t.Errorf("token %+v Expired = %v; want %v", tt.token, got, !got)
}
}
}

View File

@ -0,0 +1,247 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock
import (
"fmt"
"reflect"
"strings"
)
// Call represents an expected call to a mock.
type Call struct {
t TestReporter // for triggering test failures on invalid call setup
receiver interface{} // the receiver of the method call
method string // the name of the method
args []Matcher // the args
rets []interface{} // the return values (if any)
preReqs []*Call // prerequisite calls
// Expectations
minCalls, maxCalls int
numCalls int // actual number made
// Actions
doFunc reflect.Value
setArgs map[int]reflect.Value
}
func (c *Call) AnyTimes() *Call {
c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity
return c
}
// Do declares the action to run when the call is matched.
// It takes an interface{} argument to support n-arity functions.
func (c *Call) Do(f interface{}) *Call {
// TODO: Check arity and types here, rather than dying badly elsewhere.
c.doFunc = reflect.ValueOf(f)
return c
}
func (c *Call) Return(rets ...interface{}) *Call {
mt := c.methodType()
if len(rets) != mt.NumOut() {
c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d",
c.receiver, c.method, len(rets), mt.NumOut())
}
for i, ret := range rets {
if got, want := reflect.TypeOf(ret), mt.Out(i); got == want {
// Identical types; nothing to do.
} else if got == nil {
// Nil needs special handling.
switch want.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
// ok
default:
c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable",
i, c.receiver, c.method, want)
}
} else if got.AssignableTo(want) {
// Assignable type relation. Make the assignment now so that the generated code
// can return the values with a type assertion.
v := reflect.New(want).Elem()
v.Set(reflect.ValueOf(ret))
rets[i] = v.Interface()
} else {
c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v",
i, c.receiver, c.method, got, want)
}
}
c.rets = rets
return c
}
func (c *Call) Times(n int) *Call {
c.minCalls, c.maxCalls = n, n
return c
}
// SetArg declares an action that will set the nth argument's value,
// indirected through a pointer.
func (c *Call) SetArg(n int, value interface{}) *Call {
if c.setArgs == nil {
c.setArgs = make(map[int]reflect.Value)
}
mt := c.methodType()
// TODO: This will break on variadic methods.
// We will need to check those at invocation time.
if n < 0 || n >= mt.NumIn() {
c.t.Fatalf("SetArg(%d, ...) called for a method with %d args", n, mt.NumIn())
}
// Permit setting argument through an interface.
// In the interface case, we don't (nay, can't) check the type here.
at := mt.In(n)
switch at.Kind() {
case reflect.Ptr:
dt := at.Elem()
if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) {
c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v", n, vt, dt)
}
case reflect.Interface:
// nothing to do
default:
c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface type %v", n, at)
}
c.setArgs[n] = reflect.ValueOf(value)
return c
}
// isPreReq returns true if other is a direct or indirect prerequisite to c.
func (c *Call) isPreReq(other *Call) bool {
for _, preReq := range c.preReqs {
if other == preReq || preReq.isPreReq(other) {
return true
}
}
return false
}
// After declares that the call may only match after preReq has been exhausted.
func (c *Call) After(preReq *Call) *Call {
if preReq.isPreReq(c) {
msg := fmt.Sprintf(
"Loop in call order: %v is a prerequisite to %v (possibly indirectly).",
c, preReq,
)
panic(msg)
}
c.preReqs = append(c.preReqs, preReq)
return c
}
// Returns true iff the minimum number of calls have been made.
func (c *Call) satisfied() bool {
return c.numCalls >= c.minCalls
}
// Returns true iff the maximum number of calls have been made.
func (c *Call) exhausted() bool {
return c.numCalls >= c.maxCalls
}
func (c *Call) String() string {
args := make([]string, len(c.args))
for i, arg := range c.args {
args[i] = arg.String()
}
arguments := strings.Join(args, ", ")
return fmt.Sprintf("%T.%v(%s)", c.receiver, c.method, arguments)
}
// Tests if the given call matches the expected call.
func (c *Call) matches(args []interface{}) bool {
if len(args) != len(c.args) {
return false
}
for i, m := range c.args {
if !m.Matches(args[i]) {
return false
}
}
// Check that all prerequisite calls have been satisfied.
for _, preReqCall := range c.preReqs {
if !preReqCall.satisfied() {
return false
}
}
return true
}
// dropPrereqs tells the expected Call to not re-check prerequite calls any
// longer, and to return its current set.
func (c *Call) dropPrereqs() (preReqs []*Call) {
preReqs = c.preReqs
c.preReqs = nil
return
}
func (c *Call) call(args []interface{}) (rets []interface{}, action func()) {
c.numCalls++
// Actions
if c.doFunc.IsValid() {
doArgs := make([]reflect.Value, len(args))
ft := c.doFunc.Type()
for i := 0; i < ft.NumIn(); i++ {
if args[i] != nil {
doArgs[i] = reflect.ValueOf(args[i])
} else {
// Use the zero value for the arg.
doArgs[i] = reflect.Zero(ft.In(i))
}
}
action = func() { c.doFunc.Call(doArgs) }
}
for n, v := range c.setArgs {
reflect.ValueOf(args[n]).Elem().Set(v)
}
rets = c.rets
if rets == nil {
// Synthesize the zero value for each of the return args' types.
mt := c.methodType()
rets = make([]interface{}, mt.NumOut())
for i := 0; i < mt.NumOut(); i++ {
rets[i] = reflect.Zero(mt.Out(i)).Interface()
}
}
return
}
func (c *Call) methodType() reflect.Type {
recv := reflect.ValueOf(c.receiver)
for i := 0; i < recv.Type().NumMethod(); i++ {
if recv.Type().Method(i).Name == c.method {
return recv.Method(i).Type()
}
}
panic(fmt.Sprintf("gomock: failed finding method %s on %T", c.method, c.receiver))
}
// InOrder declares that the given calls should occur in order.
func InOrder(calls ...*Call) {
for i := 1; i < len(calls); i++ {
calls[i].After(calls[i-1])
}
}

View File

@ -0,0 +1,76 @@
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock
// callSet represents a set of expected calls, indexed by receiver and method
// name.
type callSet map[interface{}]map[string][]*Call
// Add adds a new expected call.
func (cs callSet) Add(call *Call) {
methodMap, ok := cs[call.receiver]
if !ok {
methodMap = make(map[string][]*Call)
cs[call.receiver] = methodMap
}
methodMap[call.method] = append(methodMap[call.method], call)
}
// Remove removes an expected call.
func (cs callSet) Remove(call *Call) {
methodMap, ok := cs[call.receiver]
if !ok {
return
}
sl := methodMap[call.method]
for i, c := range sl {
if c == call {
// quick removal; we don't need to maintain call order
if len(sl) > 1 {
sl[i] = sl[len(sl)-1]
}
methodMap[call.method] = sl[:len(sl)-1]
break
}
}
}
// FindMatch searches for a matching call. Returns nil if no call matched.
func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) *Call {
methodMap, ok := cs[receiver]
if !ok {
return nil
}
calls, ok := methodMap[method]
if !ok {
return nil
}
// Search through the unordered set of calls expected on a method on a
// receiver.
for _, call := range calls {
// A call should not normally still be here if exhausted,
// but it can happen if, for instance, .Times(0) was used.
// Pretend the call doesn't match.
if call.exhausted() {
continue
}
if call.matches(args) {
return call
}
}
return nil
}

View File

@ -0,0 +1,167 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// GoMock - a mock framework for Go.
//
// Standard usage:
// (1) Define an interface that you wish to mock.
// type MyInterface interface {
// SomeMethod(x int64, y string)
// }
// (2) Use mockgen to generate a mock from the interface.
// (3) Use the mock in a test:
// func TestMyThing(t *testing.T) {
// mockCtrl := gomock.NewController(t)
// defer mockCtrl.Finish()
//
// mockObj := something.NewMockMyInterface(mockCtrl)
// mockObj.EXPECT().SomeMethod(4, "blah")
// // pass mockObj to a real object and play with it.
// }
//
// By default, expected calls are not enforced to run in any particular order.
// Call order dependency can be enforced by use of InOrder and/or Call.After.
// Call.After can create more varied call order dependencies, but InOrder is
// often more convenient.
//
// The following examples create equivalent call order dependencies.
//
// Example of using Call.After to chain expected call order:
//
// firstCall := mockObj.EXPECT().SomeMethod(1, "first")
// secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall)
// mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)
//
// Example of using InOrder to declare expected call order:
//
// gomock.InOrder(
// mockObj.EXPECT().SomeMethod(1, "first"),
// mockObj.EXPECT().SomeMethod(2, "second"),
// mockObj.EXPECT().SomeMethod(3, "third"),
// )
//
// TODO:
// - Handle different argument/return types (e.g. ..., chan, map, interface).
package gomock
import "sync"
// A TestReporter is something that can be used to report test failures.
// It is satisfied by the standard library's *testing.T.
type TestReporter interface {
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
}
// A Controller represents the top-level control of a mock ecosystem.
// It defines the scope and lifetime of mock objects, as well as their expectations.
// It is safe to call Controller's methods from multiple goroutines.
type Controller struct {
mu sync.Mutex
t TestReporter
expectedCalls callSet
}
func NewController(t TestReporter) *Controller {
return &Controller{
t: t,
expectedCalls: make(callSet),
}
}
func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call {
// TODO: check arity, types.
margs := make([]Matcher, len(args))
for i, arg := range args {
if m, ok := arg.(Matcher); ok {
margs[i] = m
} else if arg == nil {
// Handle nil specially so that passing a nil interface value
// will match the typed nils of concrete args.
margs[i] = Nil()
} else {
margs[i] = Eq(arg)
}
}
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
call := &Call{t: ctrl.t, receiver: receiver, method: method, args: margs, minCalls: 1, maxCalls: 1}
ctrl.expectedCalls.Add(call)
return call
}
func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} {
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
expected := ctrl.expectedCalls.FindMatch(receiver, method, args)
if expected == nil {
ctrl.t.Fatalf("no matching expected call: %T.%v(%v)", receiver, method, args)
}
// Two things happen here:
// * the matching call no longer needs to check prerequite calls,
// * and the prerequite calls are no longer expected, so remove them.
preReqCalls := expected.dropPrereqs()
for _, preReqCall := range preReqCalls {
ctrl.expectedCalls.Remove(preReqCall)
}
rets, action := expected.call(args)
if expected.exhausted() {
ctrl.expectedCalls.Remove(expected)
}
// Don't hold the lock while doing the call's action (if any)
// so that actions may execute concurrently.
// We use the deferred Unlock to capture any panics that happen above;
// here we add a deferred Lock to balance it.
ctrl.mu.Unlock()
defer ctrl.mu.Lock()
if action != nil {
action()
}
return rets
}
func (ctrl *Controller) Finish() {
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
// If we're currently panicking, probably because this is a deferred call,
// pass through the panic.
if err := recover(); err != nil {
panic(err)
}
// Check that all remaining expected calls are satisfied.
failures := false
for _, methodMap := range ctrl.expectedCalls {
for _, calls := range methodMap {
for _, call := range calls {
if !call.satisfied() {
ctrl.t.Errorf("missing call(s) to %v", call)
failures = true
}
}
}
}
if failures {
ctrl.t.Fatalf("aborting test due to missing call(s)")
}
}

View File

@ -0,0 +1,395 @@
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock_test
import (
"fmt"
"reflect"
"testing"
"code.google.com/p/gomock/gomock"
)
type ErrorReporter struct {
t *testing.T
log []string
failed bool
fatalToken struct{}
}
func NewErrorReporter(t *testing.T) *ErrorReporter {
return &ErrorReporter{t: t}
}
func (e *ErrorReporter) reportLog() {
for _, entry := range e.log {
e.t.Log(entry)
}
}
func (e *ErrorReporter) assertPass(msg string) {
if e.failed {
e.t.Errorf("Expected pass, but got failure(s): %s", msg)
e.reportLog()
}
}
func (e *ErrorReporter) assertFail(msg string) {
if !e.failed {
e.t.Error("Expected failure, but got pass: %s", msg)
}
}
// Use to check that code triggers a fatal test failure.
func (e *ErrorReporter) assertFatal(fn func()) {
defer func() {
err := recover()
if err == nil {
var actual string
if e.failed {
actual = "non-fatal failure"
} else {
actual = "pass"
}
e.t.Error("Expected fatal failure, but got a", actual)
} else if token, ok := err.(*struct{}); ok && token == &e.fatalToken {
// This is okay - the panic is from Fatalf().
return
} else {
// Some other panic.
panic(err)
}
}()
fn()
}
// recoverUnexpectedFatal can be used as a deferred call in test cases to
// recover from and display a call to ErrorReporter.Fatalf().
func (e *ErrorReporter) recoverUnexpectedFatal() {
err := recover()
if err == nil {
// No panic.
} else if token, ok := err.(*struct{}); ok && token == &e.fatalToken {
// Unexpected fatal error happened.
e.t.Error("Got unexpected fatal error(s). All errors up to this point:")
e.reportLog()
return
} else {
// Some other panic.
panic(err)
}
}
func (e *ErrorReporter) Logf(format string, args ...interface{}) {
e.log = append(e.log, fmt.Sprintf(format, args...))
}
func (e *ErrorReporter) Errorf(format string, args ...interface{}) {
e.Logf(format, args...)
e.failed = true
}
func (e *ErrorReporter) Fatalf(format string, args ...interface{}) {
e.Logf(format, args...)
e.failed = true
panic(&e.fatalToken)
}
// A type purely for use as a receiver in testing the Controller.
type Subject struct{}
func (s *Subject) FooMethod(arg string) int {
return 0
}
func (s *Subject) BarMethod(arg string) int {
return 0
}
func assertEqual(t *testing.T, expected interface{}, actual interface{}) {
if !reflect.DeepEqual(expected, actual) {
t.Error("Expected %+v, but got %+v", expected, actual)
}
}
func createFixtures(t *testing.T) (reporter *ErrorReporter, ctrl *gomock.Controller) {
// reporter acts as a testing.T-like object that we pass to the
// Controller. We use it to test that the mock considered tests
// successful or failed.
reporter = NewErrorReporter(t)
ctrl = gomock.NewController(reporter)
return
}
func TestNoCalls(t *testing.T) {
reporter, ctrl := createFixtures(t)
ctrl.Finish()
reporter.assertPass("No calls expected or made.")
}
func TestExpectedMethodCall(t *testing.T) {
reporter, ctrl := createFixtures(t)
subject := new(Subject)
ctrl.RecordCall(subject, "FooMethod", "argument")
ctrl.Call(subject, "FooMethod", "argument")
ctrl.Finish()
reporter.assertPass("Expected method call made.")
}
func TestUnexpectedMethodCall(t *testing.T) {
reporter, ctrl := createFixtures(t)
subject := new(Subject)
reporter.assertFatal(func() {
ctrl.Call(subject, "FooMethod", "argument")
})
ctrl.Finish()
}
func TestRepeatedCall(t *testing.T) {
reporter, ctrl := createFixtures(t)
subject := new(Subject)
ctrl.RecordCall(subject, "FooMethod", "argument").Times(3)
ctrl.Call(subject, "FooMethod", "argument")
ctrl.Call(subject, "FooMethod", "argument")
ctrl.Call(subject, "FooMethod", "argument")
reporter.assertPass("After expected repeated method calls.")
reporter.assertFatal(func() {
ctrl.Call(subject, "FooMethod", "argument")
})
ctrl.Finish()
reporter.assertFail("After calling one too many times.")
}
func TestUnexpectedArgCount(t *testing.T) {
reporter, ctrl := createFixtures(t)
defer reporter.recoverUnexpectedFatal()
subject := new(Subject)
ctrl.RecordCall(subject, "FooMethod", "argument")
reporter.assertFatal(func() {
// This call is made with the wrong number of arguments...
ctrl.Call(subject, "FooMethod", "argument", "extra_argument")
})
reporter.assertFatal(func() {
// ... so is this.
ctrl.Call(subject, "FooMethod")
})
reporter.assertFatal(func() {
// The expected call wasn't made.
ctrl.Finish()
})
}
func TestAnyTimes(t *testing.T) {
reporter, ctrl := createFixtures(t)
subject := new(Subject)
ctrl.RecordCall(subject, "FooMethod", "argument").AnyTimes()
for i := 0; i < 100; i++ {
ctrl.Call(subject, "FooMethod", "argument")
}
reporter.assertPass("After 100 method calls.")
ctrl.Finish()
}
func TestDo(t *testing.T) {
_, ctrl := createFixtures(t)
subject := new(Subject)
doCalled := false
var argument string
ctrl.RecordCall(subject, "FooMethod", "argument").Do(
func(arg string) {
doCalled = true
argument = arg
})
if doCalled {
t.Error("Do() callback called too early.")
}
ctrl.Call(subject, "FooMethod", "argument")
if !doCalled {
t.Error("Do() callback not called.")
}
if "argument" != argument {
t.Error("Do callback received wrong argument.")
}
ctrl.Finish()
}
func TestReturn(t *testing.T) {
_, ctrl := createFixtures(t)
subject := new(Subject)
// Unspecified return should produce "zero" result.
ctrl.RecordCall(subject, "FooMethod", "zero")
ctrl.RecordCall(subject, "FooMethod", "five").Return(5)
assertEqual(
t,
[]interface{}{0},
ctrl.Call(subject, "FooMethod", "zero"))
assertEqual(
t,
[]interface{}{5},
ctrl.Call(subject, "FooMethod", "five"))
ctrl.Finish()
}
func TestUnorderedCalls(t *testing.T) {
reporter, ctrl := createFixtures(t)
defer reporter.recoverUnexpectedFatal()
subjectTwo := new(Subject)
subjectOne := new(Subject)
ctrl.RecordCall(subjectOne, "FooMethod", "1")
ctrl.RecordCall(subjectOne, "BarMethod", "2")
ctrl.RecordCall(subjectTwo, "FooMethod", "3")
ctrl.RecordCall(subjectTwo, "BarMethod", "4")
// Make the calls in a different order, which should be fine.
ctrl.Call(subjectOne, "BarMethod", "2")
ctrl.Call(subjectTwo, "FooMethod", "3")
ctrl.Call(subjectTwo, "BarMethod", "4")
ctrl.Call(subjectOne, "FooMethod", "1")
reporter.assertPass("After making all calls in different order")
ctrl.Finish()
reporter.assertPass("After finish")
}
func commonTestOrderedCalls(t *testing.T) (reporter *ErrorReporter, ctrl *gomock.Controller, subjectOne, subjectTwo *Subject) {
reporter, ctrl = createFixtures(t)
subjectOne = new(Subject)
subjectTwo = new(Subject)
gomock.InOrder(
ctrl.RecordCall(subjectOne, "FooMethod", "1").AnyTimes(),
ctrl.RecordCall(subjectTwo, "FooMethod", "2"),
ctrl.RecordCall(subjectTwo, "BarMethod", "3"),
)
return
}
func TestOrderedCallsCorrect(t *testing.T) {
reporter, ctrl, subjectOne, subjectTwo := commonTestOrderedCalls(t)
ctrl.Call(subjectOne, "FooMethod", "1")
ctrl.Call(subjectTwo, "FooMethod", "2")
ctrl.Call(subjectTwo, "BarMethod", "3")
ctrl.Finish()
reporter.assertPass("After finish")
}
func TestOrderedCallsInCorrect(t *testing.T) {
reporter, ctrl, subjectOne, subjectTwo := commonTestOrderedCalls(t)
ctrl.Call(subjectOne, "FooMethod", "1")
reporter.assertFatal(func() {
ctrl.Call(subjectTwo, "BarMethod", "3")
})
}
// Test that calls that are prerequites to other calls but have maxCalls >
// minCalls are removed from the expected call set.
func TestOrderedCallsWithPreReqMaxUnbounded(t *testing.T) {
reporter, ctrl, subjectOne, subjectTwo := commonTestOrderedCalls(t)
// Initially we should be able to call FooMethod("1") as many times as we
// want.
ctrl.Call(subjectOne, "FooMethod", "1")
ctrl.Call(subjectOne, "FooMethod", "1")
// But calling something that has it as a prerequite should remove it from
// the expected call set. This allows tests to ensure that FooMethod("1") is
// *not* called after FooMethod("2").
ctrl.Call(subjectTwo, "FooMethod", "2")
// Therefore this call should fail:
reporter.assertFatal(func() {
ctrl.Call(subjectOne, "FooMethod", "1")
})
}
func TestCallAfterLoopPanic(t *testing.T) {
_, ctrl := createFixtures(t)
subject := new(Subject)
firstCall := ctrl.RecordCall(subject, "Foo", "1")
secondCall := ctrl.RecordCall(subject, "Foo", "2")
thirdCall := ctrl.RecordCall(subject, "Foo", "3")
gomock.InOrder(firstCall, secondCall, thirdCall)
defer func() {
err := recover()
if err == nil {
t.Error("Call.After creation of dependency loop did not panic.")
}
}()
// This should panic due to dependency loop.
firstCall.After(thirdCall)
}
func TestPanicOverridesExpectationChecks(t *testing.T) {
ctrl := gomock.NewController(t)
reporter := NewErrorReporter(t)
reporter.assertFatal(func() {
ctrl.RecordCall(new(Subject), "FooMethod", "1")
defer ctrl.Finish()
reporter.Fatalf("Intentional panic")
})
}
func TestSetArgWithBadType(t *testing.T) {
rep, ctrl := createFixtures(t)
defer ctrl.Finish()
s := new(Subject)
// This should catch a type error:
rep.assertFatal(func() {
ctrl.RecordCall(s, "FooMethod", "1").SetArg(0, "blah")
})
ctrl.Call(s, "FooMethod", "1")
}
func TestTimes0(t *testing.T) {
rep, ctrl := createFixtures(t)
defer ctrl.Finish()
s := new(Subject)
ctrl.RecordCall(s, "FooMethod", "arg").Times(0)
rep.assertFatal(func() {
ctrl.Call(s, "FooMethod", "arg")
})
}

View File

@ -0,0 +1,97 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock
import (
"fmt"
"reflect"
)
// A Matcher is a representation of a class of values.
// It is used to represent the valid or expected arguments to a mocked method.
type Matcher interface {
// Matches returns whether y is a match.
Matches(x interface{}) bool
// String describes what the matcher matches.
String() string
}
type anyMatcher struct{}
func (anyMatcher) Matches(x interface{}) bool {
return true
}
func (anyMatcher) String() string {
return "is anything"
}
type eqMatcher struct {
x interface{}
}
func (e eqMatcher) Matches(x interface{}) bool {
return reflect.DeepEqual(e.x, x)
}
func (e eqMatcher) String() string {
return fmt.Sprintf("is equal to %v", e.x)
}
type nilMatcher struct{}
func (nilMatcher) Matches(x interface{}) bool {
if x == nil {
return true
}
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice:
return v.IsNil()
}
return false
}
func (nilMatcher) String() string {
return "is nil"
}
type notMatcher struct {
m Matcher
}
func (n notMatcher) Matches(x interface{}) bool {
return !n.m.Matches(x)
}
func (n notMatcher) String() string {
// TODO: Improve this if we add a NotString method to the Matcher interface.
return "not(" + n.m.String() + ")"
}
// Constructors
func Any() Matcher { return anyMatcher{} }
func Eq(x interface{}) Matcher { return eqMatcher{x} }
func Nil() Matcher { return nilMatcher{} }
func Not(x interface{}) Matcher {
if m, ok := x.(Matcher); ok {
return notMatcher{m}
}
return notMatcher{Eq(x)}
}

View File

@ -0,0 +1,70 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock_test
import (
"errors"
"testing"
"code.google.com/p/gomock/gomock"
mock_matcher "code.google.com/p/gomock/gomock/mock_matcher"
)
func TestMatchers(t *testing.T) {
type e interface{}
type testCase struct {
matcher gomock.Matcher
yes, no []e
}
tests := []testCase{
testCase{gomock.Any(), []e{3, nil, "foo"}, nil},
testCase{gomock.Eq(4), []e{4}, []e{3, "blah", nil, int64(4)}},
testCase{gomock.Nil(),
[]e{nil, (error)(nil), (chan bool)(nil), (*int)(nil)},
[]e{"", 0, make(chan bool), errors.New("err"), new(int)}},
testCase{gomock.Not(gomock.Eq(4)), []e{3, "blah", nil, int64(4)}, []e{4}},
}
for i, test := range tests {
for _, x := range test.yes {
if !test.matcher.Matches(x) {
t.Errorf(`test %d: "%v %s" should be true.`, i, x, test.matcher)
}
}
for _, x := range test.no {
if test.matcher.Matches(x) {
t.Errorf(`test %d: "%v %s" should be false.`, i, x, test.matcher)
}
}
}
}
// A more thorough test of notMatcher
func TestNotMatcher(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockMatcher := mock_matcher.NewMockMatcher(ctrl)
notMatcher := gomock.Not(mockMatcher)
mockMatcher.EXPECT().Matches(4).Return(true)
if match := notMatcher.Matches(4); match {
t.Errorf("notMatcher should not match 4")
}
mockMatcher.EXPECT().Matches(5).Return(false)
if match := notMatcher.Matches(5); !match {
t.Errorf("notMatcher should match 5")
}
}

View File

@ -0,0 +1,49 @@
// Automatically generated by MockGen. DO NOT EDIT!
// Source: code.google.com/p/gomock/gomock (interfaces: Matcher)
package mock_gomock
import (
gomock "code.google.com/p/gomock/gomock"
)
// Mock of Matcher interface
type MockMatcher struct {
ctrl *gomock.Controller
recorder *_MockMatcherRecorder
}
// Recorder for MockMatcher (not exported)
type _MockMatcherRecorder struct {
mock *MockMatcher
}
func NewMockMatcher(ctrl *gomock.Controller) *MockMatcher {
mock := &MockMatcher{ctrl: ctrl}
mock.recorder = &_MockMatcherRecorder{mock}
return mock
}
func (_m *MockMatcher) EXPECT() *_MockMatcherRecorder {
return _m.recorder
}
func (_m *MockMatcher) Matches(_param0 interface{}) bool {
ret := _m.ctrl.Call(_m, "Matches", _param0)
ret0, _ := ret[0].(bool)
return ret0
}
func (_mr *_MockMatcherRecorder) Matches(arg0 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Matches", arg0)
}
func (_m *MockMatcher) String() string {
ret := _m.ctrl.Call(_m, "String")
ret0, _ := ret[0].(string)
return ret0
}
func (_mr *_MockMatcherRecorder) String() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "String")
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,367 @@
// 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 googleapi contains the common code shared by all Google API
// libraries.
package googleapi
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"os"
"strings"
"code.google.com/p/google-api-go-client/googleapi/internal/uritemplates"
)
// ContentTyper is an interface for Readers which know (or would like
// to override) their Content-Type. If a media body doesn't implement
// ContentTyper, the type is sniffed from the content using
// http.DetectContentType.
type ContentTyper interface {
ContentType() string
}
const Version = "0.5"
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code and will always be populated.
Code int `json:"code"`
// Message is the server response message and is only populated when
// explicitly referenced by the JSON server response.
Message string `json:"message"`
// Body is the raw response returned by the server.
// It is often but not always JSON, depending on how the request fails.
Body string
Errors []ErrorItem
}
// ErrorItem is a detailed error code & message from the Google API frontend.
type ErrorItem struct {
// Reason is the typed error code. For example: "some_example".
Reason string `json:"reason"`
// Message is the human-readable description of the error.
Message string `json:"message"`
}
func (e *Error) Error() string {
if len(e.Errors) == 0 && e.Message == "" {
return fmt.Sprintf("googleapi: got HTTP response code %d with body: %v", e.Code, e.Body)
}
var buf bytes.Buffer
fmt.Fprintf(&buf, "googleapi: Error %d: ", e.Code)
if e.Message != "" {
fmt.Fprintf(&buf, "%s", e.Message)
}
if len(e.Errors) == 0 {
return strings.TrimSpace(buf.String())
}
if len(e.Errors) == 1 && e.Errors[0].Message == e.Message {
fmt.Fprintf(&buf, ", %s", e.Errors[0].Reason)
return buf.String()
}
fmt.Fprintln(&buf, "\nMore details:")
for _, v := range e.Errors {
fmt.Fprintf(&buf, "Reason: %s, Message: %s\n", v.Reason, v.Message)
}
return buf.String()
}
type errorReply struct {
Error *Error `json:"error"`
}
// CheckResponse returns an error (of type *Error) if the response
// status code is not 2xx.
func CheckResponse(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
slurp, err := ioutil.ReadAll(res.Body)
if err == nil {
jerr := new(errorReply)
err = json.Unmarshal(slurp, jerr)
if err == nil && jerr.Error != nil {
if jerr.Error.Code == 0 {
jerr.Error.Code = res.StatusCode
}
jerr.Error.Body = string(slurp)
return jerr.Error
}
}
return &Error{
Code: res.StatusCode,
Body: string(slurp),
}
}
type MarshalStyle bool
var WithDataWrapper = MarshalStyle(true)
var WithoutDataWrapper = MarshalStyle(false)
func (wrap MarshalStyle) JSONReader(v interface{}) (io.Reader, error) {
buf := new(bytes.Buffer)
if wrap {
buf.Write([]byte(`{"data": `))
}
err := json.NewEncoder(buf).Encode(v)
if err != nil {
return nil, err
}
if wrap {
buf.Write([]byte(`}`))
}
return buf, nil
}
func getMediaType(media io.Reader) (io.Reader, string) {
if typer, ok := media.(ContentTyper); ok {
return media, typer.ContentType()
}
typ := "application/octet-stream"
buf := make([]byte, 1024)
n, err := media.Read(buf)
buf = buf[:n]
if err == nil {
typ = http.DetectContentType(buf)
}
return io.MultiReader(bytes.NewBuffer(buf), media), typ
}
type Lengther interface {
Len() int
}
// endingWithErrorReader from r until it returns an error. If the
// final error from r is os.EOF and e is non-nil, e is used instead.
type endingWithErrorReader struct {
r io.Reader
e error
}
func (er endingWithErrorReader) Read(p []byte) (n int, err error) {
n, err = er.r.Read(p)
if err == io.EOF && er.e != nil {
err = er.e
}
return
}
func getReaderSize(r io.Reader) (io.Reader, int64) {
// Ideal case, the reader knows its own size.
if lr, ok := r.(Lengther); ok {
return r, int64(lr.Len())
}
// But maybe it's a seeker and we can seek to the end to find its size.
if s, ok := r.(io.Seeker); ok {
pos0, err := s.Seek(0, os.SEEK_CUR)
if err == nil {
posend, err := s.Seek(0, os.SEEK_END)
if err == nil {
_, err = s.Seek(pos0, os.SEEK_SET)
if err == nil {
return r, posend - pos0
} else {
// We moved it forward but can't restore it.
// Seems unlikely, but can't really restore now.
return endingWithErrorReader{strings.NewReader(""), err}, posend - pos0
}
}
}
}
// Otherwise we have to make a copy to calculate how big the reader is.
buf := new(bytes.Buffer)
// TODO(bradfitz): put a cap on this copy? spill to disk after
// a certain point?
_, err := io.Copy(buf, r)
return endingWithErrorReader{buf, err}, int64(buf.Len())
}
func typeHeader(contentType string) textproto.MIMEHeader {
h := make(textproto.MIMEHeader)
h.Set("Content-Type", contentType)
return h
}
// countingWriter counts the number of bytes it receives to write, but
// discards them.
type countingWriter struct {
n *int64
}
func (w countingWriter) Write(p []byte) (int, error) {
*w.n += int64(len(p))
return len(p), nil
}
// ConditionallyIncludeMedia does nothing if media is nil.
//
// bodyp is an in/out parameter. It should initially point to the
// reader of the application/json (or whatever) payload to send in the
// API request. It's updated to point to the multipart body reader.
//
// ctypep is an in/out parameter. It should initially point to the
// content type of the bodyp, usually "application/json". It's updated
// to the "multipart/related" content type, with random boundary.
//
// The return value is the content-length of the entire multpart body.
func ConditionallyIncludeMedia(media io.Reader, bodyp *io.Reader, ctypep *string) (totalContentLength int64, ok bool) {
if media == nil {
return
}
// Get the media type and size. The type check might return a
// different reader instance, so do the size check first,
// which looks at the specific type of the io.Reader.
var mediaType string
if typer, ok := media.(ContentTyper); ok {
mediaType = typer.ContentType()
}
media, mediaSize := getReaderSize(media)
if mediaType == "" {
media, mediaType = getMediaType(media)
}
body, bodyType := *bodyp, *ctypep
body, bodySize := getReaderSize(body)
// Calculate how big the the multipart will be.
{
totalContentLength = bodySize + mediaSize
mpw := multipart.NewWriter(countingWriter{&totalContentLength})
mpw.CreatePart(typeHeader(bodyType))
mpw.CreatePart(typeHeader(mediaType))
mpw.Close()
}
pr, pw := io.Pipe()
mpw := multipart.NewWriter(pw)
*bodyp = pr
*ctypep = "multipart/related; boundary=" + mpw.Boundary()
go func() {
defer pw.Close()
defer mpw.Close()
w, err := mpw.CreatePart(typeHeader(bodyType))
if err != nil {
return
}
_, err = io.Copy(w, body)
if err != nil {
return
}
w, err = mpw.CreatePart(typeHeader(mediaType))
if err != nil {
return
}
_, err = io.Copy(w, media)
if err != nil {
return
}
}()
return totalContentLength, true
}
func ResolveRelative(basestr, relstr string) string {
u, _ := url.Parse(basestr)
rel, _ := url.Parse(relstr)
u = u.ResolveReference(rel)
us := u.String()
us = strings.Replace(us, "%7B", "{", -1)
us = strings.Replace(us, "%7D", "}", -1)
return us
}
// has4860Fix is whether this Go environment contains the fix for
// http://golang.org/issue/4860
var has4860Fix bool
// init initializes has4860Fix by checking the behavior of the net/http package.
func init() {
r := http.Request{
URL: &url.URL{
Scheme: "http",
Opaque: "//opaque",
},
}
b := &bytes.Buffer{}
r.Write(b)
has4860Fix = bytes.HasPrefix(b.Bytes(), []byte("GET http"))
}
// SetOpaque sets u.Opaque from u.Path such that HTTP requests to it
// don't alter any hex-escaped characters in u.Path.
func SetOpaque(u *url.URL) {
u.Opaque = "//" + u.Host + u.Path
if !has4860Fix {
u.Opaque = u.Scheme + ":" + u.Opaque
}
}
// Expand subsitutes any {encoded} strings in the URL passed in using
// the map supplied.
//
// This calls SetOpaque to avoid encoding of the parameters in the URL path.
func Expand(u *url.URL, expansions map[string]string) {
expanded, err := uritemplates.Expand(u.Path, expansions)
if err == nil {
u.Path = expanded
SetOpaque(u)
}
}
// CloseBody is used to close res.Body.
// Prior to calling Close, it also tries to Read a small amount to see an EOF.
// Not seeing an EOF can prevent HTTP Transports from reusing connections.
func CloseBody(res *http.Response) {
if res == nil || res.Body == nil {
return
}
// Justification for 3 byte reads: two for up to "\r\n" after
// a JSON/XML document, and then 1 to see EOF if we haven't yet.
// TODO(bradfitz): detect Go 1.3+ and skip these reads.
// See https://codereview.appspot.com/58240043
// and https://codereview.appspot.com/49570044
buf := make([]byte, 1)
for i := 0; i < 3; i++ {
_, err := res.Body.Read(buf)
if err != nil {
break
}
}
res.Body.Close()
}
// VariantType returns the type name of the given variant.
// If the map doesn't contain the named key or the value is not a []interface{}, "" is returned.
// This is used to support "variant" APIs that can return one of a number of different types.
func VariantType(t map[string]interface{}) string {
s, _ := t["type"].(string)
return s
}
// ConvertVariant uses the JSON encoder/decoder to fill in the struct 'dst' with the fields found in variant 'v'.
// This is used to support "variant" APIs that can return one of a number of different types.
// It reports whether the conversion was successful.
func ConvertVariant(v map[string]interface{}, dst interface{}) bool {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(v)
if err != nil {
return false
}
return json.Unmarshal(buf.Bytes(), dst) == nil
}

View File

@ -0,0 +1,361 @@
// 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 googleapi
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"testing"
)
type SetOpaqueTest struct {
in *url.URL
wantRequestURI string
}
var setOpaqueTests = []SetOpaqueTest{
// no path
{
&url.URL{
Scheme: "http",
Host: "www.golang.org",
},
"http://www.golang.org",
},
// path
{
&url.URL{
Scheme: "http",
Host: "www.golang.org",
Path: "/",
},
"http://www.golang.org/",
},
// file with hex escaping
{
&url.URL{
Scheme: "https",
Host: "www.golang.org",
Path: "/file%20one&two",
},
"https://www.golang.org/file%20one&two",
},
// query
{
&url.URL{
Scheme: "http",
Host: "www.golang.org",
Path: "/",
RawQuery: "q=go+language",
},
"http://www.golang.org/?q=go+language",
},
// file with hex escaping in path plus query
{
&url.URL{
Scheme: "https",
Host: "www.golang.org",
Path: "/file%20one&two",
RawQuery: "q=go+language",
},
"https://www.golang.org/file%20one&two?q=go+language",
},
// query with hex escaping
{
&url.URL{
Scheme: "http",
Host: "www.golang.org",
Path: "/",
RawQuery: "q=go%20language",
},
"http://www.golang.org/?q=go%20language",
},
}
// prefixTmpl is a template for the expected prefix of the output of writing
// an HTTP request.
const prefixTmpl = "GET %v HTTP/1.1\r\nHost: %v\r\n"
func TestSetOpaque(t *testing.T) {
for _, test := range setOpaqueTests {
u := *test.in
SetOpaque(&u)
w := &bytes.Buffer{}
r := &http.Request{URL: &u}
if err := r.Write(w); err != nil {
t.Errorf("write request: %v", err)
continue
}
prefix := fmt.Sprintf(prefixTmpl, test.wantRequestURI, test.in.Host)
if got := string(w.Bytes()); !strings.HasPrefix(got, prefix) {
t.Errorf("got %q expected prefix %q", got, prefix)
}
}
}
type ExpandTest struct {
in string
expansions map[string]string
want string
}
var expandTests = []ExpandTest{
// no expansions
{
"http://www.golang.org/",
map[string]string{},
"http://www.golang.org/",
},
// one expansion, no escaping
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red",
},
"http://www.golang.org/red/delete",
},
// one expansion, with hex escapes
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red/blue",
},
"http://www.golang.org/red%2Fblue/delete",
},
// one expansion, with space
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red or blue",
},
"http://www.golang.org/red%20or%20blue/delete",
},
// expansion not found
{
"http://www.golang.org/{object}/delete",
map[string]string{
"bucket": "red or blue",
},
"http://www.golang.org//delete",
},
// multiple expansions
{
"http://www.golang.org/{one}/{two}/{three}/get",
map[string]string{
"one": "ONE",
"two": "TWO",
"three": "THREE",
},
"http://www.golang.org/ONE/TWO/THREE/get",
},
// utf-8 characters
{
"http://www.golang.org/{bucket}/get",
map[string]string{
"bucket": "£100",
},
"http://www.golang.org/%C2%A3100/get",
},
// punctuations
{
"http://www.golang.org/{bucket}/get",
map[string]string{
"bucket": `/\@:,.`,
},
"http://www.golang.org/%2F%5C%40%3A%2C./get",
},
// mis-matched brackets
{
"http://www.golang.org/{bucket/get",
map[string]string{
"bucket": "red",
},
"http://www.golang.org/{bucket/get",
},
// "+" prefix for suppressing escape
// See also: http://tools.ietf.org/html/rfc6570#section-3.2.3
{
"http://www.golang.org/{+topic}",
map[string]string{
"topic": "/topics/myproject/mytopic",
},
// The double slashes here look weird, but it's intentional
"http://www.golang.org//topics/myproject/mytopic",
},
}
func TestExpand(t *testing.T) {
for i, test := range expandTests {
u := url.URL{
Path: test.in,
}
Expand(&u, test.expansions)
got := u.Path
if got != test.want {
t.Errorf("got %q expected %q in test %d", got, test.want, i+1)
}
}
}
type CheckResponseTest struct {
in *http.Response
bodyText string
want error
errText string
}
var checkResponseTests = []CheckResponseTest{
{
&http.Response{
StatusCode: http.StatusOK,
},
"",
nil,
"",
},
{
&http.Response{
StatusCode: http.StatusInternalServerError,
},
`{"error":{}}`,
&Error{
Code: http.StatusInternalServerError,
Body: `{"error":{}}`,
},
`googleapi: got HTTP response code 500 with body: {"error":{}}`,
},
{
&http.Response{
StatusCode: http.StatusNotFound,
},
`{"error":{"message":"Error message for StatusNotFound."}}`,
&Error{
Code: http.StatusNotFound,
Message: "Error message for StatusNotFound.",
Body: `{"error":{"message":"Error message for StatusNotFound."}}`,
},
"googleapi: Error 404: Error message for StatusNotFound.",
},
{
&http.Response{
StatusCode: http.StatusBadRequest,
},
`{"error":"invalid_token","error_description":"Invalid Value"}`,
&Error{
Code: http.StatusBadRequest,
Body: `{"error":"invalid_token","error_description":"Invalid Value"}`,
},
`googleapi: got HTTP response code 400 with body: {"error":"invalid_token","error_description":"Invalid Value"}`,
},
{
&http.Response{
StatusCode: http.StatusBadRequest,
},
`{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`,
&Error{
Code: http.StatusBadRequest,
Errors: []ErrorItem{
{
Reason: "keyInvalid",
Message: "Bad Request",
},
},
Body: `{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}`,
Message: "Bad Request",
},
"googleapi: Error 400: Bad Request, keyInvalid",
},
}
func TestCheckResponse(t *testing.T) {
for _, test := range checkResponseTests {
res := test.in
if test.bodyText != "" {
res.Body = ioutil.NopCloser(strings.NewReader(test.bodyText))
}
g := CheckResponse(res)
if !reflect.DeepEqual(g, test.want) {
t.Errorf("CheckResponse: got %v, want %v", g, test.want)
gotJson, err := json.Marshal(g)
if err != nil {
t.Error(err)
}
wantJson, err := json.Marshal(test.want)
if err != nil {
t.Error(err)
}
t.Errorf("json(got): %q\njson(want): %q", string(gotJson), string(wantJson))
}
if g != nil && g.Error() != test.errText {
t.Errorf("CheckResponse: unexpected error message.\nGot: %q\nwant: %q", g, test.errText)
}
}
}
type VariantPoint struct {
Type string
Coordinates []float64
}
type VariantTest struct {
in map[string]interface{}
result bool
want VariantPoint
}
var coords = []interface{}{1.0, 2.0}
var variantTests = []VariantTest{
{
in: map[string]interface{}{
"type": "Point",
"coordinates": coords,
},
result: true,
want: VariantPoint{
Type: "Point",
Coordinates: []float64{1.0, 2.0},
},
},
{
in: map[string]interface{}{
"type": "Point",
"bogus": coords,
},
result: true,
want: VariantPoint{
Type: "Point",
},
},
}
func TestVariantType(t *testing.T) {
for _, test := range variantTests {
if g := VariantType(test.in); g != test.want.Type {
t.Errorf("VariantType(%v): got %v, want %v", test.in, g, test.want.Type)
}
}
}
func TestConvertVariant(t *testing.T) {
for _, test := range variantTests {
g := VariantPoint{}
r := ConvertVariant(test.in, &g)
if r != test.result {
t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, r, test.result)
}
if !reflect.DeepEqual(g, test.want) {
t.Errorf("ConvertVariant(%v): got %v, want %v", test.in, g, test.want)
}
}
}

View File

@ -0,0 +1,18 @@
Copyright (c) 2013 Joshua Tacoma
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.

View File

@ -0,0 +1,359 @@
// Copyright 2013 Joshua Tacoma. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uritemplates is a level 4 implementation of RFC 6570 (URI
// Template, http://tools.ietf.org/html/rfc6570).
//
// To use uritemplates, parse a template string and expand it with a value
// map:
//
// template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
// values := make(map[string]interface{})
// values["user"] = "jtacoma"
// values["repo"] = "uritemplates"
// expanded, _ := template.ExpandString(values)
// fmt.Printf(expanded)
//
package uritemplates
import (
"bytes"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
hex = []byte("0123456789ABCDEF")
)
func pctEncode(src []byte) []byte {
dst := make([]byte, len(src)*3)
for i, b := range src {
buf := dst[i*3 : i*3+3]
buf[0] = 0x25
buf[1] = hex[b/16]
buf[2] = hex[b%16]
}
return dst
}
func escape(s string, allowReserved bool) (escaped string) {
if allowReserved {
escaped = string(reserved.ReplaceAllFunc([]byte(s), pctEncode))
} else {
escaped = string(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
}
return escaped
}
// A UriTemplate is a parsed representation of a URI template.
type UriTemplate struct {
raw string
parts []templatePart
}
// Parse parses a URI template string into a UriTemplate object.
func Parse(rawtemplate string) (template *UriTemplate, err error) {
template = new(UriTemplate)
template.raw = rawtemplate
split := strings.Split(rawtemplate, "{")
template.parts = make([]templatePart, len(split)*2-1)
for i, s := range split {
if i == 0 {
if strings.Contains(s, "}") {
err = errors.New("unexpected }")
break
}
template.parts[i].raw = s
} else {
subsplit := strings.Split(s, "}")
if len(subsplit) != 2 {
err = errors.New("malformed template")
break
}
expression := subsplit[0]
template.parts[i*2-1], err = parseExpression(expression)
if err != nil {
break
}
template.parts[i*2].raw = subsplit[1]
}
}
if err != nil {
template = nil
}
return template, err
}
type templatePart struct {
raw string
terms []templateTerm
first string
sep string
named bool
ifemp string
allowReserved bool
}
type templateTerm struct {
name string
explode bool
truncate int
}
func parseExpression(expression string) (result templatePart, err error) {
switch expression[0] {
case '+':
result.sep = ","
result.allowReserved = true
expression = expression[1:]
case '.':
result.first = "."
result.sep = "."
expression = expression[1:]
case '/':
result.first = "/"
result.sep = "/"
expression = expression[1:]
case ';':
result.first = ";"
result.sep = ";"
result.named = true
expression = expression[1:]
case '?':
result.first = "?"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '&':
result.first = "&"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '#':
result.first = "#"
result.sep = ","
result.allowReserved = true
expression = expression[1:]
default:
result.sep = ","
}
rawterms := strings.Split(expression, ",")
result.terms = make([]templateTerm, len(rawterms))
for i, raw := range rawterms {
result.terms[i], err = parseTerm(raw)
if err != nil {
break
}
}
return result, err
}
func parseTerm(term string) (result templateTerm, err error) {
if strings.HasSuffix(term, "*") {
result.explode = true
term = term[:len(term)-1]
}
split := strings.Split(term, ":")
if len(split) == 1 {
result.name = term
} else if len(split) == 2 {
result.name = split[0]
var parsed int64
parsed, err = strconv.ParseInt(split[1], 10, 0)
result.truncate = int(parsed)
} else {
err = errors.New("multiple colons in same term")
}
if !validname.MatchString(result.name) {
err = errors.New("not a valid name: " + result.name)
}
if result.explode && result.truncate > 0 {
err = errors.New("both explode and prefix modifers on same term")
}
return result, err
}
// Expand expands a URI template with a set of values to produce a string.
func (self *UriTemplate) Expand(value interface{}) (string, error) {
values, ismap := value.(map[string]interface{})
if !ismap {
if m, ismap := struct2map(value); !ismap {
return "", errors.New("expected map[string]interface{}, struct, or pointer to struct.")
} else {
return self.Expand(m)
}
}
var buf bytes.Buffer
for _, p := range self.parts {
err := p.expand(&buf, values)
if err != nil {
return "", err
}
}
return buf.String(), nil
}
func (self *templatePart) expand(buf *bytes.Buffer, values map[string]interface{}) error {
if len(self.raw) > 0 {
buf.WriteString(self.raw)
return nil
}
var zeroLen = buf.Len()
buf.WriteString(self.first)
var firstLen = buf.Len()
for _, term := range self.terms {
value, exists := values[term.name]
if !exists {
continue
}
if buf.Len() != firstLen {
buf.WriteString(self.sep)
}
switch v := value.(type) {
case string:
self.expandString(buf, term, v)
case []interface{}:
self.expandArray(buf, term, v)
case map[string]interface{}:
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, v)
default:
if m, ismap := struct2map(value); ismap {
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, m)
} else {
str := fmt.Sprintf("%v", value)
self.expandString(buf, term, str)
}
}
}
if buf.Len() == firstLen {
original := buf.Bytes()[:zeroLen]
buf.Reset()
buf.Write(original)
}
return nil
}
func (self *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) {
if self.named {
buf.WriteString(name)
if empty {
buf.WriteString(self.ifemp)
} else {
buf.WriteString("=")
}
}
}
func (self *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) {
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
self.expandName(buf, t.name, len(s) == 0)
buf.WriteString(escape(s, self.allowReserved))
}
func (self *templatePart) expandArray(buf *bytes.Buffer, t templateTerm, a []interface{}) {
if len(a) == 0 {
return
} else if !t.explode {
self.expandName(buf, t.name, false)
}
for i, value := range a {
if t.explode && i > 0 {
buf.WriteString(self.sep)
} else if i > 0 {
buf.WriteString(",")
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
if self.named && t.explode {
self.expandName(buf, t.name, len(s) == 0)
}
buf.WriteString(escape(s, self.allowReserved))
}
}
func (self *templatePart) expandMap(buf *bytes.Buffer, t templateTerm, m map[string]interface{}) {
if len(m) == 0 {
return
}
if !t.explode {
self.expandName(buf, t.name, len(m) == 0)
}
var firstLen = buf.Len()
for k, value := range m {
if firstLen != buf.Len() {
if t.explode {
buf.WriteString(self.sep)
} else {
buf.WriteString(",")
}
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if t.explode {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune('=')
buf.WriteString(escape(s, self.allowReserved))
} else {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune(',')
buf.WriteString(escape(s, self.allowReserved))
}
}
}
func struct2map(v interface{}) (map[string]interface{}, bool) {
value := reflect.ValueOf(v)
switch value.Type().Kind() {
case reflect.Ptr:
return struct2map(value.Elem().Interface())
case reflect.Struct:
m := make(map[string]interface{})
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag
var name string
if strings.Contains(string(tag), ":") {
name = tag.Get("uri")
} else {
name = strings.TrimSpace(string(tag))
}
if len(name) == 0 {
name = value.Type().Field(i).Name
}
m[name] = value.Field(i).Interface()
}
return m, true
}
return nil, false
}

View File

@ -0,0 +1,13 @@
package uritemplates
func Expand(path string, expansions map[string]string) (string, error) {
template, err := Parse(path)
if err != nil {
return "", err
}
values := make(map[string]interface{})
for k, v := range expansions {
values[k] = v
}
return template.Expand(values)
}

View File

@ -0,0 +1,38 @@
// Copyright 2012 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 transport contains HTTP transports used to make
// authenticated API requests.
package transport
import (
"errors"
"net/http"
)
// APIKey is an HTTP Transport which wraps an underlying transport and
// appends an API Key "key" parameter to the URL of outgoing requests.
type APIKey struct {
// Key is the API Key to set on requests.
Key string
// Transport is the underlying HTTP transport.
// If nil, http.DefaultTransport is used.
Transport http.RoundTripper
}
func (t *APIKey) RoundTrip(req *http.Request) (*http.Response, error) {
rt := t.Transport
if rt == nil {
rt = http.DefaultTransport
if rt == nil {
return nil, errors.New("googleapi/transport: no Transport specified or available")
}
}
newReq := *req
args := newReq.URL.Query()
args.Set("key", t.Key)
newReq.URL.RawQuery = args.Encode()
return rt.RoundTrip(&newReq)
}

View File

@ -0,0 +1,150 @@
// Copyright 2013 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 googleapi
import (
"encoding/json"
"strconv"
)
// Int64s is a slice of int64s that marshal as quoted strings in JSON.
type Int64s []int64
func (q *Int64s) UnmarshalJSON(raw []byte) error {
*q = (*q)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return err
}
*q = append(*q, int64(v))
}
return nil
}
// Int32s is a slice of int32s that marshal as quoted strings in JSON.
type Int32s []int32
func (q *Int32s) UnmarshalJSON(raw []byte) error {
*q = (*q)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
v, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return err
}
*q = append(*q, int32(v))
}
return nil
}
// Uint64s is a slice of uint64s that marshal as quoted strings in JSON.
type Uint64s []uint64
func (q *Uint64s) UnmarshalJSON(raw []byte) error {
*q = (*q)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
v, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return err
}
*q = append(*q, uint64(v))
}
return nil
}
// Uint32s is a slice of uint32s that marshal as quoted strings in JSON.
type Uint32s []uint32
func (q *Uint32s) UnmarshalJSON(raw []byte) error {
*q = (*q)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
v, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return err
}
*q = append(*q, uint32(v))
}
return nil
}
// Float64s is a slice of float64s that marshal as quoted strings in JSON.
type Float64s []float64
func (q *Float64s) UnmarshalJSON(raw []byte) error {
*q = (*q)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return err
}
*q = append(*q, float64(v))
}
return nil
}
func quotedList(n int, fn func(dst []byte, i int) []byte) ([]byte, error) {
dst := make([]byte, 0, 2+n*10) // somewhat arbitrary
dst = append(dst, '[')
for i := 0; i < n; i++ {
if i > 0 {
dst = append(dst, ',')
}
dst = append(dst, '"')
dst = fn(dst, i)
dst = append(dst, '"')
}
dst = append(dst, ']')
return dst, nil
}
func (s Int64s) MarshalJSON() ([]byte, error) {
return quotedList(len(s), func(dst []byte, i int) []byte {
return strconv.AppendInt(dst, s[i], 10)
})
}
func (s Int32s) MarshalJSON() ([]byte, error) {
return quotedList(len(s), func(dst []byte, i int) []byte {
return strconv.AppendInt(dst, int64(s[i]), 10)
})
}
func (s Uint64s) MarshalJSON() ([]byte, error) {
return quotedList(len(s), func(dst []byte, i int) []byte {
return strconv.AppendUint(dst, s[i], 10)
})
}
func (s Uint32s) MarshalJSON() ([]byte, error) {
return quotedList(len(s), func(dst []byte, i int) []byte {
return strconv.AppendUint(dst, uint64(s[i]), 10)
})
}
func (s Float64s) MarshalJSON() ([]byte, error) {
return quotedList(len(s), func(dst []byte, i int) []byte {
return strconv.AppendFloat(dst, s[i], 'g', -1, 64)
})
}

View File

@ -0,0 +1,44 @@
// Copyright 2013 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 googleapi
import (
"encoding/json"
"reflect"
"testing"
)
func TestTypes(t *testing.T) {
type T struct {
I32 Int32s
I64 Int64s
U32 Uint32s
U64 Uint64s
F64 Float64s
}
v := &T{
I32: Int32s{-1, 2, 3},
I64: Int64s{-1, 2, 1 << 33},
U32: Uint32s{1, 2},
U64: Uint64s{1, 2, 1 << 33},
F64: Float64s{1.5, 3.33},
}
got, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
want := `{"I32":["-1","2","3"],"I64":["-1","2","8589934592"],"U32":["1","2"],"U64":["1","2","8589934592"],"F64":["1.5","3.33"]}`
if string(got) != want {
t.Fatalf("Marshal mismatch.\n got: %s\nwant: %s\n", got, want)
}
v2 := new(T)
if err := json.Unmarshal(got, v2); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if !reflect.DeepEqual(v, v2) {
t.Fatalf("Unmarshal didn't produce same results.\n got: %#v\nwant: %#v\n", v, v2)
}
}

View File

@ -0,0 +1,23 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {2014} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,65 @@
# BQSchema [![wercker status](https://app.wercker.com/status/c3ce047415c3b4ba6ac9bc5ad26d1747/s "wercker status")](https://app.wercker.com/project/bykey/c3ce047415c3b4ba6ac9bc5ad26d1747)
BQSchema is a package used to created Google Big Query schema directly from Go structs and import BigQuery QueryResponse into arrays of Go structs.
## Usage
You can use BQSchema to automatically load Google Big Query results into arrays of basic Go structs.
~~~ go
// main.go
package main
import (
"code.google.com/p/google-api-go-client/bigquery/v2"
"github.com/SeanDolphin/bqschema"
)
type person struct{
Name string
Email string
Age int
}
func main() {
// authorize the bigquery service
// create a query
result, err := bq.Jobs.Query("projectID", query).Do()
if err == nil {
var people []person
err := bqschema.ToStructs(result, &people)
// do something with people
}
}
~~~
You can also use BQSchema to create the schema fields when creating new Big Query tables from basic Go structs.
~~~ go
// main.go
package main
import (
"code.google.com/p/google-api-go-client/bigquery/v2"
"github.com/SeanDolphin/bqschema"
)
type person struct{
Name string
Email string
Age int
}
func main() {
// authorize the bigquery service
table, err := bq.Tables.Insert("projectID","datasetID", bigquery.Table{
Schema:bqschema.MustToSchema(person{})
}).Do()
}
~~~
## Documentation
Documentation is available at https://godoc.org/github.com/SeanDolphin/bqschema

View File

@ -0,0 +1,13 @@
package bqschema_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestBqschema(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Bqschema Suite")
}

View File

@ -0,0 +1,130 @@
package bqschema
import (
"code.google.com/p/google-api-go-client/bigquery/v2"
"errors"
"reflect"
"time"
)
var (
ArrayOfArray = errors.New("Array of Arrays not allowed")
UnknownType = errors.New("Unknown type")
NotStruct = errors.New("Can not convert non structs")
)
func ToSchema(src interface{}) (*bigquery.TableSchema, error) {
value := reflect.ValueOf(src)
t := value.Type()
schema := &bigquery.TableSchema{}
if t.Kind() == reflect.Struct {
schema.Fields = make([]*bigquery.TableFieldSchema, t.NumField())
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
v := pointerGuard(value.Field(i))
schema.Fields[i] = &bigquery.TableFieldSchema{
Mode: "required",
Name: sf.Name,
Type: "",
}
kind := v.Kind()
t, isSimple := simpleType(kind)
if isSimple {
schema.Fields[i].Type = t
} else {
switch kind {
case reflect.Struct:
schema.Fields[i].Mode = "nullable"
if t, fields, err := structConversion(v.Interface()); err == nil {
schema.Fields[i].Type = t
schema.Fields[i].Fields = fields
} else {
return schema, err
}
case reflect.Array, reflect.Slice:
schema.Fields[i].Mode = "repeated"
subKind := pointerGuard(v.Type().Elem()).Kind()
t, isSimple := simpleType(subKind)
if isSimple {
schema.Fields[i].Type = t
} else if subKind == reflect.Struct {
subStruct := reflect.Zero(pointerGuard(v.Type().Elem()).Type()).Interface()
if t, fields, err := structConversion(subStruct); err == nil {
schema.Fields[i].Type = t
schema.Fields[i].Fields = fields
} else {
return schema, err
}
} else {
return schema, ArrayOfArray
}
default:
return schema, UnknownType
}
}
}
} else {
return schema, NotStruct
}
return schema, nil
}
func MustToSchema(src interface{}) *bigquery.TableSchema {
schema, err := ToSchema(src)
if err != nil {
panic(err)
}
return schema
}
func simpleType(kind reflect.Kind) (string, bool) {
isSimple := true
t := ""
switch kind {
case reflect.Bool:
t = "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
t = "integer"
case reflect.Float32, reflect.Float64:
t = "float"
case reflect.String:
t = "string"
default:
isSimple = false
}
return t, isSimple
}
func structConversion(src interface{}) (string, []*bigquery.TableFieldSchema, error) {
v := reflect.ValueOf(src)
if v.Type().ConvertibleTo(reflect.TypeOf(time.Time{})) {
return "timestamp", nil, nil
} else {
schema, err := ToSchema(src)
return "record", schema.Fields, err
}
}
func pointerGuard(i interface{}) reflect.Value {
var v reflect.Value
var ok bool
v, ok = i.(reflect.Value)
if !ok {
if t, ok := i.(reflect.Type); ok {
v = reflect.Indirect(reflect.New(t))
}
}
if v.Kind() == reflect.Ptr {
v = reflect.Indirect(reflect.New(v.Type().Elem()))
}
return v
}

View File

@ -0,0 +1,651 @@
package bqschema_test
import (
"code.google.com/p/google-api-go-client/bigquery/v2"
"github.com/SeanDolphin/bqschema"
"reflect"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ToSchema", func() {
Context("when converting structs to Big Query Table Schema.", func() {
table := [][]interface{}{
[]interface{}{
struct {
A int
B float64
C string
D bool
}{},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
"should convert simple structs",
},
[]interface{}{
struct {
A []int
B []float64
C []string
D []bool
}{},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "D",
Type: "boolean",
},
},
},
"should convert structs of arrays of simple types",
},
[]interface{}{
struct {
A struct {
A int
B float64
C string
D bool
}
B struct {
A int
B float64
C string
D bool
}
}{
A: struct {
A int
B float64
C string
D bool
}{},
B: struct {
A int
B float64
C string
D bool
}{},
},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "A",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "B",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
},
},
"should convert structs of structs of simple types",
},
[]interface{}{
struct {
A []struct {
A int
B float64
C string
D bool
}
B []struct {
A int
B float64
C string
D bool
}
}{
A: []struct {
A int
B float64
C string
D bool
}{},
B: []struct {
A int
B float64
C string
D bool
}{},
},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "A",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "B",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
},
},
"should convert array of structs of simple types",
},
[]interface{}{
struct {
A time.Time
}{},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "A",
Type: "timestamp",
},
},
},
"should convert timestamps",
},
[]interface{}{
struct {
A *int
B *float64
C *string
D *bool
}{},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
"should convert pointers to simple values",
},
[]interface{}{
struct {
A []*int
B []*float64
C []*string
D []*bool
}{},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "D",
Type: "boolean",
},
},
},
"should convert structs of arrays of pointers to simple types",
},
[]interface{}{
struct {
A *struct {
A int
B float64
C string
D bool
}
B *struct {
A int
B float64
C string
D bool
}
}{
A: &struct {
A int
B float64
C string
D bool
}{},
B: &struct {
A int
B float64
C string
D bool
}{},
},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "A",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "B",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
},
},
"should convert pointers to structs of structs of simple types",
},
[]interface{}{
struct {
A []*struct {
A int
B float64
C string
D bool
}
B []*struct {
A int
B float64
C string
D bool
}
}{
A: []*struct {
A int
B float64
C string
D bool
}{},
B: []*struct {
A int
B float64
C string
D bool
}{},
},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "A",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
&bigquery.TableFieldSchema{
Mode: "repeated",
Name: "B",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
},
},
"should convert array of pointers to structs of simple types",
},
[]interface{}{
struct {
A *struct {
A *int
B *float64
C *string
D *bool
}
B *struct {
A *int
B *float64
C *string
D *bool
}
}{
A: &struct {
A *int
B *float64
C *string
D *bool
}{},
B: &struct {
A *int
B *float64
C *string
D *bool
}{},
},
bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "A",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
&bigquery.TableFieldSchema{
Mode: "nullable",
Name: "B",
Type: "record",
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "integer",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "float",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "string",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "boolean",
},
},
},
},
},
"should convert pointers to structs of points to structs of simple types",
},
}
for _, data := range table {
object := data[0]
schema := data[1]
It(data[2].(string), func() {
result, err := bqschema.ToSchema(object)
Expect(err).To(BeNil())
Expect(reflect.DeepEqual(schema, *result)).To(BeTrue())
})
}
})
Context("when converting invalid items to Big Query Table Schema", func() {
table := [][]interface{}{
[]interface{}{
1,
bqschema.NotStruct,
"not convert ints to schema",
},
[]interface{}{
1.0,
bqschema.NotStruct,
"not convert floats to schema",
},
[]interface{}{
"some string",
bqschema.NotStruct,
"not convert strings to schema",
},
[]interface{}{
false,
bqschema.NotStruct,
"not convert bools schema",
},
}
for _, data := range table {
object := data[0]
exceptedError := data[1]
It(data[2].(string), func() {
_, err := bqschema.ToSchema(object)
Expect(err).NotTo(BeNil())
Expect(err).To(Equal(exceptedError))
})
}
})
})

View File

@ -0,0 +1,78 @@
package bqschema
import (
"code.google.com/p/google-api-go-client/bigquery/v2"
"reflect"
"strconv"
"strings"
// "log"
)
func ToStructs(result *bigquery.QueryResponse, dst interface{}) error {
var err error
value := reflect.Indirect(reflect.ValueOf(dst))
itemType := value.Type().Elem()
rowCount := len(result.Rows)
nameMap := map[string]string{}
for i := 0; i < itemType.NumField(); i++ {
field := itemType.Field(i)
nameMap[strings.ToLower(field.Name)] = field.Name
}
items := reflect.MakeSlice(value.Type(), rowCount, rowCount)
for i := 0; i < rowCount; i++ {
item := reflect.Indirect(reflect.New(itemType))
row := result.Rows[i]
for j, cell := range row.F {
schemaField := result.Schema.Fields[j]
if name, ok := nameMap[strings.ToLower(schemaField.Name)]; ok {
field := item.FieldByName(name)
if field.IsValid() {
switch field.Kind() {
case reflect.Float64, reflect.Float32:
f, err := strconv.ParseFloat(cell.V.(string), 64)
if err == nil {
field.SetFloat(f)
} else {
return err
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, err := strconv.ParseInt(cell.V.(string), 10, 64)
if err == nil {
field.SetInt(i)
} else {
return err
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i, err := strconv.ParseUint(cell.V.(string), 10, 64)
if err == nil {
field.SetUint(i)
} else {
return err
}
case reflect.Bool:
b, err := strconv.ParseBool(cell.V.(string))
if err == nil {
field.SetBool(b)
} else {
return err
}
case reflect.String:
field.Set(reflect.ValueOf(cell.V))
}
}
}
}
items.Index(i).Set(item)
}
value.Set(items)
return err
}

View File

@ -0,0 +1,322 @@
package bqschema_test
import (
"code.google.com/p/google-api-go-client/bigquery/v2"
"github.com/SeanDolphin/bqschema"
"reflect"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ToStructs", func() {
Context("when converting result rows to array of structs", func() {
It("will fill an array of structs of simple types whos names match", func() {
response := &bigquery.QueryResponse{
Schema: &bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "A",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "B",
Type: "FLOAT",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "C",
Type: "STRING",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "D",
Type: "BOOLEAN",
},
},
},
Rows: []*bigquery.TableRow{
&bigquery.TableRow{
F: []*bigquery.TableCell{
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "2.0",
},
&bigquery.TableCell{
V: "some",
},
&bigquery.TableCell{
V: "false",
},
},
},
},
}
type test1 struct {
A int
B float64
C string
D bool
}
expectedResult := []test1{
test1{
A: 1,
B: 2.0,
C: "some",
D: false,
},
}
var dst []test1
err := bqschema.ToStructs(response, &dst)
Expect(err).To(BeNil())
Expect(reflect.DeepEqual(expectedResult, dst)).To(BeTrue())
})
It("will fill an array of structs of simple types whos names no matter the casing", func() {
response := &bigquery.QueryResponse{
Schema: &bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "lower",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "UPPER",
Type: "FLOAT",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "Title",
Type: "STRING",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "camelCase",
Type: "BOOLEAN",
},
},
},
Rows: []*bigquery.TableRow{
&bigquery.TableRow{
F: []*bigquery.TableCell{
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "2.0",
},
&bigquery.TableCell{
V: "some",
},
&bigquery.TableCell{
V: "false",
},
},
},
},
}
type test2 struct {
Lower int
UPPER float64
Title string
CamelCase bool
}
expectedResult := []test2{
test2{
Lower: 1,
UPPER: 2.0,
Title: "some",
CamelCase: false,
},
}
var dst []test2
err := bqschema.ToStructs(response, &dst)
Expect(err).To(BeNil())
Expect(reflect.DeepEqual(expectedResult, dst)).To(BeTrue())
})
It("will fill an array of structs of non standard types", func() {
response := &bigquery.QueryResponse{
Schema: &bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I64",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I32",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I16",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I8",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "F64",
Type: "FLOAT",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "F32",
Type: "FLOAT",
},
},
},
Rows: []*bigquery.TableRow{
&bigquery.TableRow{
F: []*bigquery.TableCell{
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "2.0",
},
&bigquery.TableCell{
V: "2.0",
},
},
},
},
}
type test3 struct {
I64 int64
I32 int32
I16 int16
I8 int8
F64 float64
F32 float32
}
expectedResult := []test3{
test3{
I64: 1,
I32: 1,
I16: 1,
I8: 1,
F64: 2.0,
F32: 2.0,
},
}
var dst []test3
err := bqschema.ToStructs(response, &dst)
Expect(err).To(BeNil())
Expect(reflect.DeepEqual(expectedResult, dst)).To(BeTrue())
})
It("will fill an array of structs of unsigned ints", func() {
response := &bigquery.QueryResponse{
Schema: &bigquery.TableSchema{
Fields: []*bigquery.TableFieldSchema{
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I64",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I32",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I16",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I8",
Type: "INTEGER",
},
&bigquery.TableFieldSchema{
Mode: "required",
Name: "I",
Type: "INTEGER",
},
},
},
Rows: []*bigquery.TableRow{
&bigquery.TableRow{
F: []*bigquery.TableCell{
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
&bigquery.TableCell{
V: "1",
},
},
},
},
}
type test4 struct {
I64 uint64
I32 uint32
I16 uint16
I8 uint8
I uint
}
expectedResult := []test4{
test4{
I64: 1,
I32: 1,
I16: 1,
I8: 1,
I: 1,
},
}
var dst []test4
err := bqschema.ToStructs(response, &dst)
Expect(err).To(BeNil())
Expect(reflect.DeepEqual(expectedResult, dst)).To(BeTrue())
})
})
})

View File

@ -0,0 +1 @@
box: pjvds/golang

View File

@ -0,0 +1,128 @@
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
package dbus
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
"github.com/godbus/dbus"
)
const (
alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
num = `0123456789`
alphanum = alpha + num
signalBuffer = 100
)
// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
func needsEscape(i int, b byte) bool {
// Escape everything that is not a-z-A-Z-0-9
// Also escape 0-9 if it's the first character
return strings.IndexByte(alphanum, b) == -1 ||
(i == 0 && strings.IndexByte(num, b) != -1)
}
// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
// rules that systemd uses for serializing special characters.
func PathBusEscape(path string) string {
// Special case the empty string
if len(path) == 0 {
return "_"
}
n := []byte{}
for i := 0; i < len(path); i++ {
c := path[i]
if needsEscape(i, c) {
e := fmt.Sprintf("_%x", c)
n = append(n, []byte(e)...)
} else {
n = append(n, c)
}
}
return string(n)
}
// Conn is a connection to systemd's dbus endpoint.
type Conn struct {
sysconn *dbus.Conn
sysobj *dbus.Object
jobListener struct {
jobs map[dbus.ObjectPath]chan string
sync.Mutex
}
subscriber struct {
updateCh chan<- *SubStateUpdate
errCh chan<- error
sync.Mutex
ignore map[dbus.ObjectPath]int64
cleanIgnore int64
}
dispatch map[string]func(dbus.Signal)
}
// New() establishes a connection to the system bus and authenticates.
func New() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
c.initJobs()
return c, nil
}
func (c *Conn) initConnection() error {
var err error
c.sysconn, err = dbus.SystemBusPrivate()
if err != nil {
return err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.sysconn.Auth(methods)
if err != nil {
c.sysconn.Close()
return err
}
err = c.sysconn.Hello()
if err != nil {
c.sysconn.Close()
return err
}
c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
// Setup the listeners on jobs so that we can get completions
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
c.initSubscription()
c.initDispatch()
return nil
}

View File

@ -0,0 +1,79 @@
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dbus
import (
"testing"
)
func TestNeedsEscape(t *testing.T) {
// Anything not 0-9a-zA-Z should always be escaped
for want, vals := range map[bool][]byte{
false: []byte{'a', 'b', 'z', 'A', 'Q', '1', '4', '9'},
true: []byte{'#', '%', '$', '!', '.', '_', '-', '%', '\\'},
} {
for i := 1; i < 10; i++ {
for _, b := range vals {
got := needsEscape(i, b)
if got != want {
t.Errorf("needsEscape(%d, %c) returned %t, want %t", i, b, got, want)
}
}
}
}
// 0-9 in position 0 should be escaped
for want, vals := range map[bool][]byte{
false: []byte{'A', 'a', 'e', 'x', 'Q', 'Z'},
true: []byte{'0', '4', '5', '9'},
} {
for _, b := range vals {
got := needsEscape(0, b)
if got != want {
t.Errorf("needsEscape(0, %c) returned %t, want %t", b, got, want)
}
}
}
}
func TestPathBusEscape(t *testing.T) {
for in, want := range map[string]string{
"": "_",
"foo.service": "foo_2eservice",
"foobar": "foobar",
"woof@woof.service": "woof_40woof_2eservice",
"0123456": "_30123456",
"account_db.service": "account_5fdb_2eservice",
"got-dashes": "got_2ddashes",
} {
got := PathBusEscape(in)
if got != want {
t.Errorf("bad result for PathBusEscape(%s): got %q, want %q", in, got, want)
}
}
}
// TestNew ensures that New() works without errors.
func TestNew(t *testing.T) {
_, err := New()
if err != nil {
t.Fatal(err)
}
}

View File

@ -0,0 +1,406 @@
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dbus
import (
"errors"
"github.com/godbus/dbus"
)
func (c *Conn) initJobs() {
c.jobListener.jobs = make(map[dbus.ObjectPath]chan string)
}
func (c *Conn) jobComplete(signal *dbus.Signal) {
var id uint32
var job dbus.ObjectPath
var unit string
var result string
dbus.Store(signal.Body, &id, &job, &unit, &result)
c.jobListener.Lock()
out, ok := c.jobListener.jobs[job]
if ok {
out <- result
delete(c.jobListener.jobs, job)
}
c.jobListener.Unlock()
}
func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) {
c.jobListener.Lock()
defer c.jobListener.Unlock()
ch := make(chan string, 1)
var path dbus.ObjectPath
err := c.sysobj.Call(job, 0, args...).Store(&path)
if err != nil {
return nil, err
}
c.jobListener.jobs[path] = ch
return ch, nil
}
func (c *Conn) runJob(job string, args ...interface{}) (string, error) {
respCh, err := c.startJob(job, args...)
if err != nil {
return "", err
}
return <-respCh, nil
}
// StartUnit enqueues a start job and depending jobs, if any (unless otherwise
// specified by the mode string).
//
// Takes the unit to activate, plus a mode string. The mode needs to be one of
// replace, fail, isolate, ignore-dependencies, ignore-requirements. If
// "replace" the call will start the unit and its dependencies, possibly
// replacing already queued jobs that conflict with this. If "fail" the call
// will start the unit and its dependencies, but will fail if this would change
// an already queued job. If "isolate" the call will start the unit in question
// and terminate all units that aren't dependencies of it. If
// "ignore-dependencies" it will start a unit but ignore all its dependencies.
// If "ignore-requirements" it will start a unit but only ignore the
// requirement dependencies. It is not recommended to make use of the latter
// two options.
//
// Result string: one of done, canceled, timeout, failed, dependency, skipped.
// done indicates successful execution of a job. canceled indicates that a job
// has been canceled before it finished execution. timeout indicates that the
// job timeout was reached. failed indicates that the job failed. dependency
// indicates that a job this job has been depending on failed and the job hence
// has been removed too. skipped indicates that a job was skipped because it
// didn't apply to the units current state.
func (c *Conn) StartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StartUnit", name, mode)
}
// StopUnit is similar to StartUnit but stops the specified unit rather
// than starting it.
func (c *Conn) StopUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode)
}
// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
func (c *Conn) ReloadUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
}
// RestartUnit restarts a service. If a service is restarted that isn't
// running it will be started.
func (c *Conn) RestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
}
// TryRestartUnit is like RestartUnit, except that a service that isn't running
// is not affected by the restart.
func (c *Conn) TryRestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
}
// ReloadOrRestart attempts a reload if the unit supports it and use a restart
// otherwise.
func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
}
// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
// flavored restart otherwise.
func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
}
// StartTransientUnit() may be used to create and start a transient unit, which
// will be released as soon as it is not running or referenced anymore or the
// system is rebooted. name is the unit name including suffix, and must be
// unique. mode is the same as in StartUnit(), properties contains properties
// of the unit.
func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
}
// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
// processes are killed.
func (c *Conn) KillUnit(name string, signal int32) {
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
}
// ResetFailedUnit resets the "failed" state of a specific unit.
func (c *Conn) ResetFailedUnit(name string) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
}
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
var err error
var props map[string]dbus.Variant
path := unitPath(unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
if err != nil {
return nil, err
}
out := make(map[string]interface{}, len(props))
for k, v := range props {
out[k] = v.Value()
}
return out, nil
}
// GetUnitProperties takes the unit name and returns all of its dbus object properties.
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
}
func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
var err error
var prop dbus.Variant
path := unitPath(unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
if err != nil {
return nil, err
}
return &Property{Name: propertyName, Value: prop}, nil
}
func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
}
// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
}
// SetUnitProperties() may be used to modify certain unit properties at runtime.
// Not all properties may be changed at runtime, but many resource management
// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
// instantly, and stored on disk for future boots, unless runtime is true, in which
// case the settings only apply until the next reboot. name is the name of the unit
// to modify. properties are the settings to set, encoded as an array of property
// name and value pairs.
func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store()
}
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
}
// ListUnits returns an array with all currently loaded units. Note that
// units may be known by multiple names at the same time, and hence there might
// be more unit names loaded than actual units behind them.
func (c *Conn) ListUnits() ([]UnitStatus, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
status := make([]UnitStatus, len(result))
statusInterface := make([]interface{}, len(status))
for i := range status {
statusInterface[i] = &status[i]
}
err = dbus.Store(resultInterface, statusInterface...)
if err != nil {
return nil, err
}
return status, nil
}
type UnitStatus struct {
Name string // The primary unit name as string
Description string // The human readable description string
LoadState string // The load state (i.e. whether the unit file has been loaded successfully)
ActiveState string // The active state (i.e. whether the unit is currently started or not)
SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not)
Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
Path dbus.ObjectPath // The unit object path
JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise
JobType string // The job type as string
JobPath dbus.ObjectPath // The job object path
}
type LinkUnitFileChange EnableUnitFileChange
// LinkUnitFiles() links unit files (that are located outside of the
// usual unit search paths) into the unit search path.
//
// It takes a list of absolute paths to unit files to link and two
// booleans. The first boolean controls whether the unit shall be
// enabled for runtime only (true, /run), or persistently (false,
// /etc).
// The second controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns a list of the changes made. The list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]LinkUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
// EnableUnitFiles() may be used to enable one or more units in the system (by
// creating symlinks to them in /etc or /run).
//
// It takes a list of unit files to enable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and two booleans: the first controls whether the unit shall
// be enabled for runtime only (true, /run), or persistently (false, /etc).
// The second one controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns one boolean and an array with the changes made. The
// boolean signals whether the unit files contained any enablement
// information (i.e. an [Install]) section. The changes list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
var carries_install_info bool
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
if err != nil {
return false, nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]EnableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return false, nil, err
}
return carries_install_info, changes, nil
}
type EnableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// DisableUnitFiles() may be used to disable one or more units in the system (by
// removing symlinks to them from /etc or /run).
//
// It takes a list of unit files to disable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and one boolean: whether the unit was enabled for runtime
// only (true, /run), or persistently (false, /etc).
//
// This call returns an array with the changes made. The changes list
// consists of structures with three strings: the type of the change (one of
// symlink or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]DisableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type DisableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// Reload instructs systemd to scan for and reload unit files. This is
// equivalent to a 'systemctl daemon-reload'.
func (c *Conn) Reload() error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
}
func unitPath(name string) dbus.ObjectPath {
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
}

View File

@ -0,0 +1,332 @@
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dbus
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/godbus/dbus"
)
func setupConn(t *testing.T) *Conn {
conn, err := New()
if err != nil {
t.Fatal(err)
}
return conn
}
func findFixture(target string, t *testing.T) string {
abs, err := filepath.Abs("../fixtures/" + target)
if err != nil {
t.Fatal(err)
}
return abs
}
func setupUnit(target string, conn *Conn, t *testing.T) {
// Blindly stop the unit in case it is running
conn.StopUnit(target, "replace")
// Blindly remove the symlink in case it exists
targetRun := filepath.Join("/run/systemd/system/", target)
os.Remove(targetRun)
}
func linkUnit(target string, conn *Conn, t *testing.T) {
abs := findFixture(target, t)
fixture := []string{abs}
changes, err := conn.LinkUnitFiles(fixture, true, true)
if err != nil {
t.Fatal(err)
}
if len(changes) < 1 {
t.Fatalf("Expected one change, got %v", changes)
}
runPath := filepath.Join("/run/systemd/system/", target)
if changes[0].Filename != runPath {
t.Fatal("Unexpected target filename")
}
}
// Ensure that basic unit starting and stopping works.
func TestStartStopUnit(t *testing.T) {
target := "start-stop.service"
conn := setupConn(t)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
// 2. Start the unit
job, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Job is not done:", job)
}
units, err := conn.ListUnits()
var unit *UnitStatus
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit == nil {
t.Fatalf("Test unit not found in list")
}
if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
// 3. Stop the unit
job, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
units, err = conn.ListUnits()
unit = nil
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit != nil {
t.Fatalf("Test unit found in list, should be stopped")
}
}
// Enables a unit and then immediately tears it down
func TestEnableDisableUnit(t *testing.T) {
target := "enable-disable.service"
conn := setupConn(t)
setupUnit(target, conn, t)
abs := findFixture(target, t)
runPath := filepath.Join("/run/systemd/system/", target)
// 1. Enable the unit
install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true)
if err != nil {
t.Fatal(err)
}
if install != false {
t.Fatal("Install was true")
}
if len(changes) < 1 {
t.Fatalf("Expected one change, got %v", changes)
}
if changes[0].Filename != runPath {
t.Fatal("Unexpected target filename")
}
// 2. Disable the unit
dChanges, err := conn.DisableUnitFiles([]string{abs}, true)
if err != nil {
t.Fatal(err)
}
if len(dChanges) != 1 {
t.Fatalf("Changes should include the path, %v", dChanges)
}
if dChanges[0].Filename != runPath {
t.Fatalf("Change should include correct filename, %+v", dChanges[0])
}
if dChanges[0].Destination != "" {
t.Fatalf("Change destination should be empty, %+v", dChanges[0])
}
}
// TestGetUnitProperties reads the `-.mount` which should exist on all systemd
// systems and ensures that one of its properties is valid.
func TestGetUnitProperties(t *testing.T) {
conn := setupConn(t)
unit := "-.mount"
info, err := conn.GetUnitProperties(unit)
if err != nil {
t.Fatal(err)
}
names := info["Wants"].([]string)
if len(names) < 1 {
t.Fatal("/ is unwanted")
}
if names[0] != "system.slice" {
t.Fatal("unexpected wants for /")
}
prop, err := conn.GetUnitProperty(unit, "Wants")
if err != nil {
t.Fatal(err)
}
if prop.Name != "Wants" {
t.Fatal("unexpected property name")
}
val := prop.Value.Value().([]string)
if !reflect.DeepEqual(val, names) {
t.Fatal("unexpected property value")
}
}
// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a
// unit with an invalid name. This test should be run with --test.timeout set,
// as a fail will manifest as GetUnitProperties hanging indefinitely.
func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) {
conn := setupConn(t)
unit := "//invalid#$^/"
_, err := conn.GetUnitProperties(unit)
if err == nil {
t.Fatal("Expected an error, got nil")
}
_, err = conn.GetUnitProperty(unit, "Wants")
if err == nil {
t.Fatal("Expected an error, got nil")
}
}
// TestSetUnitProperties changes a cgroup setting on the `tmp.mount`
// which should exist on all systemd systems and ensures that the
// property was set.
func TestSetUnitProperties(t *testing.T) {
conn := setupConn(t)
unit := "tmp.mount"
if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil {
t.Fatal(err)
}
info, err := conn.GetUnitTypeProperties(unit, "Mount")
if err != nil {
t.Fatal(err)
}
value := info["CPUShares"].(uint64)
if value != 1023 {
t.Fatal("CPUShares of unit is not 1023:", value)
}
}
// Ensure that basic transient unit starting and stopping works.
func TestStartStopTransientUnit(t *testing.T) {
conn := setupConn(t)
props := []Property{
PropExecStart([]string{"/bin/sleep", "400"}, false),
}
target := fmt.Sprintf("testing-transient-%d.service", rand.Int())
// Start the unit
job, err := conn.StartTransientUnit(target, "replace", props...)
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Job is not done:", job)
}
units, err := conn.ListUnits()
var unit *UnitStatus
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit == nil {
t.Fatalf("Test unit not found in list")
}
if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
// 3. Stop the unit
job, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
units, err = conn.ListUnits()
unit = nil
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit != nil {
t.Fatalf("Test unit found in list, should be stopped")
}
}
func TestConnJobListener(t *testing.T) {
target := "start-stop.service"
conn := setupConn(t)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
jobSize := len(conn.jobListener.jobs)
_, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
_, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
currentJobSize := len(conn.jobListener.jobs)
if jobSize != currentJobSize {
t.Fatal("JobListener jobs leaked")
}
}

View File

@ -0,0 +1,220 @@
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dbus
import (
"github.com/godbus/dbus"
)
// From the systemd docs:
//
// The properties array of StartTransientUnit() may take many of the settings
// that may also be configured in unit files. Not all parameters are currently
// accepted though, but we plan to cover more properties with future release.
// Currently you may set the Description, Slice and all dependency types of
// units, as well as RemainAfterExit, ExecStart for service units,
// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares,
// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth,
// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit,
// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map
// directly to their counterparts in unit files and as normal D-Bus object
// properties. The exception here is the PIDs field of scope units which is
// used for construction of the scope only and specifies the initial PIDs to
// add to the scope object.
type Property struct {
Name string
Value dbus.Variant
}
type PropertyCollection struct {
Name string
Properties []Property
}
type execStart struct {
Path string // the binary path to execute
Args []string // an array with all arguments to pass to the executed command, starting with argument 0
UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly
}
// PropExecStart sets the ExecStart service property. The first argument is a
// slice with the binary path to execute followed by the arguments to pass to
// the executed command. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=
func PropExecStart(command []string, uncleanIsFailure bool) Property {
execStarts := []execStart{
execStart{
Path: command[0],
Args: command,
UncleanIsFailure: uncleanIsFailure,
},
}
return Property{
Name: "ExecStart",
Value: dbus.MakeVariant(execStarts),
}
}
// PropRemainAfterExit sets the RemainAfterExit service property. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit=
func PropRemainAfterExit(b bool) Property {
return Property{
Name: "RemainAfterExit",
Value: dbus.MakeVariant(b),
}
}
// PropDescription sets the Description unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description=
func PropDescription(desc string) Property {
return Property{
Name: "Description",
Value: dbus.MakeVariant(desc),
}
}
func propDependency(name string, units []string) Property {
return Property{
Name: name,
Value: dbus.MakeVariant(units),
}
}
// PropRequires sets the Requires unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=
func PropRequires(units ...string) Property {
return propDependency("Requires", units)
}
// PropRequiresOverridable sets the RequiresOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable=
func PropRequiresOverridable(units ...string) Property {
return propDependency("RequiresOverridable", units)
}
// PropRequisite sets the Requisite unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite=
func PropRequisite(units ...string) Property {
return propDependency("Requisite", units)
}
// PropRequisiteOverridable sets the RequisiteOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable=
func PropRequisiteOverridable(units ...string) Property {
return propDependency("RequisiteOverridable", units)
}
// PropWants sets the Wants unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=
func PropWants(units ...string) Property {
return propDependency("Wants", units)
}
// PropBindsTo sets the BindsTo unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo=
func PropBindsTo(units ...string) Property {
return propDependency("BindsTo", units)
}
// PropRequiredBy sets the RequiredBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy=
func PropRequiredBy(units ...string) Property {
return propDependency("RequiredBy", units)
}
// PropRequiredByOverridable sets the RequiredByOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable=
func PropRequiredByOverridable(units ...string) Property {
return propDependency("RequiredByOverridable", units)
}
// PropWantedBy sets the WantedBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy=
func PropWantedBy(units ...string) Property {
return propDependency("WantedBy", units)
}
// PropBoundBy sets the BoundBy unit property. See
// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy=
func PropBoundBy(units ...string) Property {
return propDependency("BoundBy", units)
}
// PropConflicts sets the Conflicts unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=
func PropConflicts(units ...string) Property {
return propDependency("Conflicts", units)
}
// PropConflictedBy sets the ConflictedBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy=
func PropConflictedBy(units ...string) Property {
return propDependency("ConflictedBy", units)
}
// PropBefore sets the Before unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
func PropBefore(units ...string) Property {
return propDependency("Before", units)
}
// PropAfter sets the After unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After=
func PropAfter(units ...string) Property {
return propDependency("After", units)
}
// PropOnFailure sets the OnFailure unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure=
func PropOnFailure(units ...string) Property {
return propDependency("OnFailure", units)
}
// PropTriggers sets the Triggers unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers=
func PropTriggers(units ...string) Property {
return propDependency("Triggers", units)
}
// PropTriggeredBy sets the TriggeredBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy=
func PropTriggeredBy(units ...string) Property {
return propDependency("TriggeredBy", units)
}
// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo=
func PropPropagatesReloadTo(units ...string) Property {
return propDependency("PropagatesReloadTo", units)
}
// PropRequiresMountsFor sets the RequiresMountsFor unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor=
func PropRequiresMountsFor(units ...string) Property {
return propDependency("RequiresMountsFor", units)
}
// PropSlice sets the Slice unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice=
func PropSlice(slice string) Property {
return Property{
Name: "Slice",
Value: dbus.MakeVariant(slice),
}
}

View File

@ -0,0 +1,33 @@
package dbus
type set struct {
data map[string]bool
}
func (s *set) Add(value string) {
s.data[value] = true
}
func (s *set) Remove(value string) {
delete(s.data, value)
}
func (s *set) Contains(value string) (exists bool) {
_, exists = s.data[value]
return
}
func (s *set) Length() (int) {
return len(s.data)
}
func (s *set) Values() (values []string) {
for val, _ := range s.data {
values = append(values, val)
}
return
}
func newSet() (*set) {
return &set{make(map[string] bool)}
}

View File

@ -0,0 +1,39 @@
package dbus
import (
"testing"
)
// TestBasicSetActions asserts that Add & Remove behavior is correct
func TestBasicSetActions(t *testing.T) {
s := newSet()
if s.Contains("foo") {
t.Fatal("set should not contain 'foo'")
}
s.Add("foo")
if !s.Contains("foo") {
t.Fatal("set should contain 'foo'")
}
v := s.Values()
if len(v) != 1 {
t.Fatal("set.Values did not report correct number of values")
}
if v[0] != "foo" {
t.Fatal("set.Values did not report value")
}
s.Remove("foo")
if s.Contains("foo") {
t.Fatal("set should not contain 'foo'")
}
v = s.Values()
if len(v) != 0 {
t.Fatal("set.Values did not report correct number of values")
}
}

View File

@ -0,0 +1,251 @@
/*
Copyright 2013 CoreOS Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dbus
import (
"errors"
"time"
"github.com/godbus/dbus"
)
const (
cleanIgnoreInterval = int64(10 * time.Second)
ignoreInterval = int64(30 * time.Millisecond)
)
// Subscribe sets up this connection to subscribe to all systemd dbus events.
// This is required before calling SubscribeUnits. When the connection closes
// systemd will automatically stop sending signals so there is no need to
// explicitly call Unsubscribe().
func (c *Conn) Subscribe() error {
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
if err != nil {
return err
}
return nil
}
// Unsubscribe this connection from systemd dbus events.
func (c *Conn) Unsubscribe() error {
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
if err != nil {
return err
}
return nil
}
func (c *Conn) initSubscription() {
c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
}
func (c *Conn) initDispatch() {
ch := make(chan *dbus.Signal, signalBuffer)
c.sysconn.Signal(ch)
go func() {
for {
signal, ok := <-ch
if !ok {
return
}
switch signal.Name {
case "org.freedesktop.systemd1.Manager.JobRemoved":
c.jobComplete(signal)
unitName := signal.Body[2].(string)
var unitPath dbus.ObjectPath
c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
if unitPath != dbus.ObjectPath("") {
c.sendSubStateUpdate(unitPath)
}
case "org.freedesktop.systemd1.Manager.UnitNew":
c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath))
case "org.freedesktop.DBus.Properties.PropertiesChanged":
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
// we only care about SubState updates, which are a Unit property
c.sendSubStateUpdate(signal.Path)
}
}
}
}()
}
// Returns two unbuffered channels which will receive all changed units every
// interval. Deleted units are sent as nil.
func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
}
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
// size of the channels, the comparison function for detecting changes and a filter
// function for cutting down on the noise that your channel receives.
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func (string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
old := make(map[string]*UnitStatus)
statusChan := make(chan map[string]*UnitStatus, buffer)
errChan := make(chan error, buffer)
go func() {
for {
timerChan := time.After(interval)
units, err := c.ListUnits()
if err == nil {
cur := make(map[string]*UnitStatus)
for i := range units {
if filterUnit != nil && filterUnit(units[i].Name) {
continue
}
cur[units[i].Name] = &units[i]
}
// add all new or changed units
changed := make(map[string]*UnitStatus)
for n, u := range cur {
if oldU, ok := old[n]; !ok || isChanged(oldU, u) {
changed[n] = u
}
delete(old, n)
}
// add all deleted units
for oldN := range old {
changed[oldN] = nil
}
old = cur
if len(changed) != 0 {
statusChan <- changed
}
} else {
errChan <- err
}
<-timerChan
}
}()
return statusChan, errChan
}
type SubStateUpdate struct {
UnitName string
SubState string
}
// SetSubStateSubscriber writes to updateCh when any unit's substate changes.
// Although this writes to updateCh on every state change, the reported state
// may be more recent than the change that generated it (due to an unavoidable
// race in the systemd dbus interface). That is, this method provides a good
// way to keep a current view of all units' states, but is not guaranteed to
// show every state transition they go through. Furthermore, state changes
// will only be written to the channel with non-blocking writes. If updateCh
// is full, it attempts to write an error to errCh; if errCh is full, the error
// passes silently.
func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
c.subscriber.Lock()
defer c.subscriber.Unlock()
c.subscriber.updateCh = updateCh
c.subscriber.errCh = errCh
}
func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
c.subscriber.Lock()
defer c.subscriber.Unlock()
if c.subscriber.updateCh == nil {
return
}
if c.shouldIgnore(path) {
return
}
info, err := c.GetUnitProperties(string(path))
if err != nil {
select {
case c.subscriber.errCh <- err:
default:
}
}
name := info["Id"].(string)
substate := info["SubState"].(string)
update := &SubStateUpdate{name, substate}
select {
case c.subscriber.updateCh <- update:
default:
select {
case c.subscriber.errCh <- errors.New("update channel full!"):
default:
}
}
c.updateIgnore(path, info)
}
// The ignore functions work around a wart in the systemd dbus interface.
// Requesting the properties of an unloaded unit will cause systemd to send a
// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's
// properties on UnitNew (as that's the only indication of a new unit coming up
// for the first time), we would enter an infinite loop if we did not attempt
// to detect and ignore these spurious signals. The signal themselves are
// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an
// unloaded unit's signals for a short time after requesting its properties.
// This means that we will miss e.g. a transient unit being restarted
// *immediately* upon failure and also a transient unit being started
// immediately after requesting its status (with systemctl status, for example,
// because this causes a UnitNew signal to be sent which then causes us to fetch
// the properties).
func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
t, ok := c.subscriber.ignore[path]
return ok && t >= time.Now().UnixNano()
}
func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
c.cleanIgnore()
// unit is unloaded - it will trigger bad systemd dbus behavior
if info["LoadState"].(string) == "not-found" {
c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
}
}
// without this, ignore would grow unboundedly over time
func (c *Conn) cleanIgnore() {
now := time.Now().UnixNano()
if c.subscriber.cleanIgnore < now {
c.subscriber.cleanIgnore = now + cleanIgnoreInterval
for p, t := range c.subscriber.ignore {
if t < now {
delete(c.subscriber.ignore, p)
}
}
}
}

View File

@ -0,0 +1,43 @@
package dbus
import (
"time"
)
// SubscriptionSet returns a subscription set which is like conn.Subscribe but
// can filter to only return events for a set of units.
type SubscriptionSet struct {
*set
conn *Conn
}
func (s *SubscriptionSet) filter(unit string) bool {
return !s.Contains(unit)
}
// Subscribe starts listening for dbus events for all of the units in the set.
// Returns channels identical to conn.SubscribeUnits.
func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) {
// TODO: Make fully evented by using systemd 209 with properties changed values
return s.conn.SubscribeUnitsCustom(time.Second, 0,
mismatchUnitStatus,
func(unit string) bool { return s.filter(unit) },
)
}
// NewSubscriptionSet returns a new subscription set.
func (conn *Conn) NewSubscriptionSet() *SubscriptionSet {
return &SubscriptionSet{newSet(), conn}
}
// mismatchUnitStatus returns true if the provided UnitStatus objects
// are not equivalent. false is returned if the objects are equivalent.
// Only the Name, Description and state-related fields are used in
// the comparison.
func mismatchUnitStatus(u1, u2 *UnitStatus) bool {
return u1.Name != u2.Name ||
u1.Description != u2.Description ||
u1.LoadState != u2.LoadState ||
u1.ActiveState != u2.ActiveState ||
u1.SubState != u2.SubState
}

View File

@ -0,0 +1,66 @@
package dbus
import (
"testing"
"time"
)
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
func TestSubscriptionSetUnit(t *testing.T) {
target := "subscribe-events-set.service"
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
subSet := conn.NewSubscriptionSet()
evChan, errChan := subSet.Subscribe()
subSet.Add(target)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
job, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Couldn't start", target)
}
timeout := make(chan bool, 1)
go func() {
time.Sleep(3 * time.Second)
close(timeout)
}()
for {
select {
case changes := <-evChan:
tCh, ok := changes[target]
if !ok {
t.Fatal("Unexpected event:", changes)
}
if tCh.ActiveState == "active" && tCh.Name == target {
goto success
}
case err = <-errChan:
t.Fatal(err)
case <-timeout:
t.Fatal("Reached timeout")
}
}
success:
return
}

View File

@ -0,0 +1,91 @@
package dbus
import (
"testing"
"time"
)
// TestSubscribe exercises the basics of subscription
func TestSubscribe(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
err = conn.Unsubscribe()
if err != nil {
t.Fatal(err)
}
}
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
func TestSubscribeUnit(t *testing.T) {
target := "subscribe-events.service"
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
err = conn.Unsubscribe()
if err != nil {
t.Fatal(err)
}
evChan, errChan := conn.SubscribeUnits(time.Second)
setupUnit(target, conn, t)
linkUnit(target, conn, t)
job, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Couldn't start", target)
}
timeout := make(chan bool, 1)
go func() {
time.Sleep(3 * time.Second)
close(timeout)
}()
for {
select {
case changes := <-evChan:
tCh, ok := changes[target]
// Just continue until we see our event.
if !ok {
continue
}
if tCh.ActiveState == "active" && tCh.Name == target {
goto success
}
case err = <-errChan:
t.Fatal(err)
case <-timeout:
t.Fatal("Reached timeout")
}
}
success:
return
}

View File

@ -0,0 +1,2 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
Victor Vieux <vieux@docker.com> (@vieux)

View File

@ -0,0 +1,85 @@
package symlink
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
const maxLoopCounter = 100
// FollowSymlink will follow an existing link and scope it to the root
// path provided.
func FollowSymlinkInScope(link, root string) (string, error) {
root, err := filepath.Abs(root)
if err != nil {
return "", err
}
link, err = filepath.Abs(link)
if err != nil {
return "", err
}
if link == root {
return root, nil
}
if !strings.HasPrefix(filepath.Dir(link), root) {
return "", fmt.Errorf("%s is not within %s", link, root)
}
prev := "/"
for _, p := range strings.Split(link, "/") {
prev = filepath.Join(prev, p)
prev = filepath.Clean(prev)
loopCounter := 0
for {
loopCounter++
if loopCounter >= maxLoopCounter {
return "", fmt.Errorf("loopCounter reached MAX: %v", loopCounter)
}
if !strings.HasPrefix(prev, root) {
// Don't resolve symlinks outside of root. For example,
// we don't have to check /home in the below.
//
// /home -> usr/home
// FollowSymlinkInScope("/home/bob/foo/bar", "/home/bob/foo")
break
}
stat, err := os.Lstat(prev)
if err != nil {
if os.IsNotExist(err) {
break
}
return "", err
}
if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
dest, err := os.Readlink(prev)
if err != nil {
return "", err
}
if path.IsAbs(dest) {
prev = filepath.Join(root, dest)
} else {
prev, _ = filepath.Abs(prev)
if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) {
prev = filepath.Join(root, filepath.Base(dest))
}
}
} else {
break
}
}
}
return prev, nil
}

View File

@ -0,0 +1,121 @@
package symlink
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func abs(t *testing.T, p string) string {
o, err := filepath.Abs(p)
if err != nil {
t.Fatal(err)
}
return o
}
func TestFollowSymLinkNormal(t *testing.T) {
link := "testdata/fs/a/d/c/data"
rewrite, err := FollowSymlinkInScope(link, "testdata")
if err != nil {
t.Fatal(err)
}
if expected := abs(t, "testdata/b/c/data"); expected != rewrite {
t.Fatalf("Expected %s got %s", expected, rewrite)
}
}
func TestFollowSymLinkRelativePath(t *testing.T) {
link := "testdata/fs/i"
rewrite, err := FollowSymlinkInScope(link, "testdata")
if err != nil {
t.Fatal(err)
}
if expected := abs(t, "testdata/fs/a"); expected != rewrite {
t.Fatalf("Expected %s got %s", expected, rewrite)
}
}
func TestFollowSymLinkUnderLinkedDir(t *testing.T) {
dir, err := ioutil.TempDir("", "docker-fs-test")
if err != nil {
t.Fatal(err)
}
os.Mkdir(filepath.Join(dir, "realdir"), 0700)
os.Symlink("realdir", filepath.Join(dir, "linkdir"))
linkDir := filepath.Join(dir, "linkdir", "foo")
dirUnderLinkDir := filepath.Join(dir, "linkdir", "foo", "bar")
os.MkdirAll(dirUnderLinkDir, 0700)
rewrite, err := FollowSymlinkInScope(dirUnderLinkDir, linkDir)
if err != nil {
t.Fatal(err)
}
if rewrite != dirUnderLinkDir {
t.Fatalf("Expected %s got %s", dirUnderLinkDir, rewrite)
}
}
func TestFollowSymLinkRandomString(t *testing.T) {
if _, err := FollowSymlinkInScope("toto", "testdata"); err == nil {
t.Fatal("Random string should fail but didn't")
}
}
func TestFollowSymLinkLastLink(t *testing.T) {
link := "testdata/fs/a/d"
rewrite, err := FollowSymlinkInScope(link, "testdata")
if err != nil {
t.Fatal(err)
}
if expected := abs(t, "testdata/b"); expected != rewrite {
t.Fatalf("Expected %s got %s", expected, rewrite)
}
}
func TestFollowSymLinkRelativeLink(t *testing.T) {
link := "testdata/fs/a/e/c/data"
rewrite, err := FollowSymlinkInScope(link, "testdata")
if err != nil {
t.Fatal(err)
}
if expected := abs(t, "testdata/fs/b/c/data"); expected != rewrite {
t.Fatalf("Expected %s got %s", expected, rewrite)
}
}
func TestFollowSymLinkRelativeLinkScope(t *testing.T) {
link := "testdata/fs/a/f"
rewrite, err := FollowSymlinkInScope(link, "testdata")
if err != nil {
t.Fatal(err)
}
if expected := abs(t, "testdata/test"); expected != rewrite {
t.Fatalf("Expected %s got %s", expected, rewrite)
}
link = "testdata/fs/b/h"
rewrite, err = FollowSymlinkInScope(link, "testdata")
if err != nil {
t.Fatal(err)
}
if expected := abs(t, "testdata/root"); expected != rewrite {
t.Fatalf("Expected %s got %s", expected, rewrite)
}
}

View File

@ -0,0 +1 @@
/b

View File

@ -0,0 +1 @@
../b

View File

@ -0,0 +1 @@
../../../../test

View File

@ -0,0 +1 @@
../g

View File

@ -0,0 +1 @@
../../../../../../../../../../../../root

View File

@ -0,0 +1 @@
a

25
Godeps/_workspace/src/github.com/godbus/dbus/LICENSE generated vendored Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,38 @@
dbus
----
dbus is a simple library that implements native Go client bindings for the
D-Bus message bus system.
### Features
* Complete native implementation of the D-Bus message protocol
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
* Subpackages that help with the introspection / property interfaces
### Installation
This packages requires Go 1.1. If you installed it and set up your GOPATH, just run:
```
go get github.com/godbus/dbus
```
If you want to use the subpackages, you can install them the same way.
### Usage
The complete package documentation and some simple examples are available at
[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
gives a short overview over the basic usage.
Please note that the API is considered unstable for now and may change without
further notice.
### License
go.dbus is available under the Simplified BSD License; see LICENSE for the full
text.
Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.

253
Godeps/_workspace/src/github.com/godbus/dbus/auth.go generated vendored Normal file
View File

@ -0,0 +1,253 @@
package dbus
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"strconv"
)
// AuthStatus represents the Status of an authentication mechanism.
type AuthStatus byte
const (
// AuthOk signals that authentication is finished; the next command
// from the server should be an OK.
AuthOk AuthStatus = iota
// AuthContinue signals that additional data is needed; the next command
// from the server should be a DATA.
AuthContinue
// AuthError signals an error; the server sent invalid data or some
// other unexpected thing happened and the current authentication
// process should be aborted.
AuthError
)
type authState byte
const (
waitingForData authState = iota
waitingForOk
waitingForReject
)
// Auth defines the behaviour of an authentication mechanism.
type Auth interface {
// Return the name of the mechnism, the argument to the first AUTH command
// and the next status.
FirstData() (name, resp []byte, status AuthStatus)
// Process the given DATA command, and return the argument to the DATA
// command and the next status. If len(resp) == 0, no DATA command is sent.
HandleData(data []byte) (resp []byte, status AuthStatus)
}
// Auth authenticates the connection, trying the given list of authentication
// mechanisms (in that order). If nil is passed, the EXTERNAL and
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
// connections, this method must be called before sending any messages to the
// bus. Auth must not be called on shared connections.
func (conn *Conn) Auth(methods []Auth) error {
if methods == nil {
uid := strconv.Itoa(os.Getuid())
methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
}
in := bufio.NewReader(conn.transport)
err := conn.transport.SendNullByte()
if err != nil {
return err
}
err = authWriteLine(conn.transport, []byte("AUTH"))
if err != nil {
return err
}
s, err := authReadLine(in)
if err != nil {
return err
}
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
return errors.New("dbus: authentication protocol error")
}
s = s[1:]
for _, v := range s {
for _, m := range methods {
if name, data, status := m.FirstData(); bytes.Equal(v, name) {
var ok bool
err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data)
if err != nil {
return err
}
switch status {
case AuthOk:
err, ok = conn.tryAuth(m, waitingForOk, in)
case AuthContinue:
err, ok = conn.tryAuth(m, waitingForData, in)
default:
panic("dbus: invalid authentication status")
}
if err != nil {
return err
}
if ok {
if conn.transport.SupportsUnixFDs() {
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
if err != nil {
return err
}
line, err := authReadLine(in)
if err != nil {
return err
}
switch {
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
conn.EnableUnixFDs()
conn.unixFD = true
case bytes.Equal(line[0], []byte("ERROR")):
default:
return errors.New("dbus: authentication protocol error")
}
}
err = authWriteLine(conn.transport, []byte("BEGIN"))
if err != nil {
return err
}
go conn.inWorker()
go conn.outWorker()
return nil
}
}
}
}
return errors.New("dbus: authentication failed")
}
// tryAuth tries to authenticate with m as the mechanism, using state as the
// initial authState and in for reading input. It returns (nil, true) on
// success, (nil, false) on a REJECTED and (someErr, false) if some other
// error occured.
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
for {
s, err := authReadLine(in)
if err != nil {
return err, false
}
switch {
case state == waitingForData && string(s[0]) == "DATA":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
continue
}
data, status := m.HandleData(s[1])
switch status {
case AuthOk, AuthContinue:
if len(data) != 0 {
err = authWriteLine(conn.transport, []byte("DATA"), data)
if err != nil {
return err, false
}
}
if status == AuthOk {
state = waitingForOk
}
case AuthError:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
}
case state == waitingForData && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForData && string(s[0]) == "ERROR":
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForData && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
}
conn.uuid = string(s[1])
return nil, true
case state == waitingForData:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForOk && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
}
conn.uuid = string(s[1])
return nil, true
case state == waitingForOk && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForOk && (string(s[0]) == "DATA" ||
string(s[0]) == "ERROR"):
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForOk:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForReject && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForReject:
return errors.New("dbus: authentication protocol error"), false
default:
panic("dbus: invalid auth state")
}
}
}
// authReadLine reads a line and separates it into its fields.
func authReadLine(in *bufio.Reader) ([][]byte, error) {
data, err := in.ReadBytes('\n')
if err != nil {
return nil, err
}
data = bytes.TrimSuffix(data, []byte("\r\n"))
return bytes.Split(data, []byte{' '}), nil
}
// authWriteLine writes the given line in the authentication protocol format
// (elements of data separated by a " " and terminated by "\r\n").
func authWriteLine(out io.Writer, data ...[]byte) error {
buf := make([]byte, 0)
for i, v := range data {
buf = append(buf, v...)
if i != len(data)-1 {
buf = append(buf, ' ')
}
}
buf = append(buf, '\r')
buf = append(buf, '\n')
n, err := out.Write(buf)
if err != nil {
return err
}
if n != len(buf) {
return io.ErrUnexpectedEOF
}
return nil
}

View File

@ -0,0 +1,26 @@
package dbus
import (
"encoding/hex"
)
// AuthExternal returns an Auth that authenticates as the given user with the
// EXTERNAL mechanism.
func AuthExternal(user string) Auth {
return authExternal{user}
}
// AuthExternal implements the EXTERNAL authentication mechanism.
type authExternal struct {
user string
}
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("EXTERNAL"), b, AuthOk
}
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
return nil, AuthError
}

View File

@ -0,0 +1,102 @@
package dbus
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
"os"
)
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
// directory of the user.
func AuthCookieSha1(user, home string) Auth {
return authCookieSha1{user, home}
}
type authCookieSha1 struct {
user, home string
}
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
}
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
challenge := make([]byte, len(data)/2)
_, err := hex.Decode(challenge, data)
if err != nil {
return nil, AuthError
}
b := bytes.Split(challenge, []byte{' '})
if len(b) != 3 {
return nil, AuthError
}
context := b[0]
id := b[1]
svchallenge := b[2]
cookie := a.getCookie(context, id)
if cookie == nil {
return nil, AuthError
}
clchallenge := a.generateChallenge()
if clchallenge == nil {
return nil, AuthError
}
hash := sha1.New()
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
hexhash := make([]byte, 2*hash.Size())
hex.Encode(hexhash, hash.Sum(nil))
data = append(clchallenge, ' ')
data = append(data, hexhash...)
resp := make([]byte, 2*len(data))
hex.Encode(resp, data)
return resp, AuthOk
}
// getCookie searches for the cookie identified by id in context and returns
// the cookie content or nil. (Since HandleData can't return a specific error,
// but only whether an error occured, this function also doesn't bother to
// return an error.)
func (a authCookieSha1) getCookie(context, id []byte) []byte {
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
if err != nil {
return nil
}
defer file.Close()
rd := bufio.NewReader(file)
for {
line, err := rd.ReadBytes('\n')
if err != nil {
return nil
}
line = line[:len(line)-1]
b := bytes.Split(line, []byte{' '})
if len(b) != 3 {
return nil
}
if bytes.Equal(b[0], id) {
return b[2]
}
}
}
// generateChallenge returns a random, hex-encoded challenge, or nil on error
// (see above).
func (a authCookieSha1) generateChallenge() []byte {
b := make([]byte, 16)
n, err := rand.Read(b)
if err != nil {
return nil
}
if n != 16 {
return nil
}
enc := make([]byte, 32)
hex.Encode(enc, b)
return enc
}

147
Godeps/_workspace/src/github.com/godbus/dbus/call.go generated vendored Normal file
View File

@ -0,0 +1,147 @@
package dbus
import (
"errors"
"strings"
)
// Call represents a pending or completed method call.
type Call struct {
Destination string
Path ObjectPath
Method string
Args []interface{}
// Strobes when the call is complete.
Done chan *Call
// After completion, the error status. If this is non-nil, it may be an
// error message from the peer (with Error as its type) or some other error.
Err error
// Holds the response once the call is done.
Body []interface{}
}
var errSignature = errors.New("dbus: mismatched signature")
// Store stores the body of the reply into the provided pointers. It returns
// an error if the signatures of the body and retvalues don't match, or if
// the error status is not nil.
func (c *Call) Store(retvalues ...interface{}) error {
if c.Err != nil {
return c.Err
}
return Store(c.Body, retvalues...)
}
// Object represents a remote object on which methods can be invoked.
type Object struct {
conn *Conn
dest string
path ObjectPath
}
// Call calls a method with (*Object).Go and waits for its reply.
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
}
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
// object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return Variant{}, errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
result := Variant{}
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
if err != nil {
return Variant{}, err
}
return result, nil
}
// Go calls a method with the given arguments asynchronously. It returns a
// Call structure representing this method call. The passed channel will
// return the same value once the call is done. If ch is nil, a new channel
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
//
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
// is returned of which only the Err member is valid.
//
// If the method parameter contains a dot ('.'), the part before the last dot
// specifies the interface on which the method is called.
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
iface := ""
i := strings.LastIndex(method, ".")
if i != -1 {
iface = method[:i]
}
method = method[i+1:]
msg := new(Message)
msg.Type = TypeMethodCall
msg.serial = o.conn.getSerial()
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path)
msg.Headers[FieldDestination] = MakeVariant(o.dest)
msg.Headers[FieldMember] = MakeVariant(method)
if iface != "" {
msg.Headers[FieldInterface] = MakeVariant(iface)
}
msg.Body = args
if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
}
if msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 10)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Object).Go")
}
call := &Call{
Destination: o.dest,
Path: o.path,
Method: method,
Args: args,
Done: ch,
}
o.conn.callsLck.Lock()
o.conn.calls[msg.serial] = call
o.conn.callsLck.Unlock()
o.conn.outLck.RLock()
if o.conn.closed {
call.Err = ErrClosed
call.Done <- call
} else {
o.conn.out <- msg
}
o.conn.outLck.RUnlock()
return call
}
o.conn.outLck.RLock()
defer o.conn.outLck.RUnlock()
if o.conn.closed {
return &Call{Err: ErrClosed}
}
o.conn.out <- msg
return &Call{Err: nil}
}
// Destination returns the destination that calls on o are sent to.
func (o *Object) Destination() string {
return o.dest
}
// Path returns the path that calls on o are sent to.
func (o *Object) Path() ObjectPath {
return o.path
}

602
Godeps/_workspace/src/github.com/godbus/dbus/conn.go generated vendored Normal file
View File

@ -0,0 +1,602 @@
package dbus
import (
"errors"
"io"
"os"
"reflect"
"strings"
"sync"
)
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
var (
systemBus *Conn
systemBusLck sync.Mutex
sessionBus *Conn
sessionBusLck sync.Mutex
)
// ErrClosed is the error returned by calls on a closed connection.
var ErrClosed = errors.New("dbus: connection closed by user")
// Conn represents a connection to a message bus (usually, the system or
// session bus).
//
// Connections are either shared or private. Shared connections
// are shared between calls to the functions that return them. As a result,
// the methods Close, Auth and Hello must not be called on them.
//
// Multiple goroutines may invoke methods on a connection simultaneously.
type Conn struct {
transport
busObj *Object
unixFD bool
uuid string
names []string
namesLck sync.RWMutex
serialLck sync.Mutex
nextSerial uint32
serialUsed map[uint32]bool
calls map[uint32]*Call
callsLck sync.RWMutex
handlers map[ObjectPath]map[string]interface{}
handlersLck sync.RWMutex
out chan *Message
closed bool
outLck sync.RWMutex
signals []chan<- *Signal
signalsLck sync.Mutex
eavesdropped chan<- *Message
eavesdroppedLck sync.Mutex
}
// SessionBus returns a shared connection to the session bus, connecting to it
// if not already done.
func SessionBus() (conn *Conn, err error) {
sessionBusLck.Lock()
defer sessionBusLck.Unlock()
if sessionBus != nil {
return sessionBus, nil
}
defer func() {
if conn != nil {
sessionBus = conn
}
}()
conn, err = SessionBusPrivate()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return
}
// SessionBusPrivate returns a new private connection to the session bus.
func SessionBusPrivate() (*Conn, error) {
address := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
if address != "" && address != "autolaunch:" {
return Dial(address)
}
return sessionBusPlatform()
}
// SystemBus returns a shared connection to the system bus, connecting to it if
// not already done.
func SystemBus() (conn *Conn, err error) {
systemBusLck.Lock()
defer systemBusLck.Unlock()
if systemBus != nil {
return systemBus, nil
}
defer func() {
if conn != nil {
systemBus = conn
}
}()
conn, err = SystemBusPrivate()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return
}
// SystemBusPrivate returns a new private connection to the system bus.
func SystemBusPrivate() (*Conn, error) {
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
if address != "" {
return Dial(address)
}
return Dial(defaultSystemBusAddress)
}
// Dial establishes a new private connection to the message bus specified by address.
func Dial(address string) (*Conn, error) {
tr, err := getTransport(address)
if err != nil {
return nil, err
}
return newConn(tr)
}
// NewConn creates a new private *Conn from an already established connection.
func NewConn(conn io.ReadWriteCloser) (*Conn, error) {
return newConn(genericTransport{conn})
}
// newConn creates a new *Conn from a transport.
func newConn(tr transport) (*Conn, error) {
conn := new(Conn)
conn.transport = tr
conn.calls = make(map[uint32]*Call)
conn.out = make(chan *Message, 10)
conn.handlers = make(map[ObjectPath]map[string]interface{})
conn.nextSerial = 1
conn.serialUsed = map[uint32]bool{0: true}
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
return conn, nil
}
// BusObject returns the object owned by the bus daemon which handles
// administrative requests.
func (conn *Conn) BusObject() *Object {
return conn.busObj
}
// Close closes the connection. Any blocked operations will return with errors
// and the channels passed to Eavesdrop and Signal are closed. This method must
// not be called on shared connections.
func (conn *Conn) Close() error {
conn.outLck.Lock()
close(conn.out)
conn.closed = true
conn.outLck.Unlock()
conn.signalsLck.Lock()
for _, ch := range conn.signals {
close(ch)
}
conn.signalsLck.Unlock()
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
close(conn.eavesdropped)
}
conn.eavesdroppedLck.Unlock()
return conn.transport.Close()
}
// Eavesdrop causes conn to send all incoming messages to the given channel
// without further processing. Method replies, errors and signals will not be
// sent to the appropiate channels and method calls will not be handled. If nil
// is passed, the normal behaviour is restored.
//
// The caller has to make sure that ch is sufficiently buffered;
// if a message arrives when a write to ch is not possible, the message is
// discarded.
func (conn *Conn) Eavesdrop(ch chan<- *Message) {
conn.eavesdroppedLck.Lock()
conn.eavesdropped = ch
conn.eavesdroppedLck.Unlock()
}
// getSerial returns an unused serial.
func (conn *Conn) getSerial() uint32 {
conn.serialLck.Lock()
defer conn.serialLck.Unlock()
n := conn.nextSerial
for conn.serialUsed[n] {
n++
}
conn.serialUsed[n] = true
conn.nextSerial = n + 1
return n
}
// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
// called after authentication, but before sending any other messages to the
// bus. Hello must not be called for shared connections.
func (conn *Conn) Hello() error {
var s string
err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s)
if err != nil {
return err
}
conn.namesLck.Lock()
conn.names = make([]string, 1)
conn.names[0] = s
conn.namesLck.Unlock()
return nil
}
// inWorker runs in an own goroutine, reading incoming messages from the
// transport and dispatching them appropiately.
func (conn *Conn) inWorker() {
for {
msg, err := conn.ReadMessage()
if err == nil {
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
select {
case conn.eavesdropped <- msg:
default:
}
conn.eavesdroppedLck.Unlock()
continue
}
conn.eavesdroppedLck.Unlock()
dest, _ := msg.Headers[FieldDestination].value.(string)
found := false
if dest == "" {
found = true
} else {
conn.namesLck.RLock()
if len(conn.names) == 0 {
found = true
}
for _, v := range conn.names {
if dest == v {
found = true
break
}
}
conn.namesLck.RUnlock()
}
if !found {
// Eavesdropped a message, but no channel for it is registered.
// Ignore it.
continue
}
switch msg.Type {
case TypeMethodReply, TypeError:
serial := msg.Headers[FieldReplySerial].value.(uint32)
conn.callsLck.Lock()
if c, ok := conn.calls[serial]; ok {
if msg.Type == TypeError {
name, _ := msg.Headers[FieldErrorName].value.(string)
c.Err = Error{name, msg.Body}
} else {
c.Body = msg.Body
}
c.Done <- c
conn.serialLck.Lock()
delete(conn.serialUsed, serial)
conn.serialLck.Unlock()
delete(conn.calls, serial)
}
conn.callsLck.Unlock()
case TypeSignal:
iface := msg.Headers[FieldInterface].value.(string)
member := msg.Headers[FieldMember].value.(string)
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
// sender is optional for signals.
sender, _ := msg.Headers[FieldSender].value.(string)
if iface == "org.freedesktop.DBus" && member == "NameLost" &&
sender == "org.freedesktop.DBus" {
name, _ := msg.Body[0].(string)
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
copy(conn.names[i:], conn.names[i+1:])
conn.names = conn.names[:len(conn.names)-1]
}
}
conn.namesLck.Unlock()
}
signal := &Signal{
Sender: sender,
Path: msg.Headers[FieldPath].value.(ObjectPath),
Name: iface + "." + member,
Body: msg.Body,
}
conn.signalsLck.Lock()
for _, ch := range conn.signals {
// don't block trying to send a signal
select {
case ch <- signal:
default:
}
}
conn.signalsLck.Unlock()
case TypeMethodCall:
go conn.handleCall(msg)
}
} else if _, ok := err.(InvalidMessageError); !ok {
// Some read error occured (usually EOF); we can't really do
// anything but to shut down all stuff and returns errors to all
// pending replies.
conn.Close()
conn.callsLck.RLock()
for _, v := range conn.calls {
v.Err = err
v.Done <- v
}
conn.callsLck.RUnlock()
return
}
// invalid messages are ignored
}
}
// Names returns the list of all names that are currently owned by this
// connection. The slice is always at least one element long, the first element
// being the unique name of the connection.
func (conn *Conn) Names() []string {
conn.namesLck.RLock()
// copy the slice so it can't be modified
s := make([]string, len(conn.names))
copy(s, conn.names)
conn.namesLck.RUnlock()
return s
}
// Object returns the object identified by the given destination name and path.
func (conn *Conn) Object(dest string, path ObjectPath) *Object {
return &Object{conn, dest, path}
}
// outWorker runs in an own goroutine, encoding and sending messages that are
// sent to conn.out.
func (conn *Conn) outWorker() {
for msg := range conn.out {
err := conn.SendMessage(msg)
conn.callsLck.RLock()
if err != nil {
if c := conn.calls[msg.serial]; c != nil {
c.Err = err
c.Done <- c
}
conn.serialLck.Lock()
delete(conn.serialUsed, msg.serial)
conn.serialLck.Unlock()
} else if msg.Type != TypeMethodCall {
conn.serialLck.Lock()
delete(conn.serialUsed, msg.serial)
conn.serialLck.Unlock()
}
conn.callsLck.RUnlock()
}
}
// Send sends the given message to the message bus. You usually don't need to
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
// call is returned and the same value is sent to ch (which must be buffered)
// once the call is complete. Otherwise, ch is ignored and a Call structure is
// returned of which only the Err member is valid.
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
var call *Call
msg.serial = conn.getSerial()
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 5)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Conn).Send")
}
call = new(Call)
call.Destination, _ = msg.Headers[FieldDestination].value.(string)
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
iface, _ := msg.Headers[FieldInterface].value.(string)
member, _ := msg.Headers[FieldMember].value.(string)
call.Method = iface + "." + member
call.Args = msg.Body
call.Done = ch
conn.callsLck.Lock()
conn.calls[msg.serial] = call
conn.callsLck.Unlock()
conn.outLck.RLock()
if conn.closed {
call.Err = ErrClosed
call.Done <- call
} else {
conn.out <- msg
}
conn.outLck.RUnlock()
} else {
conn.outLck.RLock()
if conn.closed {
call = &Call{Err: ErrClosed}
} else {
conn.out <- msg
call = &Call{Err: nil}
}
conn.outLck.RUnlock()
}
return call
}
// sendError creates an error message corresponding to the parameters and sends
// it to conn.out.
func (conn *Conn) sendError(e Error, dest string, serial uint32) {
msg := new(Message)
msg.Type = TypeError
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest)
}
msg.Headers[FieldErrorName] = MakeVariant(e.Name)
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = e.Body
if len(e.Body) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
}
conn.outLck.RLock()
if !conn.closed {
conn.out <- msg
}
conn.outLck.RUnlock()
}
// sendReply creates a method reply message corresponding to the parameters and
// sends it to conn.out.
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
msg := new(Message)
msg.Type = TypeMethodReply
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest)
}
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
conn.outLck.RLock()
if !conn.closed {
conn.out <- msg
}
conn.outLck.RUnlock()
}
// Signal registers the given channel to be passed all received signal messages.
// The caller has to make sure that ch is sufficiently buffered; if a message
// arrives when a write to c is not possible, it is discarded.
//
// Multiple of these channels can be registered at the same time. Passing a
// channel that already is registered will remove it from the list of the
// registered channels.
//
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
// channel for eavesdropped messages, this channel receives all signals, and
// none of the channels passed to Signal will receive any signals.
func (conn *Conn) Signal(ch chan<- *Signal) {
conn.signalsLck.Lock()
conn.signals = append(conn.signals, ch)
conn.signalsLck.Unlock()
}
// SupportsUnixFDs returns whether the underlying transport supports passing of
// unix file descriptors. If this is false, method calls containing unix file
// descriptors will return an error and emitted signals containing them will
// not be sent.
func (conn *Conn) SupportsUnixFDs() bool {
return conn.unixFD
}
// Error represents a D-Bus message of type Error.
type Error struct {
Name string
Body []interface{}
}
func (e Error) Error() string {
if len(e.Body) >= 1 {
s, ok := e.Body[0].(string)
if ok {
return s
}
}
return e.Name
}
// Signal represents a D-Bus message of type Signal. The name member is given in
// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
type Signal struct {
Sender string
Path ObjectPath
Name string
Body []interface{}
}
// transport is a D-Bus transport.
type transport interface {
// Read and Write raw data (for example, for the authentication protocol).
io.ReadWriteCloser
// Send the initial null byte used for the EXTERNAL mechanism.
SendNullByte() error
// Returns whether this transport supports passing Unix FDs.
SupportsUnixFDs() bool
// Signal the transport that Unix FD passing is enabled for this connection.
EnableUnixFDs()
// Read / send a message, handling things like Unix FDs.
ReadMessage() (*Message, error)
SendMessage(*Message) error
}
var (
transports map[string]func(string) (transport, error) = make(map[string]func(string) (transport, error))
)
func getTransport(address string) (transport, error) {
var err error
var t transport
addresses := strings.Split(address, ";")
for _, v := range addresses {
i := strings.IndexRune(v, ':')
if i == -1 {
err = errors.New("dbus: invalid bus address (no transport)")
continue
}
f := transports[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
}
t, err = f(v[i+1:])
if err == nil {
return t, nil
}
}
return nil, err
}
// dereferenceAll returns a slice that, assuming that vs is a slice of pointers
// of arbitrary types, containes the values that are obtained from dereferencing
// all elements in vs.
func dereferenceAll(vs []interface{}) []interface{} {
for i := range vs {
v := reflect.ValueOf(vs[i])
v = v.Elem()
vs[i] = v.Interface()
}
return vs
}
// getKey gets a key from a the list of keys. Returns "" on error / not found...
func getKey(s, key string) string {
i := strings.Index(s, key)
if i == -1 {
return ""
}
if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' {
return ""
}
j := strings.Index(s, ",")
if j == -1 {
j = len(s)
}
return s[i+len(key)+1 : j]
}

View File

@ -0,0 +1,21 @@
package dbus
import (
"errors"
"os/exec"
)
func sessionBusPlatform() (*Conn, error) {
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
b, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
if len(b) == 0 {
return nil, errors.New("dbus: couldn't determine address of session bus")
}
return Dial("unix:path=" + string(b[:len(b)-1]))
}

View File

@ -0,0 +1,27 @@
// +build !darwin
package dbus
import (
"bytes"
"errors"
"os/exec"
)
func sessionBusPlatform() (*Conn, error) {
cmd := exec.Command("dbus-launch")
b, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
i := bytes.IndexByte(b, '=')
j := bytes.IndexByte(b, '\n')
if i == -1 || j == -1 {
return nil, errors.New("dbus: couldn't determine address of session bus")
}
return Dial(string(b[i+1 : j]))
}

View File

@ -0,0 +1,199 @@
package dbus
import "testing"
func TestSessionBus(t *testing.T) {
_, err := SessionBus()
if err != nil {
t.Error(err)
}
}
func TestSystemBus(t *testing.T) {
_, err := SystemBus()
if err != nil {
t.Error(err)
}
}
func TestSend(t *testing.T) {
bus, err := SessionBus()
if err != nil {
t.Error(err)
}
ch := make(chan *Call, 1)
msg := &Message{
Type: TypeMethodCall,
Flags: 0,
Headers: map[HeaderField]Variant{
FieldDestination: MakeVariant(bus.Names()[0]),
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")),
FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"),
FieldMember: MakeVariant("Ping"),
},
}
call := bus.Send(msg, ch)
<-ch
if call.Err != nil {
t.Error(call.Err)
}
}
type server struct{}
func (server) Double(i int64) (int64, *Error) {
return 2 * i, nil
}
func BenchmarkCall(b *testing.B) {
b.StopTimer()
var s string
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
name := bus.Names()[0]
obj := bus.BusObject()
b.StartTimer()
for i := 0; i < b.N; i++ {
err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s)
if err != nil {
b.Fatal(err)
}
if s != name {
b.Errorf("got %s, wanted %s", s, name)
}
}
}
func BenchmarkCallAsync(b *testing.B) {
b.StopTimer()
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
name := bus.Names()[0]
obj := bus.BusObject()
c := make(chan *Call, 50)
done := make(chan struct{})
go func() {
for i := 0; i < b.N; i++ {
v := <-c
if v.Err != nil {
b.Error(v.Err)
}
s := v.Body[0].(string)
if s != name {
b.Errorf("got %s, wanted %s", s, name)
}
}
close(done)
}()
b.StartTimer()
for i := 0; i < b.N; i++ {
obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name)
}
<-done
}
func BenchmarkServe(b *testing.B) {
b.StopTimer()
srv, err := SessionBus()
if err != nil {
b.Fatal(err)
}
cli, err := SessionBusPrivate()
if err != nil {
b.Fatal(err)
}
if err = cli.Auth(nil); err != nil {
b.Fatal(err)
}
if err = cli.Hello(); err != nil {
b.Fatal(err)
}
benchmarkServe(b, srv, cli)
}
func BenchmarkServeAsync(b *testing.B) {
b.StopTimer()
srv, err := SessionBus()
if err != nil {
b.Fatal(err)
}
cli, err := SessionBusPrivate()
if err != nil {
b.Fatal(err)
}
if err = cli.Auth(nil); err != nil {
b.Fatal(err)
}
if err = cli.Hello(); err != nil {
b.Fatal(err)
}
benchmarkServeAsync(b, srv, cli)
}
func BenchmarkServeSameConn(b *testing.B) {
b.StopTimer()
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
benchmarkServe(b, bus, bus)
}
func BenchmarkServeSameConnAsync(b *testing.B) {
b.StopTimer()
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
benchmarkServeAsync(b, bus, bus)
}
func benchmarkServe(b *testing.B, srv, cli *Conn) {
var r int64
var err error
dest := srv.Names()[0]
srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
obj := cli.Object(dest, "/org/guelfey/DBus/Test")
b.StartTimer()
for i := 0; i < b.N; i++ {
err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r)
if err != nil {
b.Fatal(err)
}
if r != 2*int64(i) {
b.Errorf("got %d, wanted %d", r, 2*int64(i))
}
}
}
func benchmarkServeAsync(b *testing.B, srv, cli *Conn) {
dest := srv.Names()[0]
srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
obj := cli.Object(dest, "/org/guelfey/DBus/Test")
c := make(chan *Call, 50)
done := make(chan struct{})
go func() {
for i := 0; i < b.N; i++ {
v := <-c
if v.Err != nil {
b.Fatal(v.Err)
}
i, r := v.Args[0].(int64), v.Body[0].(int64)
if 2*i != r {
b.Errorf("got %d, wanted %d", r, 2*i)
}
}
close(done)
}()
b.StartTimer()
for i := 0; i < b.N; i++ {
obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i))
}
<-done
}

258
Godeps/_workspace/src/github.com/godbus/dbus/dbus.go generated vendored Normal file
View File

@ -0,0 +1,258 @@
package dbus
import (
"errors"
"reflect"
"strings"
)
var (
byteType = reflect.TypeOf(byte(0))
boolType = reflect.TypeOf(false)
uint8Type = reflect.TypeOf(uint8(0))
int16Type = reflect.TypeOf(int16(0))
uint16Type = reflect.TypeOf(uint16(0))
int32Type = reflect.TypeOf(int32(0))
uint32Type = reflect.TypeOf(uint32(0))
int64Type = reflect.TypeOf(int64(0))
uint64Type = reflect.TypeOf(uint64(0))
float64Type = reflect.TypeOf(float64(0))
stringType = reflect.TypeOf("")
signatureType = reflect.TypeOf(Signature{""})
objectPathType = reflect.TypeOf(ObjectPath(""))
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
interfacesType = reflect.TypeOf([]interface{}{})
unixFDType = reflect.TypeOf(UnixFD(0))
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
)
// An InvalidTypeError signals that a value which cannot be represented in the
// D-Bus wire format was passed to a function.
type InvalidTypeError struct {
Type reflect.Type
}
func (e InvalidTypeError) Error() string {
return "dbus: invalid type " + e.Type.String()
}
// Store copies the values contained in src to dest, which must be a slice of
// pointers. It converts slices of interfaces from src to corresponding structs
// in dest. An error is returned if the lengths of src and dest or the types of
// their elements don't match.
func Store(src []interface{}, dest ...interface{}) error {
if len(src) != len(dest) {
return errors.New("dbus.Store: length mismatch")
}
for i := range src {
if err := store(src[i], dest[i]); err != nil {
return err
}
}
return nil
}
func store(src, dest interface{}) error {
if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) {
reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
return nil
} else if hasStruct(dest) {
rv := reflect.ValueOf(dest).Elem()
switch rv.Kind() {
case reflect.Struct:
vs, ok := src.([]interface{})
if !ok {
return errors.New("dbus.Store: type mismatch")
}
t := rv.Type()
ndest := make([]interface{}, 0, rv.NumField())
for i := 0; i < rv.NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
ndest = append(ndest, rv.Field(i).Addr().Interface())
}
}
if len(vs) != len(ndest) {
return errors.New("dbus.Store: type mismatch")
}
err := Store(vs, ndest...)
if err != nil {
return errors.New("dbus.Store: type mismatch")
}
case reflect.Slice:
sv := reflect.ValueOf(src)
if sv.Kind() != reflect.Slice {
return errors.New("dbus.Store: type mismatch")
}
rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len()))
for i := 0; i < sv.Len(); i++ {
if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil {
return err
}
}
case reflect.Map:
sv := reflect.ValueOf(src)
if sv.Kind() != reflect.Map {
return errors.New("dbus.Store: type mismatch")
}
keys := sv.MapKeys()
rv.Set(reflect.MakeMap(sv.Type()))
for _, key := range keys {
v := reflect.New(sv.Type().Elem())
if err := store(v, sv.MapIndex(key).Interface()); err != nil {
return err
}
rv.SetMapIndex(key, v.Elem())
}
default:
return errors.New("dbus.Store: type mismatch")
}
return nil
} else {
return errors.New("dbus.Store: type mismatch")
}
}
func hasStruct(v interface{}) bool {
t := reflect.TypeOf(v)
for {
switch t.Kind() {
case reflect.Struct:
return true
case reflect.Slice, reflect.Ptr, reflect.Map:
t = t.Elem()
default:
return false
}
}
}
// An ObjectPath is an object path as defined by the D-Bus spec.
type ObjectPath string
// IsValid returns whether the object path is valid.
func (o ObjectPath) IsValid() bool {
s := string(o)
if len(s) == 0 {
return false
}
if s[0] != '/' {
return false
}
if s[len(s)-1] == '/' && len(s) != 1 {
return false
}
// probably not used, but technically possible
if s == "/" {
return true
}
split := strings.Split(s[1:], "/")
for _, v := range split {
if len(v) == 0 {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
// documentation for more information about Unix file descriptor passsing.
type UnixFD int32
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
type UnixFDIndex uint32
// alignment returns the alignment of values of type t.
func alignment(t reflect.Type) int {
switch t {
case variantType:
return 1
case objectPathType:
return 4
case signatureType:
return 1
case interfacesType: // sometimes used for structs
return 8
}
switch t.Kind() {
case reflect.Uint8:
return 1
case reflect.Uint16, reflect.Int16:
return 2
case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
return 4
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
return 8
case reflect.Ptr:
return alignment(t.Elem())
}
return 1
}
// isKeyType returns whether t is a valid type for a D-Bus dict.
func isKeyType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
reflect.String:
return true
}
return false
}
// isValidInterface returns whether s is a valid name for an interface.
func isValidInterface(s string) bool {
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
return false
}
elem := strings.Split(s, ".")
if len(elem) < 2 {
return false
}
for _, v := range elem {
if len(v) == 0 {
return false
}
if v[0] >= '0' && v[0] <= '9' {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// isValidMember returns whether s is a valid name for a member.
func isValidMember(s string) bool {
if len(s) == 0 || len(s) > 255 {
return false
}
i := strings.Index(s, ".")
if i != -1 {
return false
}
if s[0] >= '0' && s[0] <= '9' {
return false
}
for _, c := range s {
if !isMemberChar(c) {
return false
}
}
return true
}
func isMemberChar(c rune) bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '_'
}

228
Godeps/_workspace/src/github.com/godbus/dbus/decoder.go generated vendored Normal file
View File

@ -0,0 +1,228 @@
package dbus
import (
"encoding/binary"
"io"
"reflect"
)
type decoder struct {
in io.Reader
order binary.ByteOrder
pos int
}
// newDecoder returns a new decoder that reads values from in. The input is
// expected to be in the given byte order.
func newDecoder(in io.Reader, order binary.ByteOrder) *decoder {
dec := new(decoder)
dec.in = in
dec.order = order
return dec
}
// align aligns the input to the given boundary and panics on error.
func (dec *decoder) align(n int) {
if dec.pos%n != 0 {
newpos := (dec.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-dec.pos)
if _, err := io.ReadFull(dec.in, empty); err != nil {
panic(err)
}
dec.pos = newpos
}
}
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
func (dec *decoder) binread(v interface{}) {
if err := binary.Read(dec.in, dec.order, v); err != nil {
panic(err)
}
}
func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
defer func() {
var ok bool
v := recover()
if err, ok = v.(error); ok {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = FormatError("unexpected EOF")
}
}
}()
vs = make([]interface{}, 0)
s := sig.str
for s != "" {
err, rem := validSingle(s, 0)
if err != nil {
return nil, err
}
v := dec.decode(s[:len(s)-len(rem)], 0)
vs = append(vs, v)
s = rem
}
return vs, nil
}
func (dec *decoder) decode(s string, depth int) interface{} {
dec.align(alignment(typeFor(s)))
switch s[0] {
case 'y':
var b [1]byte
if _, err := dec.in.Read(b[:]); err != nil {
panic(err)
}
dec.pos++
return b[0]
case 'b':
i := dec.decode("u", depth).(uint32)
switch {
case i == 0:
return false
case i == 1:
return true
default:
panic(FormatError("invalid value for boolean"))
}
case 'n':
var i int16
dec.binread(&i)
dec.pos += 2
return i
case 'i':
var i int32
dec.binread(&i)
dec.pos += 4
return i
case 'x':
var i int64
dec.binread(&i)
dec.pos += 8
return i
case 'q':
var i uint16
dec.binread(&i)
dec.pos += 2
return i
case 'u':
var i uint32
dec.binread(&i)
dec.pos += 4
return i
case 't':
var i uint64
dec.binread(&i)
dec.pos += 8
return i
case 'd':
var f float64
dec.binread(&f)
dec.pos += 8
return f
case 's':
length := dec.decode("u", depth).(uint32)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
return string(b[:len(b)-1])
case 'o':
return ObjectPath(dec.decode("s", depth).(string))
case 'g':
length := dec.decode("y", depth).(byte)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
sig, err := ParseSignature(string(b[:len(b)-1]))
if err != nil {
panic(err)
}
return sig
case 'v':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
var variant Variant
sig := dec.decode("g", depth).(Signature)
if len(sig.str) == 0 {
panic(FormatError("variant signature is empty"))
}
err, rem := validSingle(sig.str, 0)
if err != nil {
panic(err)
}
if rem != "" {
panic(FormatError("variant signature has multiple types"))
}
variant.sig = sig
variant.value = dec.decode(sig.str, depth+1)
return variant
case 'h':
return UnixFDIndex(dec.decode("u", depth).(uint32))
case 'a':
if len(s) > 1 && s[1] == '{' {
ksig := s[2:3]
vsig := s[3 : len(s)-1]
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
// Even for empty maps, the correct padding must be included
dec.align(8)
spos := dec.pos
for dec.pos < spos+int(length) {
dec.align(8)
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
kv := dec.decode(ksig, depth+2)
vv := dec.decode(vsig, depth+2)
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return v.Interface()
}
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length))
// Even for empty arrays, the correct padding must be included
dec.align(alignment(typeFor(s[1:])))
spos := dec.pos
for dec.pos < spos+int(length) {
ev := dec.decode(s[1:], depth+1)
v = reflect.Append(v, reflect.ValueOf(ev))
}
return v.Interface()
case '(':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
dec.align(8)
v := make([]interface{}, 0)
s = s[1 : len(s)-1]
for s != "" {
err, rem := validSingle(s, 0)
if err != nil {
panic(err)
}
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
v = append(v, ev)
s = rem
}
return v
default:
panic(SignatureError{Sig: s})
}
}
// A FormatError is an error in the wire format.
type FormatError string
func (e FormatError) Error() string {
return "dbus: wire format error: " + string(e)
}

63
Godeps/_workspace/src/github.com/godbus/dbus/doc.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
/*
Package dbus implements bindings to the D-Bus message bus system.
To use the message bus API, you first need to connect to a bus (usually the
session or system bus). The acquired connection then can be used to call methods
on remote objects and emit or receive signals. Using the Export method, you can
arrange D-Bus methods calls to be directly translated to method calls on a Go
value.
Conversion Rules
For outgoing messages, Go types are automatically converted to the
corresponding D-Bus types. The following types are directly encoded as their
respective D-Bus equivalents:
Go type | D-Bus type
------------+-----------
byte | BYTE
bool | BOOLEAN
int16 | INT16
uint16 | UINT16
int32 | INT32
uint32 | UINT32
int64 | INT64
uint64 | UINT64
float64 | DOUBLE
string | STRING
ObjectPath | OBJECT_PATH
Signature | SIGNATURE
Variant | VARIANT
UnixFDIndex | UNIX_FD
Slices and arrays encode as ARRAYs of their element type.
Maps encode as DICTs, provided that their key type can be used as a key for
a DICT.
Structs other than Variant and Signature encode as a STRUCT containing their
exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will
be skipped.
Pointers encode as the value they're pointed to.
Trying to encode any other type or a slice, map or struct containing an
unsupported type will result in an InvalidTypeError.
For incoming messages, the inverse of these rules are used, with the exception
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
containing the struct fields in the correct order. The Store function can be
used to convert such values to Go structs.
Unix FD passing
Handling Unix file descriptors deserves special mention. To use them, you should
first check that they are supported on a connection by calling SupportsUnixFDs.
If it returns true, all method of Connection will translate messages containing
UnixFD's to messages that are accompanied by the given file descriptors with the
UnixFD values being substituted by the correct indices. Similarily, the indices
of incoming messages are automatically resolved. It shouldn't be necessary to use
UnixFDIndex.
*/
package dbus

179
Godeps/_workspace/src/github.com/godbus/dbus/encoder.go generated vendored Normal file
View File

@ -0,0 +1,179 @@
package dbus
import (
"bytes"
"encoding/binary"
"io"
"reflect"
)
// An encoder encodes values to the D-Bus wire format.
type encoder struct {
out io.Writer
order binary.ByteOrder
pos int
}
// NewEncoder returns a new encoder that writes to out in the given byte order.
func newEncoder(out io.Writer, order binary.ByteOrder) *encoder {
enc := new(encoder)
enc.out = out
enc.order = order
return enc
}
// Aligns the next output to be on a multiple of n. Panics on write errors.
func (enc *encoder) align(n int) {
if enc.pos%n != 0 {
newpos := (enc.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-enc.pos)
if _, err := enc.out.Write(empty); err != nil {
panic(err)
}
enc.pos = newpos
}
}
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
func (enc *encoder) binwrite(v interface{}) {
if err := binary.Write(enc.out, enc.order, v); err != nil {
panic(err)
}
}
// Encode encodes the given values to the underyling reader. All written values
// are aligned properly as required by the D-Bus spec.
func (enc *encoder) Encode(vs ...interface{}) (err error) {
defer func() {
err, _ = recover().(error)
}()
for _, v := range vs {
enc.encode(reflect.ValueOf(v), 0)
}
return nil
}
// encode encodes the given value to the writer and panics on error. depth holds
// the depth of the container nesting.
func (enc *encoder) encode(v reflect.Value, depth int) {
enc.align(alignment(v.Type()))
switch v.Kind() {
case reflect.Uint8:
var b [1]byte
b[0] = byte(v.Uint())
if _, err := enc.out.Write(b[:]); err != nil {
panic(err)
}
enc.pos++
case reflect.Bool:
if v.Bool() {
enc.encode(reflect.ValueOf(uint32(1)), depth)
} else {
enc.encode(reflect.ValueOf(uint32(0)), depth)
}
case reflect.Int16:
enc.binwrite(int16(v.Int()))
enc.pos += 2
case reflect.Uint16:
enc.binwrite(uint16(v.Uint()))
enc.pos += 2
case reflect.Int32:
enc.binwrite(int32(v.Int()))
enc.pos += 4
case reflect.Uint32:
enc.binwrite(uint32(v.Uint()))
enc.pos += 4
case reflect.Int64:
enc.binwrite(v.Int())
enc.pos += 8
case reflect.Uint64:
enc.binwrite(v.Uint())
enc.pos += 8
case reflect.Float64:
enc.binwrite(v.Float())
enc.pos += 8
case reflect.String:
enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth)
b := make([]byte, v.Len()+1)
copy(b, v.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case reflect.Ptr:
enc.encode(v.Elem(), depth)
case reflect.Slice, reflect.Array:
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
var buf bytes.Buffer
bufenc := newEncoder(&buf, enc.order)
for i := 0; i < v.Len(); i++ {
bufenc.encode(v.Index(i), depth+1)
}
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(alignment(v.Type().Elem()))
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
case reflect.Struct:
if depth >= 64 && v.Type() != signatureType {
panic(FormatError("input exceeds container depth limit"))
}
switch t := v.Type(); t {
case signatureType:
str := v.Field(0)
enc.encode(reflect.ValueOf(byte(str.Len())), depth+1)
b := make([]byte, str.Len()+1)
copy(b, str.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case variantType:
variant := v.Interface().(Variant)
enc.encode(reflect.ValueOf(variant.sig), depth+1)
enc.encode(reflect.ValueOf(variant.value), depth+1)
default:
for i := 0; i < v.Type().NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
enc.encode(v.Field(i), depth+1)
}
}
}
case reflect.Map:
// Maps are arrays of structures, so they actually increase the depth by
// 2.
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
keys := v.MapKeys()
var buf bytes.Buffer
bufenc := newEncoder(&buf, enc.order)
for _, k := range keys {
bufenc.align(8)
bufenc.encode(k, depth+2)
bufenc.encode(v.MapIndex(k), depth+2)
}
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(8)
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
default:
panic(InvalidTypeError{v.Type()})
}
}

View File

@ -0,0 +1,50 @@
package dbus
import "fmt"
func ExampleConn_Emit() {
conn, err := SessionBus()
if err != nil {
panic(err)
}
conn.Emit("/foo/bar", "foo.bar.Baz", uint32(0xDAEDBEEF))
}
func ExampleObject_Call() {
var list []string
conn, err := SessionBus()
if err != nil {
panic(err)
}
err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&list)
if err != nil {
panic(err)
}
for _, v := range list {
fmt.Println(v)
}
}
func ExampleObject_Go() {
conn, err := SessionBus()
if err != nil {
panic(err)
}
ch := make(chan *Call, 10)
conn.BusObject().Go("org.freedesktop.DBus.ListActivatableNames", 0, ch)
select {
case call := <-ch:
if call.Err != nil {
panic(err)
}
list := call.Body[0].([]string)
for _, v := range list {
fmt.Println(v)
}
// put some other cases here
}
}

302
Godeps/_workspace/src/github.com/godbus/dbus/export.go generated vendored Normal file
View File

@ -0,0 +1,302 @@
package dbus
import (
"errors"
"reflect"
"strings"
"unicode"
)
var (
errmsgInvalidArg = Error{
"org.freedesktop.DBus.Error.InvalidArgs",
[]interface{}{"Invalid type / number of args"},
}
errmsgNoObject = Error{
"org.freedesktop.DBus.Error.NoSuchObject",
[]interface{}{"No such object"},
}
errmsgUnknownMethod = Error{
"org.freedesktop.DBus.Error.UnknownMethod",
[]interface{}{"Unknown / invalid method"},
}
)
// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string
func exportedMethod(v interface{}, name string) reflect.Value {
if v == nil {
return reflect.Value{}
}
m := reflect.ValueOf(v).MethodByName(name)
if !m.IsValid() {
return reflect.Value{}
}
t := m.Type()
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) {
return reflect.Value{}
}
return m
}
// handleCall handles the given method call (i.e. looks if it's one of the
// pre-implemented ones and searches for a corresponding handler if not).
func (conn *Conn) handleCall(msg *Message) {
name := msg.Headers[FieldMember].value.(string)
path := msg.Headers[FieldPath].value.(ObjectPath)
ifaceName, hasIface := msg.Headers[FieldInterface].value.(string)
sender, hasSender := msg.Headers[FieldSender].value.(string)
serial := msg.serial
if ifaceName == "org.freedesktop.DBus.Peer" {
switch name {
case "Ping":
conn.sendReply(sender, serial)
case "GetMachineId":
conn.sendReply(sender, serial, conn.uuid)
default:
conn.sendError(errmsgUnknownMethod, sender, serial)
}
return
}
if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
conn.sendError(errmsgUnknownMethod, sender, serial)
}
var m reflect.Value
if hasIface {
conn.handlersLck.RLock()
obj, ok := conn.handlers[path]
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
iface := obj[ifaceName]
conn.handlersLck.RUnlock()
m = exportedMethod(iface, name)
} else {
conn.handlersLck.RLock()
if _, ok := conn.handlers[path]; !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
for _, v := range conn.handlers[path] {
m = exportedMethod(v, name)
if m.IsValid() {
break
}
}
conn.handlersLck.RUnlock()
}
if !m.IsValid() {
conn.sendError(errmsgUnknownMethod, sender, serial)
return
}
t := m.Type()
vs := msg.Body
pointers := make([]interface{}, t.NumIn())
decode := make([]interface{}, 0, len(vs))
for i := 0; i < t.NumIn(); i++ {
tp := t.In(i)
val := reflect.New(tp)
pointers[i] = val.Interface()
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
val.Elem().SetString(sender)
} else {
decode = append(decode, pointers[i])
}
}
if len(decode) != len(vs) {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}
if err := Store(vs, decode...); err != nil {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}
params := make([]reflect.Value, len(pointers))
for i := 0; i < len(pointers); i++ {
params[i] = reflect.ValueOf(pointers[i]).Elem()
}
ret := m.Call(params)
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
conn.sendError(*em, sender, serial)
return
}
if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message)
reply.Type = TypeMethodReply
reply.serial = conn.getSerial()
reply.Headers = make(map[HeaderField]Variant)
if hasSender {
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
}
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
reply.Body = make([]interface{}, len(ret)-1)
for i := 0; i < len(ret)-1; i++ {
reply.Body[i] = ret[i].Interface()
}
if len(ret) != 1 {
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
}
conn.outLck.RLock()
if !conn.closed {
conn.out <- reply
}
conn.outLck.RUnlock()
}
}
// Emit emits the given signal on the message bus. The name parameter must be
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
if !path.IsValid() {
return errors.New("dbus: invalid object path")
}
i := strings.LastIndex(name, ".")
if i == -1 {
return errors.New("dbus: invalid method name")
}
iface := name[:i]
member := name[i+1:]
if !isValidMember(member) {
return errors.New("dbus: invalid method name")
}
if !isValidInterface(iface) {
return errors.New("dbus: invalid interface name")
}
msg := new(Message)
msg.Type = TypeSignal
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldInterface] = MakeVariant(iface)
msg.Headers[FieldMember] = MakeVariant(member)
msg.Headers[FieldPath] = MakeVariant(path)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
conn.outLck.RLock()
defer conn.outLck.RUnlock()
if conn.closed {
return ErrClosed
}
conn.out <- msg
return nil
}
// Export registers the given value to be exported as an object on the
// message bus.
//
// If a method call on the given path and interface is received, an exported
// method with the same name is called with v as the receiver if the
// parameters match and the last return value is of type *Error. If this
// *Error is not nil, it is sent back to the caller as an error.
// Otherwise, a method reply is sent with the other return values as its body.
//
// Any parameters with the special type Sender are set to the sender of the
// dbus message when the method is called. Parameters of this type do not
// contribute to the dbus signature of the method (i.e. the method is exposed
// as if the parameters of type Sender were not there).
//
// Every method call is executed in a new goroutine, so the method may be called
// in multiple goroutines at once.
//
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
// handled for every object.
//
// Passing nil as the first parameter will cause conn to cease handling calls on
// the given combination of path and interface.
//
// Export returns an error if path is not a valid path name.
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
if !path.IsValid() {
return errors.New("dbus: invalid path name")
}
conn.handlersLck.Lock()
if v == nil {
if _, ok := conn.handlers[path]; ok {
delete(conn.handlers[path], iface)
if len(conn.handlers[path]) == 0 {
delete(conn.handlers, path)
}
}
return nil
}
if _, ok := conn.handlers[path]; !ok {
conn.handlers[path] = make(map[string]interface{})
}
conn.handlers[path][iface] = v
conn.handlersLck.Unlock()
return nil
}
// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this
// method to release a name (see below).
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
if err != nil {
return 0, err
}
if r == uint32(ReleaseNameReplyReleased) {
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
copy(conn.names[i:], conn.names[i+1:])
conn.names = conn.names[:len(conn.names)-1]
}
}
conn.namesLck.Unlock()
}
return ReleaseNameReply(r), nil
}
// RequestName calls org.freedesktop.DBus.RequestName. You should use only this
// method to request a name because package dbus needs to keep track of all
// names that the connection has.
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
if err != nil {
return 0, err
}
if r == uint32(RequestNameReplyPrimaryOwner) {
conn.namesLck.Lock()
conn.names = append(conn.names, name)
conn.namesLck.Unlock()
}
return RequestNameReply(r), nil
}
// ReleaseNameReply is the reply to a ReleaseName call.
type ReleaseNameReply uint32
const (
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
ReleaseNameReplyNonExistent
ReleaseNameReplyNotOwner
)
// RequestNameFlags represents the possible flags for a RequestName call.
type RequestNameFlags uint32
const (
NameFlagAllowReplacement RequestNameFlags = 1 << iota
NameFlagReplaceExisting
NameFlagDoNotQueue
)
// RequestNameReply is the reply to a RequestName call.
type RequestNameReply uint32
const (
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
RequestNameReplyInQueue
RequestNameReplyExists
RequestNameReplyAlreadyOwner
)

View File

@ -0,0 +1,28 @@
package dbus
import (
"os"
"sync"
)
var (
homeDir string
homeDirLock sync.Mutex
)
func getHomeDir() string {
homeDirLock.Lock()
defer homeDirLock.Unlock()
if homeDir != "" {
return homeDir
}
homeDir = os.Getenv("HOME")
if homeDir != "" {
return homeDir
}
homeDir = lookupHomeDir()
return homeDir
}

View File

@ -0,0 +1,15 @@
// +build !static_build
package dbus
import (
"os/user"
)
func lookupHomeDir() string {
u, err := user.Current()
if err != nil {
return "/"
}
return u.HomeDir
}

View File

@ -0,0 +1,45 @@
// +build static_build
package dbus
import (
"bufio"
"os"
"strconv"
"strings"
)
func lookupHomeDir() string {
myUid := os.Getuid()
f, err := os.Open("/etc/passwd")
if err != nil {
return "/"
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
break
}
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
parts := strings.Split(line, ":")
if len(parts) >= 6 {
uid, err := strconv.Atoi(parts[2])
if err == nil && uid == myUid {
return parts[5]
}
}
}
// Default to / if we can't get a better value
return "/"
}

View File

@ -0,0 +1,27 @@
package introspect
import (
"encoding/xml"
"github.com/godbus/dbus"
"strings"
)
// Call calls org.freedesktop.Introspectable.Introspect on a remote object
// and returns the introspection data.
func Call(o *dbus.Object) (*Node, error) {
var xmldata string
var node Node
err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata)
if err != nil {
return nil, err
}
err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node)
if err != nil {
return nil, err
}
if node.Name == "" {
node.Name = string(o.Path())
}
return &node, nil
}

View File

@ -0,0 +1,86 @@
// Package introspect provides some utilities for dealing with the DBus
// introspection format.
package introspect
import "encoding/xml"
// The introspection data for the org.freedesktop.DBus.Introspectable interface.
var IntrospectData = Interface{
Name: "org.freedesktop.DBus.Introspectable",
Methods: []Method{
{
Name: "Introspect",
Args: []Arg{
{"out", "s", "out"},
},
},
},
}
// XML document type declaration of the introspection format version 1.0
const IntrospectDeclarationString = `
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
`
// The introspection data for the org.freedesktop.DBus.Introspectable interface,
// as a string.
const IntrospectDataString = `
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="out" direction="out" type="s"/>
</method>
</interface>
`
// Node is the root element of an introspection.
type Node struct {
XMLName xml.Name `xml:"node"`
Name string `xml:"name,attr,omitempty"`
Interfaces []Interface `xml:"interface"`
Children []Node `xml:"node,omitempty"`
}
// Interface describes a DBus interface that is available on the message bus.
type Interface struct {
Name string `xml:"name,attr"`
Methods []Method `xml:"method"`
Signals []Signal `xml:"signal"`
Properties []Property `xml:"property"`
Annotations []Annotation `xml:"annotation"`
}
// Method describes a Method on an Interface as retured by an introspection.
type Method struct {
Name string `xml:"name,attr"`
Args []Arg `xml:"arg"`
Annotations []Annotation `xml:"annotation"`
}
// Signal describes a Signal emitted on an Interface.
type Signal struct {
Name string `xml:"name,attr"`
Args []Arg `xml:"arg"`
Annotations []Annotation `xml:"annotation"`
}
// Property describes a property of an Interface.
type Property struct {
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Access string `xml:"access,attr"`
Annotations []Annotation `xml:"annotation"`
}
// Arg represents an argument of a method or a signal.
type Arg struct {
Name string `xml:"name,attr,omitempty"`
Type string `xml:"type,attr"`
Direction string `xml:"direction,attr,omitempty"`
}
// Annotation is an annotation in the introspection format.
type Annotation struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}

View File

@ -0,0 +1,75 @@
package introspect
import (
"encoding/xml"
"github.com/godbus/dbus"
"reflect"
"strings"
)
// Introspectable implements org.freedesktop.Introspectable.
//
// You can create it by converting the XML-formatted introspection data from a
// string to an Introspectable or call NewIntrospectable with a Node. Then,
// export it as org.freedesktop.Introspectable on you object.
type Introspectable string
// NewIntrospectable returns an Introspectable that returns the introspection
// data that corresponds to the given Node. If n.Interfaces doesn't contain the
// data for org.freedesktop.DBus.Introspectable, it is added automatically.
func NewIntrospectable(n *Node) Introspectable {
found := false
for _, v := range n.Interfaces {
if v.Name == "org.freedesktop.DBus.Introspectable" {
found = true
break
}
}
if !found {
n.Interfaces = append(n.Interfaces, IntrospectData)
}
b, err := xml.Marshal(n)
if err != nil {
panic(err)
}
return Introspectable(strings.TrimSpace(IntrospectDeclarationString) + string(b))
}
// Introspect implements org.freedesktop.Introspectable.Introspect.
func (i Introspectable) Introspect() (string, *dbus.Error) {
return string(i), nil
}
// Methods returns the description of the methods of v. This can be used to
// create a Node which can be passed to NewIntrospectable.
func Methods(v interface{}) []Method {
t := reflect.TypeOf(v)
ms := make([]Method, 0, t.NumMethod())
for i := 0; i < t.NumMethod(); i++ {
if t.Method(i).PkgPath != "" {
continue
}
mt := t.Method(i).Type
if mt.NumOut() == 0 ||
mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{"", nil}) {
continue
}
var m Method
m.Name = t.Method(i).Name
m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2)
for j := 1; j < mt.NumIn(); j++ {
if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() {
arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"}
m.Args = append(m.Args, arg)
}
}
for j := 0; j < mt.NumOut()-1; j++ {
arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"}
m.Args = append(m.Args, arg)
}
m.Annotations = make([]Annotation, 0)
ms = append(ms, m)
}
return ms
}

346
Godeps/_workspace/src/github.com/godbus/dbus/message.go generated vendored Normal file
View File

@ -0,0 +1,346 @@
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"reflect"
"strconv"
)
const protoVersion byte = 1
// Flags represents the possible flags of a D-Bus message.
type Flags byte
const (
// FlagNoReplyExpected signals that the message is not expected to generate
// a reply. If this flag is set on outgoing messages, any possible reply
// will be discarded.
FlagNoReplyExpected Flags = 1 << iota
// FlagNoAutoStart signals that the message bus should not automatically
// start an application when handling this message.
FlagNoAutoStart
)
// Type represents the possible types of a D-Bus message.
type Type byte
const (
TypeMethodCall Type = 1 + iota
TypeMethodReply
TypeError
TypeSignal
typeMax
)
func (t Type) String() string {
switch t {
case TypeMethodCall:
return "method call"
case TypeMethodReply:
return "reply"
case TypeError:
return "error"
case TypeSignal:
return "signal"
}
return "invalid"
}
// HeaderField represents the possible byte codes for the headers
// of a D-Bus message.
type HeaderField byte
const (
FieldPath HeaderField = 1 + iota
FieldInterface
FieldMember
FieldErrorName
FieldReplySerial
FieldDestination
FieldSender
FieldSignature
FieldUnixFDs
fieldMax
)
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
// invalid.
type InvalidMessageError string
func (e InvalidMessageError) Error() string {
return "dbus: invalid message: " + string(e)
}
// fieldType are the types of the various header fields.
var fieldTypes = [fieldMax]reflect.Type{
FieldPath: objectPathType,
FieldInterface: stringType,
FieldMember: stringType,
FieldErrorName: stringType,
FieldReplySerial: uint32Type,
FieldDestination: stringType,
FieldSender: stringType,
FieldSignature: signatureType,
FieldUnixFDs: uint32Type,
}
// requiredFields lists the header fields that are required by the different
// message types.
var requiredFields = [typeMax][]HeaderField{
TypeMethodCall: {FieldPath, FieldMember},
TypeMethodReply: {FieldReplySerial},
TypeError: {FieldErrorName, FieldReplySerial},
TypeSignal: {FieldPath, FieldInterface, FieldMember},
}
// Message represents a single D-Bus message.
type Message struct {
Type
Flags
Headers map[HeaderField]Variant
Body []interface{}
serial uint32
}
type header struct {
Field byte
Variant
}
// DecodeMessage tries to decode a single message in the D-Bus wire format
// from the given reader. The byte order is figured out from the first byte.
// The possibly returned error can be an error of the underlying reader, an
// InvalidMessageError or a FormatError.
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
var order binary.ByteOrder
var hlength, length uint32
var typ, flags, proto byte
var headers []header
b := make([]byte, 1)
_, err = rd.Read(b)
if err != nil {
return
}
switch b[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
dec := newDecoder(rd, order)
dec.pos = 1
msg = new(Message)
vs, err := dec.Decode(Signature{"yyyuu"})
if err != nil {
return nil, err
}
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
return nil, err
}
msg.Type = Type(typ)
msg.Flags = Flags(flags)
// get the header length separately because we need it later
b = make([]byte, 4)
_, err = io.ReadFull(rd, b)
if err != nil {
return nil, err
}
binary.Read(bytes.NewBuffer(b), order, &hlength)
if hlength+length+16 > 1<<27 {
return nil, InvalidMessageError("message is too long")
}
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order)
dec.pos = 12
vs, err = dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
if err = Store(vs, &headers); err != nil {
return nil, err
}
msg.Headers = make(map[HeaderField]Variant)
for _, v := range headers {
msg.Headers[HeaderField(v.Field)] = v.Variant
}
dec.align(8)
body := make([]byte, int(length))
if length != 0 {
_, err := io.ReadFull(rd, body)
if err != nil {
return nil, err
}
}
if err = msg.IsValid(); err != nil {
return nil, err
}
sig, _ := msg.Headers[FieldSignature].value.(Signature)
if sig.str != "" {
buf := bytes.NewBuffer(body)
dec = newDecoder(buf, order)
vs, err := dec.Decode(sig)
if err != nil {
return nil, err
}
msg.Body = vs
}
return
}
// EncodeTo encodes and sends a message to the given writer. The byte order must
// be either binary.LittleEndian or binary.BigEndian. If the message is not
// valid or an error occurs when writing, an error is returned.
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
if err := msg.IsValid(); err != nil {
return err
}
var vs [7]interface{}
switch order {
case binary.LittleEndian:
vs[0] = byte('l')
case binary.BigEndian:
vs[0] = byte('B')
default:
return errors.New("dbus: invalid byte order")
}
body := new(bytes.Buffer)
enc := newEncoder(body, order)
if len(msg.Body) != 0 {
enc.Encode(msg.Body...)
}
vs[1] = msg.Type
vs[2] = msg.Flags
vs[3] = protoVersion
vs[4] = uint32(len(body.Bytes()))
vs[5] = msg.serial
headers := make([]header, 0, len(msg.Headers))
for k, v := range msg.Headers {
headers = append(headers, header{byte(k), v})
}
vs[6] = headers
var buf bytes.Buffer
enc = newEncoder(&buf, order)
enc.Encode(vs[:]...)
enc.align(8)
body.WriteTo(&buf)
if buf.Len() > 1<<27 {
return InvalidMessageError("message is too long")
}
if _, err := buf.WriteTo(out); err != nil {
return err
}
return nil
}
// IsValid checks whether msg is a valid message and returns an
// InvalidMessageError if it is not.
func (msg *Message) IsValid() error {
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 {
return InvalidMessageError("invalid flags")
}
if msg.Type == 0 || msg.Type >= typeMax {
return InvalidMessageError("invalid message type")
}
for k, v := range msg.Headers {
if k == 0 || k >= fieldMax {
return InvalidMessageError("invalid header")
}
if reflect.TypeOf(v.value) != fieldTypes[k] {
return InvalidMessageError("invalid type of header field")
}
}
for _, v := range requiredFields[msg.Type] {
if _, ok := msg.Headers[v]; !ok {
return InvalidMessageError("missing required header")
}
}
if path, ok := msg.Headers[FieldPath]; ok {
if !path.value.(ObjectPath).IsValid() {
return InvalidMessageError("invalid path name")
}
}
if iface, ok := msg.Headers[FieldInterface]; ok {
if !isValidInterface(iface.value.(string)) {
return InvalidMessageError("invalid interface name")
}
}
if member, ok := msg.Headers[FieldMember]; ok {
if !isValidMember(member.value.(string)) {
return InvalidMessageError("invalid member name")
}
}
if errname, ok := msg.Headers[FieldErrorName]; ok {
if !isValidInterface(errname.value.(string)) {
return InvalidMessageError("invalid error name")
}
}
if len(msg.Body) != 0 {
if _, ok := msg.Headers[FieldSignature]; !ok {
return InvalidMessageError("missing signature")
}
}
return nil
}
// Serial returns the message's serial number. The returned value is only valid
// for messages received by eavesdropping.
func (msg *Message) Serial() uint32 {
return msg.serial
}
// String returns a string representation of a message similar to the format of
// dbus-monitor.
func (msg *Message) String() string {
if err := msg.IsValid(); err != nil {
return "<invalid>"
}
s := msg.Type.String()
if v, ok := msg.Headers[FieldSender]; ok {
s += " from " + v.value.(string)
}
if v, ok := msg.Headers[FieldDestination]; ok {
s += " to " + v.value.(string)
}
s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
if v, ok := msg.Headers[FieldReplySerial]; ok {
s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldUnixFDs]; ok {
s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldPath]; ok {
s += " path " + string(v.value.(ObjectPath))
}
if v, ok := msg.Headers[FieldInterface]; ok {
s += " interface " + v.value.(string)
}
if v, ok := msg.Headers[FieldErrorName]; ok {
s += " error " + v.value.(string)
}
if v, ok := msg.Headers[FieldMember]; ok {
s += " member " + v.value.(string)
}
if len(msg.Body) != 0 {
s += "\n"
}
for i, v := range msg.Body {
s += " " + MakeVariant(v).String()
if i != len(msg.Body)-1 {
s += "\n"
}
}
return s
}

View File

@ -0,0 +1,264 @@
// Package prop provides the Properties struct which can be used to implement
// org.freedesktop.DBus.Properties.
package prop
import (
"github.com/godbus/dbus"
"github.com/godbus/dbus/introspect"
"sync"
)
// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
// emitted for a property. If it is EmitTrue, the signal is emitted. If it is
// EmitInvalidates, the signal is also emitted, but the new value of the property
// is not disclosed.
type EmitType byte
const (
EmitFalse EmitType = iota
EmitTrue
EmitInvalidates
)
// ErrIfaceNotFound is the error returned to peers who try to access properties
// on interfaces that aren't found.
var ErrIfaceNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil}
// ErrPropNotFound is the error returned to peers trying to access properties
// that aren't found.
var ErrPropNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil}
// ErrReadOnly is the error returned to peers trying to set a read-only
// property.
var ErrReadOnly = &dbus.Error{"org.freedesktop.DBus.Properties.Error.ReadOnly", nil}
// ErrInvalidArg is returned to peers if the type of the property that is being
// changed and the argument don't match.
var ErrInvalidArg = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InvalidArg", nil}
// The introspection data for the org.freedesktop.DBus.Properties interface.
var IntrospectData = introspect.Interface{
Name: "org.freedesktop.DBus.Properties",
Methods: []introspect.Method{
{
Name: "Get",
Args: []introspect.Arg{
{"interface", "s", "in"},
{"property", "s", "in"},
{"value", "v", "out"},
},
},
{
Name: "GetAll",
Args: []introspect.Arg{
{"interface", "s", "in"},
{"props", "a{sv}", "out"},
},
},
{
Name: "Set",
Args: []introspect.Arg{
{"interface", "s", "in"},
{"property", "s", "in"},
{"value", "v", "in"},
},
},
},
Signals: []introspect.Signal{
{
Name: "PropertiesChanged",
Args: []introspect.Arg{
{"interface", "s", "out"},
{"changed_properties", "a{sv}", "out"},
{"invalidates_properties", "as", "out"},
},
},
},
}
// The introspection data for the org.freedesktop.DBus.Properties interface, as
// a string.
const IntrospectDataString = `
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Get">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="out" type="v"/>
</method>
<method name="GetAll">
<arg name="interface" direction="in" type="s"/>
<arg name="props" direction="out" type="a{sv}"/>
</method>
<method name="Set">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="in" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidates_properties" type="as"/>
</signal>
</interface>
`
// Prop represents a single property. It is used for creating a Properties
// value.
type Prop struct {
// Initial value. Must be a DBus-representable type.
Value interface{}
// If true, the value can be modified by calls to Set.
Writable bool
// Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
// emitted if this property changes.
Emit EmitType
// If not nil, anytime this property is changed by Set, this function is
// called with an appropiate Change as its argument. If the returned error
// is not nil, it is sent back to the caller of Set and the property is not
// changed.
Callback func(*Change) *dbus.Error
}
// Change represents a change of a property by a call to Set.
type Change struct {
Props *Properties
Iface string
Name string
Value interface{}
}
// Properties is a set of values that can be made available to the message bus
// using the org.freedesktop.DBus.Properties interface. It is safe for
// concurrent use by multiple goroutines.
type Properties struct {
m map[string]map[string]*Prop
mut sync.RWMutex
conn *dbus.Conn
path dbus.ObjectPath
}
// New returns a new Properties structure that manages the given properties.
// The key for the first-level map of props is the name of the interface; the
// second-level key is the name of the property. The returned structure will be
// exported as org.freedesktop.DBus.Properties on path.
func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties {
p := &Properties{m: props, conn: conn, path: path}
conn.Export(p, path, "org.freedesktop.DBus.Properties")
return p
}
// Get implements org.freedesktop.DBus.Properties.Get.
func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
p.mut.RLock()
defer p.mut.RUnlock()
m, ok := p.m[iface]
if !ok {
return dbus.Variant{}, ErrIfaceNotFound
}
prop, ok := m[property]
if !ok {
return dbus.Variant{}, ErrPropNotFound
}
return dbus.MakeVariant(prop.Value), nil
}
// GetAll implements org.freedesktop.DBus.Properties.GetAll.
func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
p.mut.RLock()
defer p.mut.RUnlock()
m, ok := p.m[iface]
if !ok {
return nil, ErrIfaceNotFound
}
rm := make(map[string]dbus.Variant, len(m))
for k, v := range m {
rm[k] = dbus.MakeVariant(v.Value)
}
return rm, nil
}
// GetMust returns the value of the given property and panics if either the
// interface or the property name are invalid.
func (p *Properties) GetMust(iface, property string) interface{} {
p.mut.RLock()
defer p.mut.RUnlock()
return p.m[iface][property].Value
}
// Introspection returns the introspection data that represents the properties
// of iface.
func (p *Properties) Introspection(iface string) []introspect.Property {
p.mut.RLock()
defer p.mut.RUnlock()
m := p.m[iface]
s := make([]introspect.Property, 0, len(m))
for k, v := range m {
p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()}
if v.Writable {
p.Access = "readwrite"
} else {
p.Access = "read"
}
s = append(s, p)
}
return s
}
// set sets the given property and emits PropertyChanged if appropiate. p.mut
// must already be locked.
func (p *Properties) set(iface, property string, v interface{}) {
prop := p.m[iface][property]
prop.Value = v
switch prop.Emit {
case EmitFalse:
// do nothing
case EmitInvalidates:
p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
iface, map[string]dbus.Variant{}, []string{property})
case EmitTrue:
p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)},
[]string{})
default:
panic("invalid value for EmitType")
}
}
// Set implements org.freedesktop.Properties.Set.
func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
p.mut.Lock()
defer p.mut.Unlock()
m, ok := p.m[iface]
if !ok {
return ErrIfaceNotFound
}
prop, ok := m[property]
if !ok {
return ErrPropNotFound
}
if !prop.Writable {
return ErrReadOnly
}
if newv.Signature() != dbus.SignatureOf(prop.Value) {
return ErrInvalidArg
}
if prop.Callback != nil {
err := prop.Callback(&Change{p, iface, property, newv.Value()})
if err != nil {
return err
}
}
p.set(iface, property, newv.Value())
return nil
}
// SetMust sets the value of the given property and panics if the interface or
// the property name are invalid.
func (p *Properties) SetMust(iface, property string, v interface{}) {
p.mut.Lock()
p.set(iface, property, v)
p.mut.Unlock()
}

View File

@ -0,0 +1,369 @@
package dbus
import (
"bytes"
"encoding/binary"
"io/ioutil"
"math"
"reflect"
"testing"
)
var protoTests = []struct {
vs []interface{}
bigEndian []byte
littleEndian []byte
}{
{
[]interface{}{int32(0)},
[]byte{0, 0, 0, 0},
[]byte{0, 0, 0, 0},
},
{
[]interface{}{true, false},
[]byte{0, 0, 0, 1, 0, 0, 0, 0},
[]byte{1, 0, 0, 0, 0, 0, 0, 0},
},
{
[]interface{}{byte(0), uint16(12), int16(32), uint32(43)},
[]byte{0, 0, 0, 12, 0, 32, 0, 0, 0, 0, 0, 43},
[]byte{0, 0, 12, 0, 32, 0, 0, 0, 43, 0, 0, 0},
},
{
[]interface{}{int64(-1), uint64(1<<64 - 1)},
bytes.Repeat([]byte{255}, 16),
bytes.Repeat([]byte{255}, 16),
},
{
[]interface{}{math.Inf(+1)},
[]byte{0x7f, 0xf0, 0, 0, 0, 0, 0, 0},
[]byte{0, 0, 0, 0, 0, 0, 0xf0, 0x7f},
},
{
[]interface{}{"foo"},
[]byte{0, 0, 0, 3, 'f', 'o', 'o', 0},
[]byte{3, 0, 0, 0, 'f', 'o', 'o', 0},
},
{
[]interface{}{Signature{"ai"}},
[]byte{2, 'a', 'i', 0},
[]byte{2, 'a', 'i', 0},
},
{
[]interface{}{[]int16{42, 256}},
[]byte{0, 0, 0, 4, 0, 42, 1, 0},
[]byte{4, 0, 0, 0, 42, 0, 0, 1},
},
{
[]interface{}{MakeVariant("foo")},
[]byte{1, 's', 0, 0, 0, 0, 0, 3, 'f', 'o', 'o', 0},
[]byte{1, 's', 0, 0, 3, 0, 0, 0, 'f', 'o', 'o', 0},
},
{
[]interface{}{MakeVariant(MakeVariant(Signature{"v"}))},
[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
},
{
[]interface{}{map[int32]bool{42: true}},
[]byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1},
[]byte{8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0},
},
{
[]interface{}{map[string]Variant{}, byte(42)},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
},
{
[]interface{}{[]uint64{}, byte(42)},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
},
}
func TestProto(t *testing.T) {
for i, v := range protoTests {
buf := new(bytes.Buffer)
bigEnc := newEncoder(buf, binary.BigEndian)
bigEnc.Encode(v.vs...)
marshalled := buf.Bytes()
if bytes.Compare(marshalled, v.bigEndian) != 0 {
t.Errorf("test %d (marshal be): got '%v', but expected '%v'\n", i+1, marshalled,
v.bigEndian)
}
buf.Reset()
litEnc := newEncoder(buf, binary.LittleEndian)
litEnc.Encode(v.vs...)
marshalled = buf.Bytes()
if bytes.Compare(marshalled, v.littleEndian) != 0 {
t.Errorf("test %d (marshal le): got '%v', but expected '%v'\n", i+1, marshalled,
v.littleEndian)
}
unmarshalled := reflect.MakeSlice(reflect.TypeOf(v.vs),
0, 0)
for i := range v.vs {
unmarshalled = reflect.Append(unmarshalled,
reflect.New(reflect.TypeOf(v.vs[i])))
}
bigDec := newDecoder(bytes.NewReader(v.bigEndian), binary.BigEndian)
vs, err := bigDec.Decode(SignatureOf(v.vs...))
if err != nil {
t.Errorf("test %d (unmarshal be): %s\n", i+1, err)
continue
}
if !reflect.DeepEqual(vs, v.vs) {
t.Errorf("test %d (unmarshal be): got %#v, but expected %#v\n", i+1, vs, v.vs)
}
litDec := newDecoder(bytes.NewReader(v.littleEndian), binary.LittleEndian)
vs, err = litDec.Decode(SignatureOf(v.vs...))
if err != nil {
t.Errorf("test %d (unmarshal le): %s\n", i+1, err)
continue
}
if !reflect.DeepEqual(vs, v.vs) {
t.Errorf("test %d (unmarshal le): got %#v, but expected %#v\n", i+1, vs, v.vs)
}
}
}
func TestProtoMap(t *testing.T) {
m := map[string]uint8{
"foo": 23,
"bar": 2,
}
var n map[string]uint8
buf := new(bytes.Buffer)
enc := newEncoder(buf, binary.LittleEndian)
enc.Encode(m)
dec := newDecoder(buf, binary.LittleEndian)
vs, err := dec.Decode(Signature{"a{sy}"})
if err != nil {
t.Fatal(err)
}
if err = Store(vs, &n); err != nil {
t.Fatal(err)
}
if len(n) != 2 || n["foo"] != 23 || n["bar"] != 2 {
t.Error("got", n)
}
}
func TestProtoVariantStruct(t *testing.T) {
var variant Variant
v := MakeVariant(struct {
A int32
B int16
}{1, 2})
buf := new(bytes.Buffer)
enc := newEncoder(buf, binary.LittleEndian)
enc.Encode(v)
dec := newDecoder(buf, binary.LittleEndian)
vs, err := dec.Decode(Signature{"v"})
if err != nil {
t.Fatal(err)
}
if err = Store(vs, &variant); err != nil {
t.Fatal(err)
}
sl := variant.Value().([]interface{})
v1, v2 := sl[0].(int32), sl[1].(int16)
if v1 != int32(1) {
t.Error("got", v1, "as first int")
}
if v2 != int16(2) {
t.Error("got", v2, "as second int")
}
}
func TestProtoStructTag(t *testing.T) {
type Bar struct {
A int32
B chan interface{} `dbus:"-"`
C int32
}
var bar1, bar2 Bar
bar1.A = 234
bar2.C = 345
buf := new(bytes.Buffer)
enc := newEncoder(buf, binary.LittleEndian)
enc.Encode(bar1)
dec := newDecoder(buf, binary.LittleEndian)
vs, err := dec.Decode(Signature{"(ii)"})
if err != nil {
t.Fatal(err)
}
if err = Store(vs, &bar2); err != nil {
t.Fatal(err)
}
if bar1 != bar2 {
t.Error("struct tag test: got", bar2)
}
}
func TestProtoStoreStruct(t *testing.T) {
var foo struct {
A int32
B string
c chan interface{}
D interface{} `dbus:"-"`
}
src := []interface{}{[]interface{}{int32(42), "foo"}}
err := Store(src, &foo)
if err != nil {
t.Fatal(err)
}
}
func TestProtoStoreNestedStruct(t *testing.T) {
var foo struct {
A int32
B struct {
C string
D float64
}
}
src := []interface{}{
[]interface{}{
int32(42),
[]interface{}{
"foo",
3.14,
},
},
}
err := Store(src, &foo)
if err != nil {
t.Fatal(err)
}
}
func TestMessage(t *testing.T) {
buf := new(bytes.Buffer)
message := new(Message)
message.Type = TypeMethodCall
message.serial = 32
message.Headers = map[HeaderField]Variant{
FieldPath: MakeVariant(ObjectPath("/org/foo/bar")),
FieldMember: MakeVariant("baz"),
}
message.Body = make([]interface{}, 0)
err := message.EncodeTo(buf, binary.LittleEndian)
if err != nil {
t.Error(err)
}
_, err = DecodeMessage(buf)
if err != nil {
t.Error(err)
}
}
func TestProtoStructInterfaces(t *testing.T) {
b := []byte{42}
vs, err := newDecoder(bytes.NewReader(b), binary.LittleEndian).Decode(Signature{"(y)"})
if err != nil {
t.Fatal(err)
}
if vs[0].([]interface{})[0].(byte) != 42 {
t.Errorf("wrongs results (got %v)", vs)
}
}
// ordinary org.freedesktop.DBus.Hello call
var smallMessage = &Message{
Type: TypeMethodCall,
serial: 1,
Headers: map[HeaderField]Variant{
FieldDestination: MakeVariant("org.freedesktop.DBus"),
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")),
FieldInterface: MakeVariant("org.freedesktop.DBus"),
FieldMember: MakeVariant("Hello"),
},
}
// org.freedesktop.Notifications.Notify
var bigMessage = &Message{
Type: TypeMethodCall,
serial: 2,
Headers: map[HeaderField]Variant{
FieldDestination: MakeVariant("org.freedesktop.Notifications"),
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/Notifications")),
FieldInterface: MakeVariant("org.freedesktop.Notifications"),
FieldMember: MakeVariant("Notify"),
FieldSignature: MakeVariant(Signature{"susssasa{sv}i"}),
},
Body: []interface{}{
"app_name",
uint32(0),
"dialog-information",
"Notification",
"This is the body of a notification",
[]string{"ok", "Ok"},
map[string]Variant{
"sound-name": MakeVariant("dialog-information"),
},
int32(-1),
},
}
func BenchmarkDecodeMessageSmall(b *testing.B) {
var err error
var rd *bytes.Reader
b.StopTimer()
buf := new(bytes.Buffer)
err = smallMessage.EncodeTo(buf, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
decoded := buf.Bytes()
b.StartTimer()
for i := 0; i < b.N; i++ {
rd = bytes.NewReader(decoded)
_, err = DecodeMessage(rd)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecodeMessageBig(b *testing.B) {
var err error
var rd *bytes.Reader
b.StopTimer()
buf := new(bytes.Buffer)
err = bigMessage.EncodeTo(buf, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
decoded := buf.Bytes()
b.StartTimer()
for i := 0; i < b.N; i++ {
rd = bytes.NewReader(decoded)
_, err = DecodeMessage(rd)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkEncodeMessageSmall(b *testing.B) {
var err error
for i := 0; i < b.N; i++ {
err = smallMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkEncodeMessageBig(b *testing.B) {
var err error
for i := 0; i < b.N; i++ {
err = bigMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
}
}

257
Godeps/_workspace/src/github.com/godbus/dbus/sig.go generated vendored Normal file
View File

@ -0,0 +1,257 @@
package dbus
import (
"fmt"
"reflect"
"strings"
)
var sigToType = map[byte]reflect.Type{
'y': byteType,
'b': boolType,
'n': int16Type,
'q': uint16Type,
'i': int32Type,
'u': uint32Type,
'x': int64Type,
't': uint64Type,
'd': float64Type,
's': stringType,
'g': signatureType,
'o': objectPathType,
'v': variantType,
'h': unixFDIndexType,
}
// Signature represents a correct type signature as specified by the D-Bus
// specification. The zero value represents the empty signature, "".
type Signature struct {
str string
}
// SignatureOf returns the concatenation of all the signatures of the given
// values. It panics if one of them is not representable in D-Bus.
func SignatureOf(vs ...interface{}) Signature {
var s string
for _, v := range vs {
s += getSignature(reflect.TypeOf(v))
}
return Signature{s}
}
// SignatureOfType returns the signature of the given type. It panics if the
// type is not representable in D-Bus.
func SignatureOfType(t reflect.Type) Signature {
return Signature{getSignature(t)}
}
// getSignature returns the signature of the given type and panics on unknown types.
func getSignature(t reflect.Type) string {
// handle simple types first
switch t.Kind() {
case reflect.Uint8:
return "y"
case reflect.Bool:
return "b"
case reflect.Int16:
return "n"
case reflect.Uint16:
return "q"
case reflect.Int32:
if t == unixFDType {
return "h"
}
return "i"
case reflect.Uint32:
if t == unixFDIndexType {
return "h"
}
return "u"
case reflect.Int64:
return "x"
case reflect.Uint64:
return "t"
case reflect.Float64:
return "d"
case reflect.Ptr:
return getSignature(t.Elem())
case reflect.String:
if t == objectPathType {
return "o"
}
return "s"
case reflect.Struct:
if t == variantType {
return "v"
} else if t == signatureType {
return "g"
}
var s string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
s += getSignature(t.Field(i).Type)
}
}
return "(" + s + ")"
case reflect.Array, reflect.Slice:
return "a" + getSignature(t.Elem())
case reflect.Map:
if !isKeyType(t.Key()) {
panic(InvalidTypeError{t})
}
return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
}
panic(InvalidTypeError{t})
}
// ParseSignature returns the signature represented by this string, or a
// SignatureError if the string is not a valid signature.
func ParseSignature(s string) (sig Signature, err error) {
if len(s) == 0 {
return
}
if len(s) > 255 {
return Signature{""}, SignatureError{s, "too long"}
}
sig.str = s
for err == nil && len(s) != 0 {
err, s = validSingle(s, 0)
}
if err != nil {
sig = Signature{""}
}
return
}
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
// is not valid.
func ParseSignatureMust(s string) Signature {
sig, err := ParseSignature(s)
if err != nil {
panic(err)
}
return sig
}
// Empty retruns whether the signature is the empty signature.
func (s Signature) Empty() bool {
return s.str == ""
}
// Single returns whether the signature represents a single, complete type.
func (s Signature) Single() bool {
err, r := validSingle(s.str, 0)
return err != nil && r == ""
}
// String returns the signature's string representation.
func (s Signature) String() string {
return s.str
}
// A SignatureError indicates that a signature passed to a function or received
// on a connection is not a valid signature.
type SignatureError struct {
Sig string
Reason string
}
func (e SignatureError) Error() string {
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
}
// Try to read a single type from this string. If it was successfull, err is nil
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
// SignatureError and rem is "". depth is the current recursion depth which may
// not be greater than 64 and should be given as 0 on the first call.
func validSingle(s string, depth int) (err error, rem string) {
if s == "" {
return SignatureError{Sig: s, Reason: "empty signature"}, ""
}
if depth > 64 {
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
}
switch s[0] {
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
return nil, s[1:]
case 'a':
if len(s) > 1 && s[1] == '{' {
i := findMatching(s[1:], '{', '}')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
}
i++
rem = s[i+1:]
s = s[2:i]
if err, _ = validSingle(s[:1], depth+1); err != nil {
return err, ""
}
err, nr := validSingle(s[1:], depth+1)
if err != nil {
return err, ""
}
if nr != "" {
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
}
return nil, rem
}
return validSingle(s[1:], depth+1)
case '(':
i := findMatching(s, '(', ')')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
}
rem = s[i+1:]
s = s[1:i]
for err == nil && s != "" {
err, s = validSingle(s, depth+1)
}
if err != nil {
rem = ""
}
return
}
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
}
func findMatching(s string, left, right rune) int {
n := 0
for i, v := range s {
if v == left {
n++
} else if v == right {
n--
}
if n == 0 {
return i
}
}
return -1
}
// typeFor returns the type of the given signature. It ignores any left over
// characters and panics if s doesn't start with a valid type signature.
func typeFor(s string) (t reflect.Type) {
err, _ := validSingle(s, 0)
if err != nil {
panic(err)
}
if t, ok := sigToType[s[0]]; ok {
return t
}
switch s[0] {
case 'a':
if s[1] == '{' {
i := strings.LastIndex(s, "}")
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
} else {
t = reflect.SliceOf(typeFor(s[1:]))
}
case '(':
t = interfacesType
}
return
}

View File

@ -0,0 +1,70 @@
package dbus
import (
"testing"
)
var sigTests = []struct {
vs []interface{}
sig Signature
}{
{
[]interface{}{new(int32)},
Signature{"i"},
},
{
[]interface{}{new(string)},
Signature{"s"},
},
{
[]interface{}{new(Signature)},
Signature{"g"},
},
{
[]interface{}{new([]int16)},
Signature{"an"},
},
{
[]interface{}{new(int16), new(uint32)},
Signature{"nu"},
},
{
[]interface{}{new(map[byte]Variant)},
Signature{"a{yv}"},
},
{
[]interface{}{new(Variant), new([]map[int32]string)},
Signature{"vaa{is}"},
},
}
func TestSig(t *testing.T) {
for i, v := range sigTests {
sig := SignatureOf(v.vs...)
if sig != v.sig {
t.Errorf("test %d: got %q, expected %q", i+1, sig.str, v.sig.str)
}
}
}
var getSigTest = []interface{}{
[]struct {
b byte
i int32
t uint64
s string
}{},
map[string]Variant{},
}
func BenchmarkGetSignatureSimple(b *testing.B) {
for i := 0; i < b.N; i++ {
SignatureOf("", int32(0))
}
}
func BenchmarkGetSignatureLong(b *testing.B) {
for i := 0; i < b.N; i++ {
SignatureOf(getSigTest...)
}
}

View File

@ -0,0 +1,6 @@
package dbus
func (t *unixTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}

View File

@ -0,0 +1,35 @@
package dbus
import (
"encoding/binary"
"errors"
"io"
)
type genericTransport struct {
io.ReadWriteCloser
}
func (t genericTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}
func (t genericTransport) SupportsUnixFDs() bool {
return false
}
func (t genericTransport) EnableUnixFDs() {}
func (t genericTransport) ReadMessage() (*Message, error) {
return DecodeMessage(t)
}
func (t genericTransport) SendMessage(msg *Message) error {
for _, v := range msg.Body {
if _, ok := v.(UnixFD); ok {
return errors.New("dbus: unix fd passing not enabled")
}
}
return msg.EncodeTo(t, binary.LittleEndian)
}

View File

@ -0,0 +1,196 @@
//+build !windows
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"syscall"
)
type oobReader struct {
conn *net.UnixConn
oob []byte
buf [4096]byte
}
func (o *oobReader) Read(b []byte) (n int, err error) {
n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
if err != nil {
return n, err
}
if flags&syscall.MSG_CTRUNC != 0 {
return n, errors.New("dbus: control data truncated (too many fds received)")
}
o.oob = append(o.oob, o.buf[:oobn]...)
return n, nil
}
type unixTransport struct {
*net.UnixConn
hasUnixFDs bool
}
func newUnixTransport(keys string) (transport, error) {
var err error
t := new(unixTransport)
abstract := getKey(keys, "abstract")
path := getKey(keys, "path")
switch {
case abstract == "" && path == "":
return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
case abstract != "" && path == "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
case abstract == "" && path != "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
default:
return nil, errors.New("dbus: invalid address (both path and abstract set)")
}
}
func init() {
transports["unix"] = newUnixTransport
}
func (t *unixTransport) EnableUnixFDs() {
t.hasUnixFDs = true
}
func (t *unixTransport) ReadMessage() (*Message, error) {
var (
blen, hlen uint32
csheader [16]byte
headers []header
order binary.ByteOrder
unixfds uint32
)
// To be sure that all bytes of out-of-band data are read, we use a special
// reader that uses ReadUnix on the underlying connection instead of Read
// and gathers the out-of-band data in a buffer.
rd := &oobReader{conn: t.UnixConn}
// read the first 16 bytes (the part of the header that has a constant size),
// from which we can figure out the length of the rest of the message
if _, err := io.ReadFull(rd, csheader[:]); err != nil {
return nil, err
}
switch csheader[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
// csheader[4:8] -> length of message body, csheader[12:16] -> length of
// header fields (without alignment)
binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen)
binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen)
if hlen%8 != 0 {
hlen += 8 - (hlen % 8)
}
// decode headers and look for unix fds
headerdata := make([]byte, hlen+4)
copy(headerdata, csheader[12:])
if _, err := io.ReadFull(t, headerdata[4:]); err != nil {
return nil, err
}
dec := newDecoder(bytes.NewBuffer(headerdata), order)
dec.pos = 12
vs, err := dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
Store(vs, &headers)
for _, v := range headers {
if v.Field == byte(FieldUnixFDs) {
unixfds, _ = v.Variant.value.(uint32)
}
}
all := make([]byte, 16+hlen+blen)
copy(all, csheader[:])
copy(all[16:], headerdata[4:])
if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil {
return nil, err
}
if unixfds != 0 {
if !t.hasUnixFDs {
return nil, errors.New("dbus: got unix fds on unsupported transport")
}
// read the fds from the OOB data
scms, err := syscall.ParseSocketControlMessage(rd.oob)
if err != nil {
return nil, err
}
if len(scms) != 1 {
return nil, errors.New("dbus: received more than one socket control message")
}
fds, err := syscall.ParseUnixRights(&scms[0])
if err != nil {
return nil, err
}
msg, err := DecodeMessage(bytes.NewBuffer(all))
if err != nil {
return nil, err
}
// substitute the values in the message body (which are indices for the
// array receiver via OOB) with the actual values
for i, v := range msg.Body {
if j, ok := v.(UnixFDIndex); ok {
if uint32(j) >= unixfds {
return nil, InvalidMessageError("invalid index for unix fd")
}
msg.Body[i] = UnixFD(fds[j])
}
}
return msg, nil
}
return DecodeMessage(bytes.NewBuffer(all))
}
func (t *unixTransport) SendMessage(msg *Message) error {
fds := make([]int, 0)
for i, v := range msg.Body {
if fd, ok := v.(UnixFD); ok {
msg.Body[i] = UnixFDIndex(len(fds))
fds = append(fds, int(fd))
}
}
if len(fds) != 0 {
if !t.hasUnixFDs {
return errors.New("dbus: unix fd passing not enabled")
}
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds)))
oob := syscall.UnixRights(fds...)
buf := new(bytes.Buffer)
msg.EncodeTo(buf, binary.LittleEndian)
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
if err != nil {
return err
}
if n != buf.Len() || oobn != len(oob) {
return io.ErrShortWrite
}
} else {
if err := msg.EncodeTo(t, binary.LittleEndian); err != nil {
return nil
}
}
return nil
}
func (t *unixTransport) SupportsUnixFDs() bool {
return true
}

View File

@ -0,0 +1,49 @@
package dbus
import (
"os"
"testing"
)
const testString = `This is a test!
This text should be read from the file that is created by this test.`
type unixFDTest struct{}
func (t unixFDTest) Test(fd UnixFD) (string, *Error) {
var b [4096]byte
file := os.NewFile(uintptr(fd), "testfile")
defer file.Close()
n, err := file.Read(b[:])
if err != nil {
return "", &Error{"com.github.guelfey.test.Error", nil}
}
return string(b[:n]), nil
}
func TestUnixFDs(t *testing.T) {
conn, err := SessionBus()
if err != nil {
t.Fatal(err)
}
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
defer w.Close()
if _, err := w.Write([]byte(testString)); err != nil {
t.Fatal(err)
}
name := conn.Names()[0]
test := unixFDTest{}
conn.Export(test, "/com/github/guelfey/test", "com.github.guelfey.test")
var s string
obj := conn.Object(name, "/com/github/guelfey/test")
err = obj.Call("com.github.guelfey.test.Test", 0, UnixFD(r.Fd())).Store(&s)
if err != nil {
t.Fatal(err)
}
if s != testString {
t.Fatal("got", s, "wanted", testString)
}
}

View File

@ -0,0 +1,95 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for DragonFly BSD
package dbus
/*
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// http://golang.org/src/pkg/syscall/types_dragonfly.go
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
//salign := sizeofPtr
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
// still require 32-bit aligned access to network subsystem.
//if darwin64Bit || dragonfly64Bit {
// salign = 4
//}
salign := 4
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@ -0,0 +1,25 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
package dbus
import (
"io"
"os"
"syscall"
)
func (t *unixTransport) SendNullByte() error {
ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := syscall.UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

129
Godeps/_workspace/src/github.com/godbus/dbus/variant.go generated vendored Normal file
View File

@ -0,0 +1,129 @@
package dbus
import (
"bytes"
"fmt"
"reflect"
"strconv"
)
// Variant represents the D-Bus variant type.
type Variant struct {
sig Signature
value interface{}
}
// MakeVariant converts the given value to a Variant. It panics if v cannot be
// represented as a D-Bus type.
func MakeVariant(v interface{}) Variant {
return Variant{SignatureOf(v), v}
}
// ParseVariant parses the given string as a variant as described at
// https://developer.gnome.org/glib/unstable/gvariant-text.html. If sig is not
// empty, it is taken to be the expected signature for the variant.
func ParseVariant(s string, sig Signature) (Variant, error) {
tokens := varLex(s)
p := &varParser{tokens: tokens}
n, err := varMakeNode(p)
if err != nil {
return Variant{}, err
}
if sig.str == "" {
sig, err = varInfer(n)
if err != nil {
return Variant{}, err
}
}
v, err := n.Value(sig)
if err != nil {
return Variant{}, err
}
return MakeVariant(v), nil
}
// format returns a formatted version of v and whether this string can be parsed
// unambigously.
func (v Variant) format() (string, bool) {
switch v.sig.str[0] {
case 'b', 'i':
return fmt.Sprint(v.value), true
case 'n', 'q', 'u', 'x', 't', 'd', 'h':
return fmt.Sprint(v.value), false
case 's':
return strconv.Quote(v.value.(string)), true
case 'o':
return strconv.Quote(string(v.value.(ObjectPath))), false
case 'g':
return strconv.Quote(v.value.(Signature).str), false
case 'v':
s, unamb := v.value.(Variant).format()
if !unamb {
return "<@" + v.value.(Variant).sig.str + " " + s + ">", true
}
return "<" + s + ">", true
case 'y':
return fmt.Sprintf("%#x", v.value.(byte)), false
}
rv := reflect.ValueOf(v.value)
switch rv.Kind() {
case reflect.Slice:
if rv.Len() == 0 {
return "[]", false
}
unamb := true
buf := bytes.NewBuffer([]byte("["))
for i := 0; i < rv.Len(); i++ {
// TODO: slooow
s, b := MakeVariant(rv.Index(i).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
if i != rv.Len()-1 {
buf.WriteString(", ")
}
}
buf.WriteByte(']')
return buf.String(), unamb
case reflect.Map:
if rv.Len() == 0 {
return "{}", false
}
unamb := true
buf := bytes.NewBuffer([]byte("{"))
for i, k := range rv.MapKeys() {
s, b := MakeVariant(k.Interface()).format()
unamb = unamb && b
buf.WriteString(s)
buf.WriteString(": ")
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
if i != rv.Len()-1 {
buf.WriteString(", ")
}
}
buf.WriteByte('}')
return buf.String(), unamb
}
return `"INVALID"`, true
}
// Signature returns the D-Bus signature of the underlying value of v.
func (v Variant) Signature() Signature {
return v.sig
}
// String returns the string representation of the underlying value of v as
// described at https://developer.gnome.org/glib/unstable/gvariant-text.html.
func (v Variant) String() string {
s, unamb := v.format()
if !unamb {
return "@" + v.sig.str + " " + s
}
return s
}
// Value returns the underlying value of v.
func (v Variant) Value() interface{} {
return v.value
}

View File

@ -0,0 +1,284 @@
package dbus
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// Heavily inspired by the lexer from text/template.
type varToken struct {
typ varTokenType
val string
}
type varTokenType byte
const (
tokEOF varTokenType = iota
tokError
tokNumber
tokString
tokBool
tokArrayStart
tokArrayEnd
tokDictStart
tokDictEnd
tokVariantStart
tokVariantEnd
tokComma
tokColon
tokType
tokByteString
)
type varLexer struct {
input string
start int
pos int
width int
tokens []varToken
}
type lexState func(*varLexer) lexState
func varLex(s string) []varToken {
l := &varLexer{input: s}
l.run()
return l.tokens
}
func (l *varLexer) accept(valid string) bool {
if strings.IndexRune(valid, l.next()) >= 0 {
return true
}
l.backup()
return false
}
func (l *varLexer) backup() {
l.pos -= l.width
}
func (l *varLexer) emit(t varTokenType) {
l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
l.start = l.pos
}
func (l *varLexer) errorf(format string, v ...interface{}) lexState {
l.tokens = append(l.tokens, varToken{
tokError,
fmt.Sprintf(format, v...),
})
return nil
}
func (l *varLexer) ignore() {
l.start = l.pos
}
func (l *varLexer) next() rune {
var r rune
if l.pos >= len(l.input) {
l.width = 0
return -1
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += l.width
return r
}
func (l *varLexer) run() {
for state := varLexNormal; state != nil; {
state = state(l)
}
}
func (l *varLexer) peek() rune {
r := l.next()
l.backup()
return r
}
func varLexNormal(l *varLexer) lexState {
for {
r := l.next()
switch {
case r == -1:
l.emit(tokEOF)
return nil
case r == '[':
l.emit(tokArrayStart)
case r == ']':
l.emit(tokArrayEnd)
case r == '{':
l.emit(tokDictStart)
case r == '}':
l.emit(tokDictEnd)
case r == '<':
l.emit(tokVariantStart)
case r == '>':
l.emit(tokVariantEnd)
case r == ':':
l.emit(tokColon)
case r == ',':
l.emit(tokComma)
case r == '\'' || r == '"':
l.backup()
return varLexString
case r == '@':
l.backup()
return varLexType
case unicode.IsSpace(r):
l.ignore()
case unicode.IsNumber(r) || r == '+' || r == '-':
l.backup()
return varLexNumber
case r == 'b':
pos := l.start
if n := l.peek(); n == '"' || n == '\'' {
return varLexByteString
}
// not a byte string; try to parse it as a type or bool below
l.pos = pos + 1
l.width = 1
fallthrough
default:
// either a bool or a type. Try bools first.
l.backup()
if l.pos+4 <= len(l.input) {
if l.input[l.pos:l.pos+4] == "true" {
l.pos += 4
l.emit(tokBool)
continue
}
}
if l.pos+5 <= len(l.input) {
if l.input[l.pos:l.pos+5] == "false" {
l.pos += 5
l.emit(tokBool)
continue
}
}
// must be a type.
return varLexType
}
}
}
var varTypeMap = map[string]string{
"boolean": "b",
"byte": "y",
"int16": "n",
"uint16": "q",
"int32": "i",
"uint32": "u",
"int64": "x",
"uint64": "t",
"double": "f",
"string": "s",
"objectpath": "o",
"signature": "g",
}
func varLexByteString(l *varLexer) lexState {
q := l.next()
Loop:
for {
switch l.next() {
case '\\':
if r := l.next(); r != -1 {
break
}
fallthrough
case -1:
return l.errorf("unterminated bytestring")
case q:
break Loop
}
}
l.emit(tokByteString)
return varLexNormal
}
func varLexNumber(l *varLexer) lexState {
l.accept("+-")
digits := "0123456789"
if l.accept("0") {
if l.accept("x") {
digits = "0123456789abcdefABCDEF"
} else {
digits = "01234567"
}
}
for strings.IndexRune(digits, l.next()) >= 0 {
}
l.backup()
if l.accept(".") {
for strings.IndexRune(digits, l.next()) >= 0 {
}
l.backup()
}
if l.accept("eE") {
l.accept("+-")
for strings.IndexRune("0123456789", l.next()) >= 0 {
}
l.backup()
}
if r := l.peek(); unicode.IsLetter(r) {
l.next()
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
}
l.emit(tokNumber)
return varLexNormal
}
func varLexString(l *varLexer) lexState {
q := l.next()
Loop:
for {
switch l.next() {
case '\\':
if r := l.next(); r != -1 {
break
}
fallthrough
case -1:
return l.errorf("unterminated string")
case q:
break Loop
}
}
l.emit(tokString)
return varLexNormal
}
func varLexType(l *varLexer) lexState {
at := l.accept("@")
for {
r := l.next()
if r == -1 {
break
}
if unicode.IsSpace(r) {
l.backup()
break
}
}
if at {
if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
return l.errorf("%s", err)
}
} else {
if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
l.emit(tokType)
return varLexNormal
}
return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
}
l.emit(tokType)
return varLexNormal
}

View File

@ -0,0 +1,817 @@
package dbus
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"unicode/utf8"
)
type varParser struct {
tokens []varToken
i int
}
func (p *varParser) backup() {
p.i--
}
func (p *varParser) next() varToken {
if p.i < len(p.tokens) {
t := p.tokens[p.i]
p.i++
return t
}
return varToken{typ: tokEOF}
}
type varNode interface {
Infer() (Signature, error)
String() string
Sigs() sigSet
Value(Signature) (interface{}, error)
}
func varMakeNode(p *varParser) (varNode, error) {
var sig Signature
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokNumber:
return varMakeNumNode(t, sig)
case tokString:
return varMakeStringNode(t, sig)
case tokBool:
if sig.str != "" && sig.str != "b" {
return nil, varTypeError{t.val, sig}
}
b, err := strconv.ParseBool(t.val)
if err != nil {
return nil, err
}
return boolNode(b), nil
case tokArrayStart:
return varMakeArrayNode(p, sig)
case tokVariantStart:
return varMakeVariantNode(p, sig)
case tokDictStart:
return varMakeDictNode(p, sig)
case tokType:
if sig.str != "" {
return nil, errors.New("unexpected type annotation")
}
if t.val[0] == '@' {
sig.str = t.val[1:]
} else {
sig.str = varTypeMap[t.val]
}
case tokByteString:
if sig.str != "" && sig.str != "ay" {
return nil, varTypeError{t.val, sig}
}
b, err := varParseByteString(t.val)
if err != nil {
return nil, err
}
return byteStringNode(b), nil
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
}
type varTypeError struct {
val string
sig Signature
}
func (e varTypeError) Error() string {
return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
}
type sigSet map[Signature]bool
func (s sigSet) Empty() bool {
return len(s) == 0
}
func (s sigSet) Intersect(s2 sigSet) sigSet {
r := make(sigSet)
for k := range s {
if s2[k] {
r[k] = true
}
}
return r
}
func (s sigSet) Single() (Signature, bool) {
if len(s) == 1 {
for k := range s {
return k, true
}
}
return Signature{}, false
}
func (s sigSet) ToArray() sigSet {
r := make(sigSet, len(s))
for k := range s {
r[Signature{"a" + k.str}] = true
}
return r
}
type numNode struct {
sig Signature
str string
val interface{}
}
var numSigSet = sigSet{
Signature{"y"}: true,
Signature{"n"}: true,
Signature{"q"}: true,
Signature{"i"}: true,
Signature{"u"}: true,
Signature{"x"}: true,
Signature{"t"}: true,
Signature{"d"}: true,
}
func (n numNode) Infer() (Signature, error) {
if strings.ContainsAny(n.str, ".e") {
return Signature{"d"}, nil
}
return Signature{"i"}, nil
}
func (n numNode) String() string {
return n.str
}
func (n numNode) Sigs() sigSet {
if n.sig.str != "" {
return sigSet{n.sig: true}
}
if strings.ContainsAny(n.str, ".e") {
return sigSet{Signature{"d"}: true}
}
return numSigSet
}
func (n numNode) Value(sig Signature) (interface{}, error) {
if n.sig.str != "" && n.sig != sig {
return nil, varTypeError{n.str, sig}
}
if n.val != nil {
return n.val, nil
}
return varNumAs(n.str, sig)
}
func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
if sig.str == "" {
return numNode{str: tok.val}, nil
}
num, err := varNumAs(tok.val, sig)
if err != nil {
return nil, err
}
return numNode{sig: sig, val: num}, nil
}
func varNumAs(s string, sig Signature) (interface{}, error) {
isUnsigned := false
size := 32
switch sig.str {
case "n":
size = 16
case "i":
case "x":
size = 64
case "y":
size = 8
isUnsigned = true
case "q":
size = 16
isUnsigned = true
case "u":
isUnsigned = true
case "t":
size = 64
isUnsigned = true
case "d":
d, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
}
return d, nil
default:
return nil, varTypeError{s, sig}
}
base := 10
if strings.HasPrefix(s, "0x") {
base = 16
s = s[2:]
}
if strings.HasPrefix(s, "0") && len(s) != 1 {
base = 8
s = s[1:]
}
if isUnsigned {
i, err := strconv.ParseUint(s, base, size)
if err != nil {
return nil, err
}
var v interface{} = i
switch sig.str {
case "y":
v = byte(i)
case "q":
v = uint16(i)
case "u":
v = uint32(i)
}
return v, nil
}
i, err := strconv.ParseInt(s, base, size)
if err != nil {
return nil, err
}
var v interface{} = i
switch sig.str {
case "n":
v = int16(i)
case "i":
v = int32(i)
}
return v, nil
}
type stringNode struct {
sig Signature
str string // parsed
val interface{} // has correct type
}
var stringSigSet = sigSet{
Signature{"s"}: true,
Signature{"g"}: true,
Signature{"o"}: true,
}
func (n stringNode) Infer() (Signature, error) {
return Signature{"s"}, nil
}
func (n stringNode) String() string {
return n.str
}
func (n stringNode) Sigs() sigSet {
if n.sig.str != "" {
return sigSet{n.sig: true}
}
return stringSigSet
}
func (n stringNode) Value(sig Signature) (interface{}, error) {
if n.sig.str != "" && n.sig != sig {
return nil, varTypeError{n.str, sig}
}
if n.val != nil {
return n.val, nil
}
switch {
case sig.str == "g":
return Signature{n.str}, nil
case sig.str == "o":
return ObjectPath(n.str), nil
case sig.str == "s":
return n.str, nil
default:
return nil, varTypeError{n.str, sig}
}
}
func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
return nil, fmt.Errorf("invalid type %q for string", sig.str)
}
s, err := varParseString(tok.val)
if err != nil {
return nil, err
}
n := stringNode{str: s}
if sig.str == "" {
return stringNode{str: s}, nil
}
n.sig = sig
switch sig.str {
case "o":
n.val = ObjectPath(s)
case "g":
n.val = Signature{s}
case "s":
n.val = s
}
return n, nil
}
func varParseString(s string) (string, error) {
// quotes are guaranteed to be there
s = s[1 : len(s)-1]
buf := new(bytes.Buffer)
for len(s) != 0 {
r, size := utf8.DecodeRuneInString(s)
if r == utf8.RuneError && size == 1 {
return "", errors.New("invalid UTF-8")
}
s = s[size:]
if r != '\\' {
buf.WriteRune(r)
continue
}
r, size = utf8.DecodeRuneInString(s)
if r == utf8.RuneError && size == 1 {
return "", errors.New("invalid UTF-8")
}
s = s[size:]
switch r {
case 'a':
buf.WriteRune(0x7)
case 'b':
buf.WriteRune(0x8)
case 'f':
buf.WriteRune(0xc)
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case '\n':
case 'u':
if len(s) < 4 {
return "", errors.New("short unicode escape")
}
r, err := strconv.ParseUint(s[:4], 16, 32)
if err != nil {
return "", err
}
buf.WriteRune(rune(r))
s = s[4:]
case 'U':
if len(s) < 8 {
return "", errors.New("short unicode escape")
}
r, err := strconv.ParseUint(s[:8], 16, 32)
if err != nil {
return "", err
}
buf.WriteRune(rune(r))
s = s[8:]
default:
buf.WriteRune(r)
}
}
return buf.String(), nil
}
var boolSigSet = sigSet{Signature{"b"}: true}
type boolNode bool
func (boolNode) Infer() (Signature, error) {
return Signature{"b"}, nil
}
func (b boolNode) String() string {
if b {
return "true"
}
return "false"
}
func (boolNode) Sigs() sigSet {
return boolSigSet
}
func (b boolNode) Value(sig Signature) (interface{}, error) {
if sig.str != "b" {
return nil, varTypeError{b.String(), sig}
}
return bool(b), nil
}
type arrayNode struct {
set sigSet
children []varNode
val interface{}
}
func (n arrayNode) Infer() (Signature, error) {
for _, v := range n.children {
csig, err := varInfer(v)
if err != nil {
continue
}
return Signature{"a" + csig.str}, nil
}
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
}
func (n arrayNode) String() string {
s := "["
for i, v := range n.children {
s += v.String()
if i != len(n.children)-1 {
s += ", "
}
}
return s + "]"
}
func (n arrayNode) Sigs() sigSet {
return n.set
}
func (n arrayNode) Value(sig Signature) (interface{}, error) {
if n.set.Empty() {
// no type information whatsoever, so this must be an empty slice
return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
}
if !n.set[sig] {
return nil, varTypeError{n.String(), sig}
}
s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
for i, v := range n.children {
rv, err := v.Value(Signature{sig.str[1:]})
if err != nil {
return nil, err
}
s.Index(i).Set(reflect.ValueOf(rv))
}
return s.Interface(), nil
}
func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
var n arrayNode
if sig.str != "" {
n.set = sigSet{sig: true}
}
if t := p.next(); t.typ == tokArrayEnd {
return n, nil
} else {
p.backup()
}
Loop:
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
cn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if cset := cn.Sigs(); !cset.Empty() {
if n.set.Empty() {
n.set = cset.ToArray()
} else {
nset := cset.ToArray().Intersect(n.set)
if nset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
}
n.set = nset
}
}
n.children = append(n.children, cn)
switch t := p.next(); t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokArrayEnd:
break Loop
case tokComma:
continue
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
return n, nil
}
type variantNode struct {
n varNode
}
var variantSet = sigSet{
Signature{"v"}: true,
}
func (variantNode) Infer() (Signature, error) {
return Signature{"v"}, nil
}
func (n variantNode) String() string {
return "<" + n.n.String() + ">"
}
func (variantNode) Sigs() sigSet {
return variantSet
}
func (n variantNode) Value(sig Signature) (interface{}, error) {
if sig.str != "v" {
return nil, varTypeError{n.String(), sig}
}
sig, err := varInfer(n.n)
if err != nil {
return nil, err
}
v, err := n.n.Value(sig)
if err != nil {
return nil, err
}
return MakeVariant(v), nil
}
func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
n, err := varMakeNode(p)
if err != nil {
return nil, err
}
if t := p.next(); t.typ != tokVariantEnd {
return nil, fmt.Errorf("unexpected %q", t.val)
}
vn := variantNode{n}
if sig.str != "" && sig.str != "v" {
return nil, varTypeError{vn.String(), sig}
}
return variantNode{n}, nil
}
type dictEntry struct {
key, val varNode
}
type dictNode struct {
kset, vset sigSet
children []dictEntry
val interface{}
}
func (n dictNode) Infer() (Signature, error) {
for _, v := range n.children {
ksig, err := varInfer(v.key)
if err != nil {
continue
}
vsig, err := varInfer(v.val)
if err != nil {
continue
}
return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
}
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
}
func (n dictNode) String() string {
s := "{"
for i, v := range n.children {
s += v.key.String() + ": " + v.val.String()
if i != len(n.children)-1 {
s += ", "
}
}
return s + "}"
}
func (n dictNode) Sigs() sigSet {
r := sigSet{}
for k := range n.kset {
for v := range n.vset {
sig := "a{" + k.str + v.str + "}"
r[Signature{sig}] = true
}
}
return r
}
func (n dictNode) Value(sig Signature) (interface{}, error) {
set := n.Sigs()
if set.Empty() {
// no type information -> empty dict
return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
}
if !set[sig] {
return nil, varTypeError{n.String(), sig}
}
m := reflect.MakeMap(typeFor(sig.str))
ksig := Signature{sig.str[2:3]}
vsig := Signature{sig.str[3 : len(sig.str)-1]}
for _, v := range n.children {
kv, err := v.key.Value(ksig)
if err != nil {
return nil, err
}
vv, err := v.val.Value(vsig)
if err != nil {
return nil, err
}
m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return m.Interface(), nil
}
func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
var n dictNode
if sig.str != "" {
if len(sig.str) < 5 {
return nil, fmt.Errorf("invalid signature %q for dict type", sig)
}
ksig := Signature{string(sig.str[2])}
vsig := Signature{sig.str[3 : len(sig.str)-1]}
n.kset = sigSet{ksig: true}
n.vset = sigSet{vsig: true}
}
if t := p.next(); t.typ == tokDictEnd {
return n, nil
} else {
p.backup()
}
Loop:
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
kn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if kset := kn.Sigs(); !kset.Empty() {
if n.kset.Empty() {
n.kset = kset
} else {
n.kset = kset.Intersect(n.kset)
if n.kset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
}
}
}
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokColon:
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
vn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if vset := vn.Sigs(); !vset.Empty() {
if n.vset.Empty() {
n.vset = vset
} else {
n.vset = n.vset.Intersect(vset)
if n.vset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
}
}
}
n.children = append(n.children, dictEntry{kn, vn})
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokDictEnd:
break Loop
case tokComma:
continue
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
return n, nil
}
type byteStringNode []byte
var byteStringSet = sigSet{
Signature{"ay"}: true,
}
func (byteStringNode) Infer() (Signature, error) {
return Signature{"ay"}, nil
}
func (b byteStringNode) String() string {
return string(b)
}
func (b byteStringNode) Sigs() sigSet {
return byteStringSet
}
func (b byteStringNode) Value(sig Signature) (interface{}, error) {
if sig.str != "ay" {
return nil, varTypeError{b.String(), sig}
}
return []byte(b), nil
}
func varParseByteString(s string) ([]byte, error) {
// quotes and b at start are guaranteed to be there
b := make([]byte, 0, 1)
s = s[2 : len(s)-1]
for len(s) != 0 {
c := s[0]
s = s[1:]
if c != '\\' {
b = append(b, c)
continue
}
c = s[0]
s = s[1:]
switch c {
case 'a':
b = append(b, 0x7)
case 'b':
b = append(b, 0x8)
case 'f':
b = append(b, 0xc)
case 'n':
b = append(b, '\n')
case 'r':
b = append(b, '\r')
case 't':
b = append(b, '\t')
case 'x':
if len(s) < 2 {
return nil, errors.New("short escape")
}
n, err := strconv.ParseUint(s[:2], 16, 8)
if err != nil {
return nil, err
}
b = append(b, byte(n))
s = s[2:]
case '0':
if len(s) < 3 {
return nil, errors.New("short escape")
}
n, err := strconv.ParseUint(s[:3], 8, 8)
if err != nil {
return nil, err
}
b = append(b, byte(n))
s = s[3:]
default:
b = append(b, c)
}
}
return append(b, 0), nil
}
func varInfer(n varNode) (Signature, error) {
if sig, ok := n.Sigs().Single(); ok {
return sig, nil
}
return n.Infer()
}

View File

@ -0,0 +1,78 @@
package dbus
import "reflect"
import "testing"
var variantFormatTests = []struct {
v interface{}
s string
}{
{int32(1), `1`},
{"foo", `"foo"`},
{ObjectPath("/org/foo"), `@o "/org/foo"`},
{Signature{"i"}, `@g "i"`},
{[]byte{}, `@ay []`},
{[]int32{1, 2}, `[1, 2]`},
{[]int64{1, 2}, `@ax [1, 2]`},
{[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`},
{[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`},
{map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`},
{map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`},
{map[string]Variant{}, `@a{sv} {}`},
}
func TestFormatVariant(t *testing.T) {
for i, v := range variantFormatTests {
if s := MakeVariant(v.v).String(); s != v.s {
t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s)
}
}
}
var variantParseTests = []struct {
s string
v interface{}
}{
{"1", int32(1)},
{"true", true},
{"false", false},
{"1.0", float64(1.0)},
{"0x10", int32(16)},
{"1e1", float64(10)},
{`"foo"`, "foo"},
{`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"},
{`"\u00e4\U0001f603"`, "\u00e4\U0001f603"},
{"[1]", []int32{1}},
{"[1, 2, 3]", []int32{1, 2, 3}},
{"@ai []", []int32{}},
{"[1, 5.0]", []float64{1, 5.0}},
{"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}},
{`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}},
{"<1>", MakeVariant(int32(1))},
{"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}},
{`[[], [""]]`, [][]string{{}, {""}}},
{`@a{ss} {}`, map[string]string{}},
{`{"foo": 1}`, map[string]int32{"foo": 1}},
{`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}},
{`{"a": <1>, "b": <"foo">}`,
map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}},
{`b''`, []byte{0}},
{`b"abc"`, []byte{'a', 'b', 'c', 0}},
{`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}},
{`[[0], b""]`, [][]byte{{0}, {0}}},
{"int16 0", int16(0)},
{"byte 0", byte(0)},
}
func TestParseVariant(t *testing.T) {
for i, v := range variantParseTests {
nv, err := ParseVariant(v.s, Signature{})
if err != nil {
t.Errorf("test %d: parsing failed: %s", i+1, err)
continue
}
if !reflect.DeepEqual(nv.value, v.v) {
t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v)
}
}
}

191
Godeps/_workspace/src/github.com/golang/glog/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

44
Godeps/_workspace/src/github.com/golang/glog/README generated vendored Normal file
View File

@ -0,0 +1,44 @@
glog
====
Leveled execution logs for Go.
This is an efficient pure Go implementation of leveled logs in the
manner of the open source C++ package
http://code.google.com/p/google-glog
By binding methods to booleans it is possible to use the log package
without paying the expense of evaluating the arguments to the log.
Through the -vmodule flag, the package also provides fine-grained
control over logging at the file level.
The comment from glog.go introduces the ideas:
Package glog implements logging analogous to the Google-internal
C++ INFO/ERROR/V setup. It provides functions Info, Warning,
Error, Fatal, plus formatting variants such as Infof. It
also provides V-style logging controlled by the -v and
-vmodule=file=2 flags.
Basic examples:
glog.Info("Prepare to repel boarders")
glog.Fatalf("Initialization failed: %s", err)
See the documentation for the V function for an explanation
of these examples:
if glog.V(2) {
glog.Info("Starting transaction...")
}
glog.V(2).Infoln("Processed", nItems, "elements")
The repository contains an open source version of the log package
used inside Google. The master copy of the source lives inside
Google, not here. The code in this repo is for export only and is not itself
under development. Feature requests will be ignored.
Send bug reports to golang-nuts@googlegroups.com.

1034
Godeps/_workspace/src/github.com/golang/glog/glog.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,124 @@
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// File I/O for logs.
package glog
import (
"errors"
"flag"
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"sync"
"time"
)
// MaxSize is the maximum size of a log file in bytes.
var MaxSize uint64 = 1024 * 1024 * 1800
// logDirs lists the candidate directories for new log files.
var logDirs []string
// If non-empty, overrides the choice of directory in which to write logs.
// See createLogDirs for the full list of possible destinations.
var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
func createLogDirs() {
if *logDir != "" {
logDirs = append(logDirs, *logDir)
}
logDirs = append(logDirs, os.TempDir())
}
var (
pid = os.Getpid()
program = filepath.Base(os.Args[0])
host = "unknownhost"
userName = "unknownuser"
)
func init() {
h, err := os.Hostname()
if err == nil {
host = shortHostname(h)
}
current, err := user.Current()
if err == nil {
userName = current.Username
}
// Sanitize userName since it may contain filepath separators on Windows.
userName = strings.Replace(userName, `\`, "_", -1)
}
// shortHostname returns its argument, truncating at the first period.
// For instance, given "www.google.com" it returns "www".
func shortHostname(hostname string) string {
if i := strings.Index(hostname, "."); i >= 0 {
return hostname[:i]
}
return hostname
}
// logName returns a new log file name containing tag, with start time t, and
// the name for the symlink for tag.
func logName(tag string, t time.Time) (name, link string) {
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
program,
host,
userName,
tag,
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
t.Minute(),
t.Second(),
pid)
return name, program + "." + tag
}
var onceLogDirs sync.Once
// create creates a new log file and returns the file and its filename, which
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
// successfully, create also attempts to update the symlink for that tag, ignoring
// errors.
func create(tag string, t time.Time) (f *os.File, filename string, err error) {
onceLogDirs.Do(createLogDirs)
if len(logDirs) == 0 {
return nil, "", errors.New("log: no log dirs")
}
name, link := logName(tag, t)
var lastErr error
for _, dir := range logDirs {
fname := filepath.Join(dir, name)
f, err := os.Create(fname)
if err == nil {
symlink := filepath.Join(dir, link)
os.Remove(symlink) // ignore err
os.Symlink(name, symlink) // ignore err
return f, fname, nil
}
lastErr = err
}
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
}

View File

@ -0,0 +1,333 @@
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package glog
import (
"bytes"
"fmt"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
// Test that shortHostname works as advertised.
func TestShortHostname(t *testing.T) {
for hostname, expect := range map[string]string{
"": "",
"host": "host",
"host.google.com": "host",
} {
if got := shortHostname(hostname); expect != got {
t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got)
}
}
}
// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.
type flushBuffer struct {
bytes.Buffer
}
func (f *flushBuffer) Flush() error {
return nil
}
func (f *flushBuffer) Sync() error {
return nil
}
// swap sets the log writers and returns the old array.
func (l *loggingT) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) {
l.mu.Lock()
defer l.mu.Unlock()
old = l.file
for i, w := range writers {
logging.file[i] = w
}
return
}
// newBuffers sets the log writers to all new byte buffers and returns the old array.
func (l *loggingT) newBuffers() [numSeverity]flushSyncWriter {
return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
}
// contents returns the specified log value as a string.
func contents(s severity) string {
return logging.file[s].(*flushBuffer).String()
}
// contains reports whether the string is contained in the log.
func contains(s severity, str string, t *testing.T) bool {
return strings.Contains(contents(s), str)
}
// setFlags configures the logging flags how the test expects them.
func setFlags() {
logging.toStderr = false
}
// Test that Info works as advertised.
func TestInfo(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
Info("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that the header has the correct format.
func TestHeader(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
defer func(previous func() time.Time) { timeNow = previous }(timeNow)
timeNow = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, .678901e9, time.Local)
}
Info("test")
var line, pid int
n, err := fmt.Sscanf(contents(infoLog), "I0102 15:04:05.678901 %d glog_test.go:%d] test\n", &pid, &line)
if n != 2 || err != nil {
t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog))
}
}
// Test that an Error log goes to Warning and Info.
// Even in the Info log, the source character will be E, so the data should
// all be identical.
func TestError(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
Error("test")
if !contains(errorLog, "E", t) {
t.Errorf("Error has wrong character: %q", contents(errorLog))
}
if !contains(errorLog, "test", t) {
t.Error("Error failed")
}
str := contents(errorLog)
if !contains(warningLog, str, t) {
t.Error("Warning failed")
}
if !contains(infoLog, str, t) {
t.Error("Info failed")
}
}
// Test that a Warning log goes to Info.
// Even in the Info log, the source character will be W, so the data should
// all be identical.
func TestWarning(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
Warning("test")
if !contains(warningLog, "W", t) {
t.Errorf("Warning has wrong character: %q", contents(warningLog))
}
if !contains(warningLog, "test", t) {
t.Error("Warning failed")
}
str := contents(warningLog)
if !contains(infoLog, str, t) {
t.Error("Info failed")
}
}
// Test that a V log goes to Info.
func TestV(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
logging.verbosity.Set("2")
defer logging.verbosity.Set("0")
V(2).Info("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that a vmodule enables a log in this file.
func TestVmoduleOn(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
logging.vmodule.Set("glog_test=2")
defer logging.vmodule.Set("")
if !V(1) {
t.Error("V not enabled for 1")
}
if !V(2) {
t.Error("V not enabled for 2")
}
if V(3) {
t.Error("V enabled for 3")
}
V(2).Info("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that a vmodule of another file does not enable a log in this file.
func TestVmoduleOff(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
logging.vmodule.Set("notthisfile=2")
defer logging.vmodule.Set("")
for i := 1; i <= 3; i++ {
if V(Level(i)) {
t.Errorf("V enabled for %d", i)
}
}
V(2).Info("test")
if contents(infoLog) != "" {
t.Error("V logged incorrectly")
}
}
// vGlobs are patterns that match/don't match this file at V=2.
var vGlobs = map[string]bool{
// Easy to test the numeric match here.
"glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail.
"glog_test=2": true,
"glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed.
// These all use 2 and check the patterns. All are true.
"*=2": true,
"?l*=2": true,
"????_*=2": true,
"??[mno]?_*t=2": true,
// These all use 2 and check the patterns. All are false.
"*x=2": false,
"m*=2": false,
"??_*=2": false,
"?[abc]?_*t=2": false,
}
// Test that vmodule globbing works as advertised.
func testVmoduleGlob(pat string, match bool, t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
defer logging.vmodule.Set("")
logging.vmodule.Set(pat)
if V(2) != Verbose(match) {
t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match)
}
}
// Test that a vmodule globbing works as advertised.
func TestVmoduleGlob(t *testing.T) {
for glob, match := range vGlobs {
testVmoduleGlob(glob, match, t)
}
}
func TestRollover(t *testing.T) {
setFlags()
var err error
defer func(previous func(error)) { logExitFunc = previous }(logExitFunc)
logExitFunc = func(e error) {
err = e
}
defer func(previous uint64) { MaxSize = previous }(MaxSize)
MaxSize = 512
Info("x") // Be sure we have a file.
info, ok := logging.file[infoLog].(*syncBuffer)
if !ok {
t.Fatal("info wasn't created")
}
if err != nil {
t.Fatalf("info has initial error: %v", err)
}
fname0 := info.file.Name()
Info(strings.Repeat("x", int(MaxSize))) // force a rollover
if err != nil {
t.Fatalf("info has error after big write: %v", err)
}
// Make sure the next log file gets a file name with a different
// time stamp.
//
// TODO: determine whether we need to support subsecond log
// rotation. C++ does not appear to handle this case (nor does it
// handle Daylight Savings Time properly).
time.Sleep(1 * time.Second)
Info("x") // create a new file
if err != nil {
t.Fatalf("error after rotation: %v", err)
}
fname1 := info.file.Name()
if fname0 == fname1 {
t.Errorf("info.f.Name did not change: %v", fname0)
}
if info.nbytes >= MaxSize {
t.Errorf("file size was not reset: %d", info.nbytes)
}
}
func TestLogBacktraceAt(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
// The peculiar style of this code simplifies line counting and maintenance of the
// tracing block below.
var infoLine string
setTraceLocation := func(file string, line int, ok bool, delta int) {
if !ok {
t.Fatal("could not get file:line")
}
_, file = filepath.Split(file)
infoLine = fmt.Sprintf("%s:%d", file, line+delta)
err := logging.traceLocation.Set(infoLine)
if err != nil {
t.Fatal("error setting log_backtrace_at: ", err)
}
}
{
// Start of tracing block. These lines know about each other's relative position.
_, file, line, ok := runtime.Caller(0)
setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.
Info("we want a stack trace here")
}
numAppearances := strings.Count(contents(infoLog), infoLine)
if numAppearances < 2 {
// Need 2 appearances, one in the log header and one in the trace:
// log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here
// ...
// github.com/glog/glog_test.go:280 (0x41ba91)
// ...
// We could be more precise but that would require knowing the details
// of the traceback format, which may not be dependable.
t.Fatal("got no trace back; log is ", contents(infoLog))
}
}
func BenchmarkHeader(b *testing.B) {
for i := 0; i < b.N; i++ {
logging.putBuffer(logging.header(infoLog))
}
}